From 378455e733f618cad624164d28d4ff673eae8d77 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:42:00 +0200 Subject: [PATCH 001/286] Fixed absence of case.for_customer in documentation generation, added test --- source/app/iris_engine/reporter/reporter.py | 1 + tests/requirements.txt | 1 + tests/tests_rest_reports.py | 16 ++++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/source/app/iris_engine/reporter/reporter.py b/source/app/iris_engine/reporter/reporter.py index b56f70c28..35f82e587 100644 --- a/source/app/iris_engine/reporter/reporter.py +++ b/source/app/iris_engine/reporter/reporter.py @@ -361,6 +361,7 @@ def _get_case_info(self): # Set date case_info['date'] = datetime.utcnow().strftime("%Y-%m-%d") + case_info['case']['for_customer'] = case_info['case'].get('client').get('customer_name') return case_info diff --git a/tests/requirements.txt b/tests/requirements.txt index 4d6d5ef1a..24996af1b 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1 +1,2 @@ requests >= 2.31.0, < 3.0.0 +python-docx ~= 1.1 diff --git a/tests/tests_rest_reports.py b/tests/tests_rest_reports.py index 790d4207f..fa1e359a5 100644 --- a/tests/tests_rest_reports.py +++ b/tests/tests_rest_reports.py @@ -17,6 +17,8 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from unittest import TestCase +from io import BytesIO +from docx import Document from iris import Iris @@ -43,3 +45,17 @@ def test_generate_report__in_safe_mode_should_return_200(self): response = self._subject.get(f'/case/report/generate-investigation/{report_identifier}', {'cid': case_identifier, 'safe': True}) self.assertEqual(200, response.status_code) + + def test_generate_report_should_render_variable_case_for_customer(self): + data = {'report_name': 'name', 'report_type': 1, 'report_language': 1, 'report_description': 'description', + 'report_name_format': 'report_name_format'} + response = self._subject.post_multipart_encoded_file('/manage/templates/add', data, + 'data/report_templates/variable_case_for_customer_report_template.docx').json() + report_identifier = response['data']['report_id'] + case_identifier = self._subject.create_dummy_case() + response = self._subject.get(f'/case/report/generate-investigation/{report_identifier}', + {'cid': case_identifier, 'safe': True}) + with BytesIO(response.content) as content: + document = Document(content) + self.assertEqual('IrisInitialClient', document.paragraphs[0].text) + From f37a1ae2c93e9aa8fc4d7e42aaa2826ae317ba5a Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:47:53 +0200 Subject: [PATCH 002/286] Forgot report template file for test --- ...riable_case_for_customer_report_template.docx | Bin 0 -> 4355 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/data/report_templates/variable_case_for_customer_report_template.docx diff --git a/tests/data/report_templates/variable_case_for_customer_report_template.docx b/tests/data/report_templates/variable_case_for_customer_report_template.docx new file mode 100644 index 0000000000000000000000000000000000000000..2bf2c006801c921916a6a5c4643a8dfc62cbc2f2 GIT binary patch literal 4355 zcmaJ^2UL^W(xr#qL8OE97C@@Pl~ANtDGG!lgc2aMNRuL6K!r$=UZq|^il87MMM!9& zD@X?wP>@dO{)zX!C;I;9WUZ`Zt+Vsx?3vj!+env?h!KyJloU_TvdR+gTu@*?+xkG< zeSs3V_sYlmx`ZIgn2l2=wQn=-qW4kgeO^^h-aSB6#@e?bG~lvp<@Z?-d6Q9mKC(G* zXOn%z1AUfnke!eK>a1`Dc}osANkRjrl!07!qrfJMzmmpuGOR@^W}^!2>9rH@cj>K_ z%Sd^bW$`!}njn>K^VXFjyk{CP>0#3Oc~ax=peU=?1og*SC-QPZ%(Z+`0!}t1*3at{ zB$x~W9f$3T0?WM*p)#qI0brNYy9ElA=*=54nuDOKz^z*fHyd823Kd&j_y6uH;~Cu7 zjC3EiSxgux?3`rV%`?&kkgg2jUz)_;SD640&**hOw-eE#)#4V+mr=~uo&(AH38Z+mHbysdG^>|qqNlbKh-r_ktt{ZDYe zXT5eb-MyBO`|48UDqEIXW{xf`nXxw*Zz5CdAeG!|g4Hf*bH&y-Y}f*%>PeRPCfeA9 zbsNz`?-cc=8iFa9>JSz}hA`+2spSIUYNwL~rhCZ>x%1@g4E<8mEHOb|>dw0D0Ns|$ z5I1gx7FL>>#Xn`(fA@6&77$Gh{1KX{!_V{>s zQ-914?fLxJdwJo~lYG!I4a6biXX#k;dy_H-XhLplB9jg zpT{H9H{`?Q#SpjWliSmB!(`bO*OwjX1^EcX^~>R@$@*JYa+6Vj0^MO2D#gZog1Vu| zcj%j5pzy?`{1AgLv}+M2KsT4@#Nl@<><`>DR8~cx)Byg4E|desl_44J5=JyWob1!Y zzRC#Qh0;x17uSz_p`ljRV%rL$`SlM;+b*7pQZT6BC}R$YU$?2&bYtV!XnVMsdIaC} zV*@#RGY>ApUzi*P%!c)s9V#OJRTl3mi@%is5X(>yly@qNaG!`%zUt3 zQ(xA$LskTC_v=p0b$;MSe77o^LE0|6G@TU{UVoz?^rb2<_drZRczAMlS$6f_;47Pg zm1`0Ikj>U^`VxpGoAZxkvt!Bj_w;cD{-Y7tzPRH7fx&T{|DzWOeR3@E(U@G`{@D^U zT0{v<-Ai;2Pb#OOU5>0rDCjO>yrs}$$ETB!4Jn|l?eQb<5WwT%Ju&U@0^^2}5;`xT zm!tg860}>91}4^Im44w}Yz#y%)L~tLM&kR@eD4b4l0O{Tn23dR=9mgja_1GVMjRWG zFcJz(nGu{+_1;UBk9||^0d|XK`x~vy%8Hl|hjGMcrL=3!?OjhIc^4!%BjJ-%zJVo!~5{7}>eHD*-FNV+pdfa%5=!n*ATb|_kW#qySyx7cYwx(@!}!2q8|4+`DXc^@sY1}WybV6f#YYpc;I_qrVh;YM(xN|F>07$pgBp0c$breecr#VCO?^^PO2f9OiaV^zlHf%5@$rv(cp&kkpCY3U!qoq)V zJ8)Po5Lz#n)M&X6`U!pd>YwvgOy5L^GVY1MUoSDA53(7SKd#lFA^}4&x^#%s7AZ0n z$qcWW2dJ6#Pb=v}-6n`3Yx4UjR!YUIr#`irEU(e+L=(V%MsCW631)VSIu03rm?6w~l+5>_anNuhhk!xK0Buv?^u8vSlx|nK4Nj6B9dq@n!d?y;u<$FKW9Z4~`S9en#tq98B=nmd$x zaX+|>_x_Au<&c<$EErlLt=S}^&rm9GlxrbDZh0@voX{fW%-VdC)V6S4eY0gruE!9Q z*JBMed{|hK$C;pW+V2X(^-6O}OnU)Ichs z>fLzDXyYg>b~J!^jH9xpGN>8NDw}jduV&0t7LCl2m~*V1WnH|BC+jAAfT1`P>J5PG z5Nz5JEuT%$6$>9l&V@(5bY}0GqTec!e!4G#@7c8LplbXFhiI~tZD;^hL&l7ezHtF?OiuC|yuR9IgR za#udfJRmH`aHYF3hL|(6sMYcBNPO}{o$N<#AIZyI<*M5lcN3uxSSvRx;wl-}oY>gt zU$w;*{)1L_Kr^=R9mxMw`1JoSd?!yB+|>TI`;QH)5;2G!;gsDQcjJXHhz|Qq5%NsA z^%veOYxsI!N3^u9C&N3YfQ(5cl>j+!-IL|x(1y~T7Jq20Sej}9vPe&qlHW|uo&2;h zs3mMnhc}9xomgxaUt`eOe^lbRu)1-%veqn9Q(Vo!LqsizM!Jw$t#|w_=k25IZuncX zKGQ-i6AW6fcC9_28k7e)V7*x3X0=F>YL&>7tIoplRwi7gfl2x5Xt@NiHTcK-JK#68$;AY*QzHZ%AOCi-%a2ZZN>zetk1tTCmQxq%YDonQb?7 z`omY@R7xwVNBlWVho$3L;7gc3b6rO(Ox64%F9dky8pb0A)&0_@L%!CZT!v_0&#D2X z`Qn;NU`0}F8T2*s%LfQ3iRbC)YE|sv=sKDvBLpAEJG12^nF4l}uMqx;_x}tpUkDuT z3UmG;IglhNSf?bV1}3*-p0kf^NKTg5S+ohR6NINx;((tfqy@Le>_VVKoK42Tc_$}V z*x#HQO-n*FL7ffH^4sjn?<`a-d_8HMR6|Tgy`y9_>U?nV(ZHyE#bxOPkqfmvAA?4Q zE+Z|=-S6%SJYkd~{VEjLxvgg*YYcVZO;GGB;~J}~ut|}ZP`M-;c0X8ZoG!Vmjn(g% ze2nA8M27c#U1N57p%%<(Xw$MqeByx~m+zVsOBy>3+V!o5U5{p`+Z{{T1$#*w6LGM= zcjVGxvLzA7!RNblpfdyH%c~SR4e+wqS#d<{>i!|D+}yA})NF?WN0{o#`}l2a8G2$# z``=E00eiwq4O^txjmOqB&Ox9uK~&&^MoNmTBT%o=oA(Z=^GkCl0LmHYzGDDi~N_ zGtaO#-pZXoyCz$l3sjNHyGX$*TJIfC91N$bfAFS;WTgupq}@1-57$!L<%Wb($XYe< zG+Hffr6u@0o_0Uxt>EteVD7_Qt>=(R{e}I-**&k~u35P2wOcX0RY>5|J!e~nXRv)d zldgfq!|KKHj_RkP`Jd^Q{7h7&oh6hcqp5ZkVXAcu8<*ATV+M0-IG=Zc{1grdo68Ku zS;HylA6+Wk!-SVq@dI{miljKwMly3ha(u%kc;w~JJaNVa@?RMovR&tx@q$p#E|tcy zt%<~ID_@DF$zncPEuA=ewCJX8zE*tNP9ZPXjt){HB*m8vL#S>{Crb9XV%~^d`!O~t z#_5oz*o4^p7@PP6jCemS>v`*jv#h_yIRk5?`z!Fgr@%G9&!ERX>c4#6@9^_mGR}DX zj9RSD{RjRp8}d8){8EYAq<;n%(VzF~-+$@+O7ZJ8B(QA#*Gl?3{Jb#Z_Ku$s1o#*H z=N9ri_`JH{wEJiHVs-w Date: Wed, 8 Oct 2025 14:04:05 +0200 Subject: [PATCH 003/286] 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 004/286] 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 005/286] 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 006/286] 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 007/286] 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 008/286] 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 009/286] 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 010/286] 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 011/286] 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 012/286] 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 013/286] 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 014/286] 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 7a24e1ef04a4ace0d2ecfbfbdab79b3c24ca2093 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 18:29:06 +0200 Subject: [PATCH 015/286] Fixed problem with filter_id --- .../app/blueprints/rest/v2/alerts_filters.py | 2 ++ source/app/business/alerts_filters.py | 25 +++++++++++++++++++ 2 files changed, 27 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..7a05eff3e 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -26,6 +26,7 @@ from app.blueprints.iris_user import iris_current_user from app.schema.marshables import SavedFilterSchema +from app.business.alerts_filters import alerts_filters_add class AlertsFiltersOperations: @@ -41,6 +42,7 @@ def create(self): try: new_saved_filter = self._load(request_data) + alerts_filters_add(new_saved_filter) return response_api_created(self._schema.dump(new_saved_filter)) except ValidationError as e: diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py new file mode 100644 index 000000000..4393b1188 --- /dev/null +++ b/source/app/business/alerts_filters.py @@ -0,0 +1,25 @@ +# 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 alerts_filters_add(new_saved_filter): + db.session.add(new_saved_filter) + db.session.commit() + From b898352c2f882add850fdbe282e27e552827d3a3 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 18:30:11 +0200 Subject: [PATCH 016/286] Fixed static 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 7a05eff3e..683a81d83 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -28,6 +28,7 @@ from app.business.alerts_filters import alerts_filters_add + class AlertsFiltersOperations: def __init__(self): From 0690eb01a6a0e22db711d326982d20b64d49a877 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 19:02:09 +0200 Subject: [PATCH 017/286] 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 018/286] 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 019/286] 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 020/286] 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 021/286] 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 022/286] 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 023/286] 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 024/286] 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 025/286] 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 026/286] 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 027/286] 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 028/286] 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 029/286] 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 030/286] 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 031/286] 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 032/286] 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 033/286] 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 034/286] 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 035/286] 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 036/286] 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 037/286] 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 038/286] 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 039/286] 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 040/286] 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 041/286] 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 042/286] 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 043/286] 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 044/286] 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 045/286] 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 046/286] 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 047/286] 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 048/286] 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 049/286] 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 050/286] 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 051/286] 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 052/286] 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 053/286] 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 054/286] 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 055/286] 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 056/286] 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 057/286] 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 058/286] 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 059/286] 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 060/286] 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 061/286] 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 062/286] 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 063/286] 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 064/286] 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 065/286] 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 066/286] 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 067/286] 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 068/286] 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 069/286] 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 070/286] 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 071/286] 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. From d912cac35770788b2f5031e40c2f69a61e85e2ce Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:42:35 +0200 Subject: [PATCH 072/286] Added rule RUF100 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 284baedb2..99b0cc9db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "PLR0402", "RET506", "TID252", "UP032", "W29"] +select = ["E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "PLR0402", "RET506", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.importlinter] From 74b9866fa65933fbd61532703e4567442ccd1ec4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:43:37 +0200 Subject: [PATCH 073/286] Added ruff rule RUF029 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 99b0cc9db..84d7adb24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "PLR0402", "RET506", "RUF100", "TID252", "UP032", "W29"] +select = ["E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.importlinter] From e0ba692d6399cf291d12746836890297c13ee262 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:56:30 +0200 Subject: [PATCH 074/286] Added ruff rules FURB14* --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 84d7adb24..2256ffa4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] +select = ["E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB14", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.importlinter] From 405e75856ca33c248a5dd8791fdca2f97897668d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:03:06 +0200 Subject: [PATCH 075/286] Added ruff rule ARG003 and ARG005 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2256ffa4c..126d9f89f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB14", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] +select = ["ARG003", "ARG005", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB14", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.importlinter] From 0d4f59f037ff54f235175516ab99b8fb4d44cab2 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:07:22 +0200 Subject: [PATCH 076/286] Added ruff rules PYI* --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 126d9f89f..6a26b64fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB14", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] +select = ["ARG003", "ARG005", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB14", "PLR0402", "PYI", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.importlinter] From 86e556b0c44b53e0487153396cf57c7e5ea6d4a5 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:12:02 +0200 Subject: [PATCH 077/286] Fixed and added all ruff B00* rules --- pyproject.toml | 2 +- source/app/blueprints/rest/alerts_routes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6a26b64fc..eacf6fd89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB14", "PLR0402", "PYI", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] +select = ["ARG003", "ARG005", "B00", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB14", "PLR0402", "PYI", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.importlinter] diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 4f73a7538..f1f3b9e16 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -439,7 +439,7 @@ def alerts_batch_update_route() -> Response: if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) - if getattr(alert, 'alert_owner_id') is None: + if alert.alert_owner_id is None: updates['alert_owner_id'] = iris_current_user.id if data.get('alert_owner_id') == "-1" or data.get('alert_owner_id') == -1: From 03298178f8c8ed7367366ee2c76d78afb4d9ea6e Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:16:19 +0200 Subject: [PATCH 078/286] Set ruff rules FURB142, FURB145 and FURB148 rather than RUF14* (I am not sure whether we want RUF140) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eacf6fd89..ccd0c9336 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB14", "PLR0402", "PYI", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] +select = ["ARG003", "ARG005", "B00", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "PYI", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.importlinter] From 4070b5562f1ac693bf1b162b5dd4bf0e9ab7e1c9 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:17:27 +0200 Subject: [PATCH 079/286] Removed PYI rules: we do not seem to use pyi --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ccd0c9336..a3bd0e2bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "PYI", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] +select = ["ARG003", "ARG005", "B00", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.importlinter] From 535556fb3860235e8256e56ac34fa5e5a5aa5e3e Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:14:54 +0200 Subject: [PATCH 080/286] Added dead code check with vulture --- .github/workflows/ci.yml | 8 +- .vulture.ignore.py | 1251 ++++++++++++++++++++++++++++++++++++++ pyproject.toml | 3 + 3 files changed, 1259 insertions(+), 3 deletions(-) create mode 100644 .vulture.ignore.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea55d631d..a37928455 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,12 +31,14 @@ jobs: uses: astral-sh/ruff-action@v3 with: args: check --output-format=github - - name: Check dependencies with import-linter + - name: Check import dependencies with import-linter run: | - python -m venv venv - source venv/bin/activate pip install import-linter PYTHONPATH=source lint-imports + - name: Looking for dead code with vulture + run: | + pip install vulture + vulture --verbose build-docker-db: name: Build docker db diff --git a/.vulture.ignore.py b/.vulture.ignore.py new file mode 100644 index 000000000..5c284050a --- /dev/null +++ b/.vulture.ignore.py @@ -0,0 +1,1251 @@ +ReverseProxied # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:44) +_.autoescape # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:99) +dropzone # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:115) +shutdown_session # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:148) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:14) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:15) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:16) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:17) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:28) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:63) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:43) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:40) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:17) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:18) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:19) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:20) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:31) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:50) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:18) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:19) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:20) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:21) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:182) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:37) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:18) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:19) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:20) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:21) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:55) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:31) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:42) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:30) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:13) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:14) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:15) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:16) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:27) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:16) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:17) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:18) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:19) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:347) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:12) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:13) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:14) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:15) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:24) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:50) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:12) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:13) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:14) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:15) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:73) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:14) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:15) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:16) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:17) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:38) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:79) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:57) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:12) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:13) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:14) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:15) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:27) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:43) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:16) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:17) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:18) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:19) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:46) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:41) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:14) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:15) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:16) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:17) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:47) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:16) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:17) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:18) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:19) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:33) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:41) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:93) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:37) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:41) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:56) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:16) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:17) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:18) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:19) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:118) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:12) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:13) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:14) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:15) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:26) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:16) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:17) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:18) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:19) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:125) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:29) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:58) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:51) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:14) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:15) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:16) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:17) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:54) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:14) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:15) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:16) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:17) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:67) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:14) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:15) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:16) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:17) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:32) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:67) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:14) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:15) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:16) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:17) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:29) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:14) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:15) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:16) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:17) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:32) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:22) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:23) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:24) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:25) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:239) +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:15) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:16) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:17) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:18) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:55) +_.token_user # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/access_controls.py:361) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:51) +interfaces # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:53) +_.resolve_iocs # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:60) +_.resolve_case # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:71) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:78) +total_count # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:81) +_.resolve_total_count # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:84) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:91) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:101) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:124) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:129) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:138) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:155) +_.resolve_cases # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:70) +_.resolve_case # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:78) +_.resolve_ioc # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:83) +ioc_create # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:92) +ioc_update # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:93) +ioc_delete # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:94) +case_create # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:96) +case_delete # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:97) +case_update # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:98) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:42) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:47) +total_count # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:50) +_.resolve_total_count # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:52) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:59) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:72) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:93) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:108) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:147) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:152) +SlicedResult # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/sliced_result.py:19) +_.is_anonymous # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/iris_user.py:33) +activities_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/activities/activities_routes.py:35) +alerts_list_view_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/alerts/alerts_routes.py:41) +alert_comment_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/alerts/alerts_routes.py:61) +case_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:44) +add_asset_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:64) +asset_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:83) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:101) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:104) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:105) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:109) +case_comment_asset_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:116) +case_graph # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_graphs_routes.py:34) +case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:46) +case_add_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:61) +case_view_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:76) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:90) +case_comment_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:102) +case_notes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_notes_routes.py:37) +case_comment_note_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_notes_routes.py:55) +case_rfile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_rfiles_routes.py:40) +case_edit_rfile_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_rfiles_routes.py:52) +case_add_rfile_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_rfiles_routes.py:68) +case_comment_evidence_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_rfiles_routes.py:77) +case_r # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_routes.py:49) +case_pipelines_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_routes.py:76) +groups_cac_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_routes.py:98) +case_md_helper # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_routes.py:110) +case_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:44) +case_add_task_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:56) +case_task_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:72) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:88) +case_comment_task_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:97) +case_timeline # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:49) +case_getgraph_page # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:61) +case_comment_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:70) +event_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:84) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:95) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:98) +case_filter_help_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:120) +case_add_event_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:129) +add_gtask_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dashboard/dashboard_routes.py:77) +edit_gtask_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dashboard/dashboard_routes.py:93) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dashboard/dashboard_routes.py:106) +datastore_add_file_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:38) +datastore_add_multi_files_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:54) +datastore_filter_help_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:70) +datastore_update_file_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:79) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:96) +datastore_info_file_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:103) +demo_landing # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/demo_landing/demo_landing.py:35) +dim_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dim_tasks/dim_tasks.py:38) +_validate_local_login # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:77) +oidc_login # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:148) +oidc_authorise # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:172) +mfa_setup # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:274) +mfa_verify # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:322) +manage_ac_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_access_control.py:36) +manage_ac_audit_user_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_access_control.py:47) +manage_ac_audit_users_page # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_access_control.py:59) +view_assets_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:38) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:49) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:50) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:51) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:52) +add_assets_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:59) +manage_attributes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_attributes_routes.py:36) +attributes_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_attributes_routes.py:47) +attributes_preview # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_attributes_routes.py:65) +update_case_classification_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:37) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:60) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:61) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:62) +add_case_classification_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:68) +update_case_state_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:37) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:60) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:61) +add_case_state_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:67) +manage_case_templates # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:36) +case_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:47) +add_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:87) +upload_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:127) +manage_index_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_cases_routes.py:51) +details_case_from_case_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_cases_routes.py:101) +manage_details_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_cases_routes.py:107) +add_case_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_cases_routes.py:113) +manage_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:46) +view_customer_page # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:58) +customer_add_contact_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:76) +customer_edit_contact_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:88) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:100) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:101) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:102) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:103) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:105) +view_customer_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:110) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:122) +add_customers_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:130) +update_evidence_type_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:37) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:60) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:61) +add_evidence_type_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:67) +manage_groups_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:41) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:54) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:55) +manage_groups_add_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:60) +manage_groups_members_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:73) +manage_groups_cac_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:88) +view_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:35) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:46) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:47) +add_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:55) +manage_modules_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:41) +add_module_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:52) +getmodule_param # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:64) +view_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:81) +manage_objects # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_objects_routes.py:33) +manage_settings # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_srv_settings_routes.py:38) +manage_report_templates # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_templates_routes.py:38) +add_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_templates_routes.py:51) +add_user_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:41) +view_user_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:55) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:69) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:70) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:71) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:72) +manage_user_group_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:80) +manage_user_customers_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:95) +manage_user_cac_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:112) +get_overview # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/overview/overview_routes.py:34) +user_settings # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/profile/profile_routes.py:35) +update_pwd_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/profile/profile_routes.py:47) +search_file_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/search/search_routes.py:34) +list_activities # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/activities_routes.py:30) +list_all_activities # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/activities_routes.py:43) +alerts_list_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:74) +alerts_add_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:195) +alerts_get_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:225) +alerts_similarities_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:259) +alerts_update_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:303) +alerts_batch_update_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:393) +alerts_batch_delete_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:469) +alerts_delete_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:514) +alerts_escalate_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:561) +alerts_merge_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:632) +alerts_unmerge_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:699) +alerts_batch_merge_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:755) +alerts_batch_escalate_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:832) +alert_comments_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:907) +alert_comment_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:936) +alert_comment_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:969) +alert_comment_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:999) +case_comment_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:1023) +api_ping # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/api_routes.py:28) +api_version # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/api_routes.py:34) +case_filter_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:65) +case_list_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:107) +case_assets_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:152) +deprecated_add_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:162) +case_upload_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:180) +deprecated_asset_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:279) +asset_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:298) +deprecated_asset_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:322) +case_comment_asset_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:338) +case_comment_asset_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:350) +case_comment_asset_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:387) +case_comment_asset_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:399) +case_comment_asset_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:406) +case_list_rfiles # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:55) +case_rfiles_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:70) +case_add_rfile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:80) +case_get_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:99) +case_edit_rfile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:112) +case_delete_rfile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:138) +case_comment_evidence_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:152) +case_comment_evidence_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:164) +case_comment_evidence_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:201) +case_comment_evidence_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:213) +case_comment_evidence_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:220) +case_graph_get_data # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_graphs_routes.py:34) +case_list_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:66) +case_ioc_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:93) +deprecated_case_add_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:103) +case_upload_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:122) +deprecated_case_delete_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:209) +deprecated_case_view_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:229) +case_update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:243) +case_comment_ioc_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:269) +case_comment_ioc_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:281) +case_comment_ioc_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:318) +case_comment_ioc_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:330) +case_comment_ioc_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:337) +case_note_detail # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:64) +case_note_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:114) +case_note_save # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:132) +case_note_list_history # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:155) +case_note_revision # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:171) +case_note_revision_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:187) +case_note_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:201) +case_directory_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:226) +case_directory_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:254) +case_directory_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:289) +case_load_notes_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:312) +case_notes_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:320) +case_search_notes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:330) +case_add_notes_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:344) +case_delete_notes_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:352) +case_get_notes_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:360) +case_filter_notes_directories # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:368) +case_edit_notes_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:381) +case_comment_note_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:389) +case_comment_note_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:401) +case_comment_note_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:438) +case_comment_note_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:450) +case_comment_note_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:457) +case_routes_exists # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:61) +desc_fetch # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:72) +summary_fetch # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:98) +activity_fetch # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:107) +export_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:116) +meta_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:123) +case_add_tasklog # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:131) +case_get_users # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:150) +group_cac_set_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:160) +user_cac_set_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:203) +case_update_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:253) +case_review # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:283) +case_get_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:57) +case_get_tasks_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:78) +case_task_status_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:88) +deprecated_case_add_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:107) +deprecated_case_task_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:134) +deprecated_case_edit_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:148) +deprecated_case_delete_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:178) +case_comment_task_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:191) +case_comment_task_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:204) +case_comment_task_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:242) +case_comment_task_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:256) +case_comment_task_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:264) +case_comments_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:85) +case_comment_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:97) +case_comment_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:111) +case_comment_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:123) +case_comment_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:130) +case_get_timeline_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:169) +case_getgraph_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:179) +case_getgraph # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:206) +case_gettimeline_api_nofilter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:237) +case_filter_timeline # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:331) +case_delete_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:625) +event_flag # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:641) +event_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:657) +case_edit_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:680) +case_add_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:717) +case_duplicate_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:749) +case_event_date_convert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:807) +case_events_upload_csv # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:832) +cases_context_search # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/context_routes.py:38) +set_ctx # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/context_routes.py:50) +logout # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:72) +get_cases_charts # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:108) +get_gtasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:137) +view_gtask # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:155) +utask_statusupdate # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:165) +add_gtask # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:202) +edit_gtask # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:238) +gtask_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:275) +list_own_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:299) +get_utasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:311) +get_reviews # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:329) +datastore_list_tree # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:58) +datastore_list_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:67) +datastore_info_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:91) +datastore_update_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:106) +datastore_move_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:154) +datastore_move_folder # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:176) +datastore_view_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:204) +datastore_add_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:228) +_.added_by_user_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:242) +_.file_date_added # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:243) +datastore_add_interactive_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:279) +datastore_add_folder # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:312) +datastore_rename_folder # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:336) +datastore_delete_folder_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:363) +datastore_delete_file_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:375) +dim_hooks_call # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dim_tasks_routes.py:45) +list_dim_hook_options_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dim_tasks_routes.py:152) +list_dim_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dim_tasks_routes.py:172) +filters_add_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:36) +filters_update_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:70) +filters_delete_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:104) +filters_get_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:134) +filters_list_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:161) +manage_ac_compute_effective_all_ac # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_access_control_routes.py:33) +manage_ac_compute_effective_ac # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_access_control_routes.py:42) +manage_ac_reset_mfa # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_access_control_routes.py:51) +manage_ac_audit_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_access_control_routes.py:60) +list_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:35) +get_case_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:53) +search_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:69) +list_alert_resolution # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:91) +get_case_alert_resolution # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:109) +search_alert_resolution # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:125) +list_anastatus # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:34) +list_compr_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:47) +list_outcome_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:55) +view_anastatus # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:72) +search_analysis_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:88) +manage_assets_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_routes.py:33) +list_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:41) +view_asset_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:64) +view_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:83) +add_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:114) +delete_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:144) +search_assets_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:175) +list_attributes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_attributes_routes.py:34) +update_attribute # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_attributes_routes.py:52) +list_case_classifications # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:39) +get_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:56) +update_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:76) +add_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:112) +delete_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:142) +search_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:166) +list_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:39) +get_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:54) +update_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:73) +add_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:111) +delete_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:141) +list_case_templates # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_templates_routes.py:42) +add_case_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_templates_routes.py:56) +update_case_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_templates_routes.py:94) +delete_case_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_templates_routes.py:136) +get_case_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:70) +manage_case_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:84) +api_delete_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:148) +api_reopen_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:162) +api_case_close # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:199) +api_add_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:244) +api_list_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:262) +update_case_info # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:270) +update_case_files # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:307) +manage_cases_uploadfiles # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:367) +list_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:54) +view_customer # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:64) +customer_update_contact # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:77) +customer_add_contact # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:106) +get_customer_case_stats # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:135) +view_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:219) +add_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:243) +delete_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:270) +delete_contact_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:292) +list_event_categories # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_event_categories_routes.py:31) +search_event_category # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_event_categories_routes.py:62) +list_evidence_types # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:39) +get_evidence_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:53) +update_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:73) +add_evidence_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:108) +delete_evidence_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:138) +manage_groups_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:59) +manage_groups_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:67) +manage_groups_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:94) +manage_groups_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:132) +manage_groups_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:152) +manage_groups_members_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:165) +manage_groups_members_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:192) +manage_groups_cac_add_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:217) +manage_groups_cac_delete_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:263) +list_ioc_types # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:38) +get_ioc_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:46) +add_ioc_type_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:57) +remove_ioc_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:79) +update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:100) +search_ioc_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:126) +manage_modules_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:49) +add_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:57) +update_module_param # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:95) +enable_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:121) +disable_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:140) +view_delete_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:150) +export_mod_config # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:164) +import_mod_config # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:178) +view_modules_hook # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:209) +site_map # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:219) +manage_make_db_backup # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_server_settings_routes.py:41) +manage_update_settings # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_server_settings_routes.py:55) +list_severities # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_severities_routes.py:33) +get_case_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_severities_routes.py:48) +search_analysis_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_severities_routes.py:63) +manage_tags_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_tags.py:34) +manage_tags_suggest # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_tags.py:74) +list_task_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_task_status_routes.py:29) +view_task_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_task_status_routes.py:44) +report_templates_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:55) +add_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:87) +_.created_by_user_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:99) +download_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:140) +delete_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:154) +list_tlp_types # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_tlps_routes.py:29) +get_tlp_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_tlps_routes.py:37) +manage_users_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:61) +manage_users_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:70) +add_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:111) +view_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:144) +manage_user_group_ # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:157) +manage_user_customers_ # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:186) +manage_user_cac_add_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:210) +manage_user_cac_delete_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:246) +update_user_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:285) +deactivate_user_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:316) +activate_user_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:338) +renew_user_api_key # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:357) +view_delete_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:377) +exists_user_restricted # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:408) +lookup_name_restricted # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:425) +manage_users_list_restricted # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:444) +get_overview_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/overview_routes.py:30) +user_renew_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:48) +user_has_permission # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:60) +update_user_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:83) +profile_set_theme # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:115) +profile_set_deletion_prompt # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:133) +_.has_deletion_confirmation # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:145) +profile_set_minisidebar # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:151) +_.has_mini_sidebar # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:163) +profile_refresh_permissions_and_ac # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:169) +profile_whoami # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:183) +user_is_admin # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:202) +download_case_activity # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/reports_route.py:41) +generate_report # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/reports_route.py:68) +search_file_post # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/search_routes.py:30) +alerts_list_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts.py:243) +create_alert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts.py:249) +get_alert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts.py:255) +update_alert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts.py:261) +create_alert_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_filters.py:110) +get_alert_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_filters.py:116) +update_alert_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_filters.py:122) +delete_alert_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_filters.py:128) +get_alerts_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:102) +create_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:108) +read_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:114) +update_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:120) +delete_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:126) +get_assets_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:115) +create_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:121) +get_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:127) +update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:133) +delete_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:139) +logout # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/auth.py:89) +refresh_token_endpoint # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/auth.py:127) +case_list_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/assets.py:160) +add_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/assets.py:166) +update_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/assets.py:178) +create_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/events.py:158) +get_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/events.py:164) +update_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/events.py:170) +get_evidences # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:160) +create_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:166) +get_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:172) +update_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:178) +delete_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:184) +get_case_iocs # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:166) +add_ioc_to_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:172) +get_case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:178) +update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:184) +delete_case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:190) +create_note # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes.py:147) +update_note # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes.py:159) +create_note_directory # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:148) +get_note_directory # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:154) +update_note_directory # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:160) +delete_note_directory # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:166) +get_note_directory_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:172) +case_get_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:158) +add_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:164) +get_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:170) +update_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:176) +delete_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:182) +get_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:188) +create_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:194) +case_routes_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:200) +rest_v2_cases_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:206) +case_routes_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:212) +list_own_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/dashboard.py:39) +list_own_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/dashboard.py:50) +list_own_reviews # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/dashboard.py:59) +get_event_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/events_routes/comments.py:116) +create_event_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/events_routes/comments.py:122) +get_event_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/events_routes/comments.py:128) +update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/events_routes/comments.py:134) +get_evidence_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/evidences_routes/comments.py:117) +create_evidence_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/evidences_routes/comments.py:123) +get_evidence_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/evidences_routes/comments.py:129) +update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/evidences_routes/comments.py:135) +get_case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs.py:109) +update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs.py:115) +delete_case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs.py:121) +get_iocs_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:115) +create_iocs_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:121) +get_ioc_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:127) +update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:133) +get_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/manage_routes/customers.py:71) +put_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/manage_routes/users.py:109) +get_notes_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:116) +create_notes_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:122) +get_note_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:128) +update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:134) +get_profile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/profile.py:60) +update_profile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/profile.py:66) +manage_list_tags # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tags.py:66) +get_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks.py:76) +delete_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks.py:82) +get_tasks_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks_routes/comments.py:116) +create_tasks_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks_routes/comments.py:122) +get_task_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks_routes/comments.py:128) +update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks_routes/comments.py:134) +_.note_creationdate # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/business/notes.py:46) +_.validate_config # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:52) +_.config_key_vault # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:59) +AuthenticationType # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:169) +local # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:170) +oidc_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:171) +result_backend # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:223) +broker_url # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:224) +result_extended # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:225) +result_serializer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:226) +worker_pool_restarts # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:227) +broker_connection_retry_on_startup # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:228) +CSRF_ENABLED # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:245) +SECRET_KEY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:247) +SECURITY_PASSWORD_SALT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:249) +SECURITY_LOGIN_USER_TEMPLATE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:251) +IRIS_ADM_EMAIL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:253) +IRIS_ADM_PASSWORD # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:254) +IRIS_ADM_USERNAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:255) +IRIS_ADM_API_KEY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:256) +SESSION_COOKIE_SAMESITE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:259) +SESSION_COOKIE_SECURE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:260) +SESSION_COOKIE_HTTPONLY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:261) +ACCESS_TOKEN_EXPIRES_MINUTES # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:263) +REFRESH_TOKEN_EXPIRES_DAYS # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:264) +PG_ACCOUNT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:266) +PG_PASSWD # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:267) +PGA_ACCOUNT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:268) +PGA_PASSWD # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:269) +PG_SERVER # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:270) +PG_PORT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:271) +PG_DB # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:272) +DB_RETRY_COUNT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:274) +DB_RETRY_DELAY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:275) +DEMO_DOMAIN # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:279) +DEMO_USERS_SEED # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:280) +DEMO_ADM_SEED # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:281) +MAX_CONTENT_LENGTH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:282) +IRIS_ALLOW_ORIGIN # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:288) +WTF_CSRF_TIME_LIMIT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:290) +SQLALCHEMY_TRACK_MODIFICATIONS # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:294) +SQLALCHEMY_DATABASE_URI # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:296) +SQLALCHEMY_BINDS # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:297) +SQALCHEMY_PIGGER_URI # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:301) +UPLOADED_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:308) +TEMPLATES_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:309) +UPDATES_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:311) +RELEASE_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:313) +RELEASE_SIGNATURE_KEY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:316) +PG_CLIENT_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:318) +ASSET_STORE_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:319) +DATASTORE_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:320) +ASSET_SHOW_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:321) +ORGANISATION_NAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:323) +LOGIN_BANNER_TEXT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:324) +LOGIN_PTFM_CONTACT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:325) +UPDATE_DIR_NAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:327) +DROPZONE_MAX_FILE_SIZE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:329) +DROPZONE_TIMEOUT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:331) +CELERY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:336) +DEVELOPMENT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:339) +DEVELOPMENT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:341) +TLS_ROOT_CA # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:344) +IRIS_NEW_USERS_DEFAULT_GROUP # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:348) +AUTHENTICATION_ACCOUNT_SERVICE_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:353) +AUTHENTICATION_PROXY_LOGOUT_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:354) +AUTHENTICATION_TOKEN_INTROSPECTION_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:355) +AUTHENTICATION_JWKS_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:356) +AUTHENTICATION_CLIENT_ID # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:357) +AUTHENTICATION_CLIENT_SECRET # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:358) +AUTHENTICATION_AUDIENCE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:359) +AUTHENTICATION_VERIFY_TOKEN_EXP # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:360) +AUTHENTICATION_TOKEN_VERIFY_MODE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:362) +AUTHENTICATION_INIT_ADMINISTRATOR_EMAIL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:364) +AUTHENTICATION_APP_ADMIN_ROLE_NAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:366) +LDAP_AUTHENTICATION_TYPE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:385) +LDAP_ATTRIBUTE_DISPLAY_NAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:394) +LDAP_ATTRIBUTE_MAIL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:395) +LDAP_TLS_VERSION # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:408) +LDAP_TLS_VERSION # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:410) +LDAP_TLS_VERSION # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:412) +LDAP_CONNECT_STRING # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:415) +PRIVATE_KEY_PASSWORD # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:428) +OIDC_ISSUER_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:439) +OIDC_CLIENT_ID # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:440) +OIDC_CLIENT_SECRET # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:441) +OIDC_AUTH_ENDPOINT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:442) +OIDC_TOKEN_ENDPOINT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:443) +OIDC_END_SESSION_ENDPOINT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:444) +OIDC_SCOPES # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:445) +OIDC_MAPPING_USERNAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:446) +OIDC_MAPPING_EMAIL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:447) +OIDC_MAPPING_USERGROUP # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:448) +OIDC_MAPPING_ROLES # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:449) +CACHE_TYPE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:452) +CACHE_DEFAULT_TIMEOUT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:453) +db_list_all_alerts # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:92) +add_alert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:279) +_.alert_severity # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:307) +register_related_alerts # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:887) +delete_related_alert_cache # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:951) +details # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:1003) +get_assets_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_assets_db.py:130) +update_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_assets_db.py:148) +get_case_summary # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_db.py:50) +case_set_desc_crc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_db.py:111) +case_name_exists # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_db.py:183) +update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_iocs_db.py:65) +add_ioc_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_iocs_db.py:176) +update_note # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:124) +add_note # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:171) +_.note_creationdate # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:174) +get_groups_short # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:189) +get_groups_detail # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:224) +add_note_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:272) +_.group_user # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:276) +delete_note_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:293) +update_note_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:337) +find_pattern_in_notes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:354) +get_case_notes_comments_count # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:386) +get_case_client # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/client/client_db.py:209) +ctx_get_user_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/context/context_db.py:31) +update_gtask_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:131) +update_utask_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:147) +get_task_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:166) +EvidenceStorage # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:23) +_.is_evidence_registered # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:24) +_.add_evidence # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:32) +_.added_by_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/modules_db.py:54) +get_module_config_from_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/modules_db.py:160) +module_list_available_hooks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/modules_db.py:230) +manage_ac_audit_users_db # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_access_control_db.py:36) +get_client_users # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_access_control_db.py:126) +add_tab_attribute # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_attribute_db.py:120) +add_tab_attribute_field # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_attribute_db.py:139) +_.note_creationdate # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_case_templates_db.py:232) +list_cases_dict_unrestricted # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_cases_db.py:85) +get_evidence_type_by_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_evidence_types_db.py:70) +search_evidence_type_by_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_evidence_types_db.py:83) +remove_case_access_from_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_groups_db.py:283) +list_users_id # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:69) +_.allow_alerts # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:151) +_.allow_alerts # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:175) +update_user_orgs # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:189) +change_user_primary_org # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:234) +remove_case_access_from_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:426) +get_users_id_view_from_user_id # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:567) +get_users_list_user_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:585) +get_users_list_restricted_user_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:601) +_.get_underlying_exception # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/persistence_error.py:25) +export_case_json_extended # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/reporter/report_db.py:51) +_.object_updated_by_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/states.py:49) +_.object_updated_by_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/states.py:57) +_.load_css # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/flask_dropzone/__init__.py:140) +_.load_js # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/flask_dropzone/__init__.py:163) +_.style # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/flask_dropzone/__init__.py:368) +random_filename # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/flask_dropzone/utils.py:34) +RegisterForm # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:45) +MultiCheckboxField # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:63) +widget # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:64) +option_widget # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:65) +report_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:105) +report_name_format # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:106) +AddOrganisationForm # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:128) +org_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:130) +org_url # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:131) +org_logo # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:132) +org_email # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:133) +org_nationality # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:134) +org_sector # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:135) +org_type # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:136) +case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:148) +CaseNoteForm # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:223) +ac_get_effective_permissions_from_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/access_control/utils.py:138) +ac_user_has_permission # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/access_control/utils.py:840) +check_module_compatibility # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:46) +get_mod_config_by_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:234) +register_hook # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:323) +_.wait_till_return # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:372) +_.max_retry # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:374) +deregister_from_hook # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:391) +on_task_init # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/tasker/tasks.py:33) +chunks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/tasker/tasks.py:92) +notify_server_ready_to_reboot # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:70) +notify_server_has_updated # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:74) +inner_init_server_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:78) +updates_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:79) +get_release_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:117) +alert_uuid # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:50) +alert_source_link # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:56) +alert_context # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:60) +alert_note # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:63) +severity_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:90) +resolution_status_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:106) +similarity_type # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:144) +matching_asset_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:145) +matching_ioc_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:146) +matching_asset # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:150) +matching_ioc # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:151) +org_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:75) +org_url # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:76) +org_logo # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:77) +org_email # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:78) +org_nationality # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:79) +org_sector # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:80) +org_type # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:81) +allow_alerts # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:191) +ctx_human_case # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:213) +has_mini_sidebar # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:218) +has_deletion_confirmation # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:219) +closing_note # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/cases.py:61) +reviewer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/cases.py:80) +tag_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/cases.py:170) +children # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/cases.py:204) +not_applicable # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:57) +to_be_determined # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:71) +last_update_date # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:130) +asset_enrichment # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:172) +created_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:199) +created_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:211) +created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:221) +contact_uuid # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:250) +object_updated_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:294) +updated_by # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:300) +CaseGraphAssets # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:321) +CaseGraphLinks # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:333) +source_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:338) +dest_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:339) +created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:367) +path_uuid # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:391) +file_date_added # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:408) +added_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:416) +data_parent # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:422) +ioc_asset_link_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:439) +CasesAssetsExt # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:454) +asset_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:460) +note_creationdate # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:474) +group_user # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:521) +link_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:533) +CaseKanban # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:543) +kanban_data # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:547) +acquisition_date # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:559) +chain_of_custody # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:569) +user_open # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:606) +user_close # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:607) +user_update # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:608) +tag_creation_date # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:617) +_.tag_creation_date # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:625) +user_open # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:671) +user_close # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:672) +user_update # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:673) +https_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:698) +http_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:699) +enforce_mfa # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:709) +added_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:717) +retry_on_fail # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:749) +max_retry # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:750) +wait_till_return # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:752) +IrisReport # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:758) +filter_name # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:806) +filter_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:807) +filter_data # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:808) +worker # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:835) +retries # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:836) +queue # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:837) +_._register_modules_pipelines # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/post_init.py:1459) +POSTGRES_BIGINT_MAX # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:106) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:183) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:185) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:186) +_.verify_directory_name # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:204) +user_roles_str # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:257) +user_isadmin # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:262) +user_primary_organisation_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:264) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:267) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:269) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:270) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:271) +_.verify_username # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:274) +_.verify_email # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:314) +_.verify_password # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:354) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:418) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:420) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:421) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:429) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:431) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:432) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:446) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:448) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:449) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:482) +CaseAddNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:509) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:552) +CaseGroupNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:579) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:587) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:589) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:607) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:609) +asset_enrichment # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:687) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:693) +sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:695) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:696) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:697) +_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:718) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:763) +created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:800) +_.validate_string_or_list_of_dict # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:840) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:889) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:891) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:923) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:925) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:926) +_.get_link # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:944) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:951) +sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:953) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:954) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:955) +_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:958) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1004) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1043) +sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1045) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1046) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1047) +_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1050) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1096) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1132) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1134) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1135) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1136) +event_comments_map # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1157) +children # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1159) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1161) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1163) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1164) +_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1195) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1268) +_.get_event_category_id # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1294) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1308) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1310) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1311) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1326) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1328) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1329) +_.file_date_added # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1369) +_.added_by_user_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1370) +http_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1476) +https_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1477) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1480) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1482) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1501) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1503) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1518) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1520) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1559) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1561) +case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1602) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1611) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1613) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1614) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1615) +_.classification_filter # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1618) +_.verify_customer # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1637) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1666) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1711) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1713) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1728) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1730) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1731) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1732) +_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1735) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1783) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1785) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1786) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1824) +log_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1864) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1866) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1867) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1879) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1881) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1893) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1895) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1913) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1915) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1916) +_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1919) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1953) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1997) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1999) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2000) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2001) +_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2004) +group_permissions_list # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2050) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2053) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2055) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2056) +_.parse_permissions # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2091) +AuthorizationOrganisationSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2119) +org_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2127) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2129) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2131) +has_deletion_confirmation # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2179) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2181) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2183) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2184) +validate_asset_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2189) +validate_asset_tlp # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2206) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2231) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2233) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2245) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2247) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2259) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2261) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2273) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2275) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2295) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2297) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2298) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2299) +_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2302) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2323) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2325) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2326) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2327) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2332) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2334) +ModuleHooksSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2338) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2345) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2347) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2348) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2349) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2354) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2356) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2357) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2358) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2363) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2365) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2366) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2367) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2374) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2376) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2377) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2378) +case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2399) +reviewer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2410) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2416) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2418) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2419) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2420) +_.classification_filter # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2423) +_.verify_customer # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2442) +reviewer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2481) +_.get_status_name # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2485) +_.get_protagonists # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2488) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2506) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2508) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2509) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2510) +user_roles_str # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2522) +user_api_key # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2528) +user_isadmin # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2530) +user_groups # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2533) +user_customers # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2535) +user_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2537) +user_primary_organisation_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2538) +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2540) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2542) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2543) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2544) +_.get_user_primary_organisation # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2548) +_.verify_username # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2554) +_.verify_email # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2594) +_.verify_password # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2634) diff --git a/pyproject.toml b/pyproject.toml index a3bd0e2bb..c3310d197 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,9 @@ preview = true select = ["ARG003", "ARG005", "B00", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] +[tool.vulture] +paths = ["source/app", ".vulture.ignore.py"] + [tool.importlinter] root_package = "app" include_external_packages = true From 9cc46dacf6065a59cce63ac9a5c5c590a5610c86 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:27:03 +0200 Subject: [PATCH 081/286] Renamed vulture ignore file so that ruff does not parse it as a python file --- .github/workflows/ci.yml | 2 +- .vulture.ignore.py => .vulture.ignore | 0 pyproject.toml | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename .vulture.ignore.py => .vulture.ignore (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a37928455..57f4dceda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: - name: Looking for dead code with vulture run: | pip install vulture - vulture --verbose + vulture build-docker-db: name: Build docker db diff --git a/.vulture.ignore.py b/.vulture.ignore similarity index 100% rename from .vulture.ignore.py rename to .vulture.ignore diff --git a/pyproject.toml b/pyproject.toml index c3310d197..4ae83ff47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ select = ["ARG003", "ARG005", "B00", "E101", "E225", "E23", "E24", "E3", "E4", " ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] -paths = ["source/app", ".vulture.ignore.py"] +paths = ["source/app", ".vulture.ignore"] [tool.importlinter] root_package = "app" From 3fb283ab6bd645ccef2b5998571d951c1c1a5d4c Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:30:00 +0200 Subject: [PATCH 082/286] Removed validate_asset_type from vulture ignore list to check it fails the build --- .vulture.ignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.vulture.ignore b/.vulture.ignore index 5c284050a..98ad4a24b 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -1181,7 +1181,6 @@ has_deletion_confirmation # unused variable (/home/ubuntu/code/dfir-iris/iris-w Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2181) load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2183) exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2184) -validate_asset_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2189) validate_asset_tlp # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2206) Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2231) load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2233) From 13bb46c25120d462e737dbbbf1f08c507616f2fb Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:35:00 +0200 Subject: [PATCH 083/286] Removed seemingly dead methods --- .vulture.ignore | 1 - source/app/schema/marshables.py | 34 --------------------------------- 2 files changed, 35 deletions(-) diff --git a/.vulture.ignore b/.vulture.ignore index 98ad4a24b..f97a8f61f 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -1181,7 +1181,6 @@ has_deletion_confirmation # unused variable (/home/ubuntu/code/dfir-iris/iris-w Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2181) load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2183) exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2184) -validate_asset_tlp # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2206) Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2231) load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2233) Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2245) diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index a45ab7777..d3d526826 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -2186,40 +2186,6 @@ class Meta: unknown = EXCLUDE -def validate_asset_type(asset_id: int) -> None: - """Validates the asset type ID. - - This function validates the asset type ID by checking if it exists in the database. - If the ID is invalid, it raises a validation error. - - Args: - asset_id: The asset type ID to validate. - - Raises: - ValidationError: If the asset type ID is invalid. - - """ - if not AssetsType.query.get(asset_id): - raise ValidationError("Invalid asset_type ID") - - -def validate_asset_tlp(tlp_id: int) -> None: - """Validates the asset TLP ID. - - This function validates the asset TLP ID by checking if it exists in the database. - If the ID is invalid, it raises a validation error. - - Args: - tlp_id: The asset TLP ID to validate. - - Raises: - ValidationError: If the asset TLP ID is invalid. - - """ - if not Tlp.query.get(tlp_id): - raise ValidationError("Invalid asset_tlp ID") - - class SeveritySchema(ma.SQLAlchemyAutoSchema): """Schema for serializing and deserializing Severity objects. From e89de9e29bdf623b22815a61a32e1e02fc6fc0da Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:38:12 +0200 Subject: [PATCH 084/286] Commented seemingly dead code which is used by iris-module-interface --- .vulture.ignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vulture.ignore b/.vulture.ignore index f97a8f61f..746a76dc2 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -1,3 +1,4 @@ +EvidenceStorage # Do not remove: this is used by package iris-module-interface (see https://github.com/dfir-iris/iris-module-interface, iris_interface/IrisModuleInterface.py) ReverseProxied # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:44) _.autoescape # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:99) dropzone # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:115) @@ -902,7 +903,6 @@ ctx_get_user_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/sour update_gtask_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:131) update_utask_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:147) get_task_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:166) -EvidenceStorage # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:23) _.is_evidence_registered # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:24) _.add_evidence # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:32) _.added_by_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/modules_db.py:54) From e080126d080d76aa86c2252be6d492cc12ce4c75 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 08:03:40 +0100 Subject: [PATCH 085/286] Started implementation of PUT /api/v2/manage/customers/{identifier} --- .../rest/v2/manage_routes/customers.py | 21 ++++++++++++++++++- source/app/business/customers.py | 7 ++++++- source/app/datamgmt/client/client_db.py | 20 ++++++++++-------- tests/tests_rest_customers.py | 9 ++++++++ 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index 57c42582b..1e3f0bb2f 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -30,6 +30,7 @@ from app.business.errors import ObjectNotFoundError from app.business.customers import customers_create_with_user from app.business.customers import customers_get +from app.business.customers import customers_update from app.blueprints.iris_user import iris_current_user @@ -56,6 +57,19 @@ def read(self, identifier): except ObjectNotFoundError: return response_api_not_found() + def update(self, identifier): + try: + customer = customers_get(identifier) + request_data = request.get_json() + updated_customer = self._schema.load(request_data, instance=customer) + customers_update() + result = self._schema.dump(updated_customer) + return response_api_success(result) + except ValidationError as e: + return response_api_error('Data error', data=e.messages) + except ObjectNotFoundError: + return response_api_not_found() + customers_blueprint = Blueprint('customers_rest_v2', __name__, url_prefix='/customers') @@ -70,5 +84,10 @@ def create_customer(): @customers_blueprint.get('/') @ac_api_requires(Permissions.customers_read) -def get_event(identifier): +def get_customer(identifier): return customers.read(identifier) + +@customers_blueprint.put('/') +@ac_api_requires(Permissions.customers_write) +def put_customer(identifier): + return customers.update(identifier) diff --git a/source/app/business/customers.py b/source/app/business/customers.py index a38639317..897f5e0bd 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -16,12 +16,13 @@ # 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_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 +from app.datamgmt.client.client_db import create_customer from app.datamgmt.client.client_db import get_customer from app.datamgmt.client.client_db import get_customer_by_name +from app.datamgmt.client.client_db import update_customer from app.business.errors import ObjectNotFoundError @@ -48,3 +49,7 @@ def customers_get_by_name(name) -> Client: if not customer: raise ObjectNotFoundError() return customer + + +def customers_update(): + update_customer() \ No newline at end of file diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index 1f250cb11..521ac5c54 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -163,11 +163,15 @@ def update_contact(data, contact_id, customer_id) -> Contact: return contact +def update_customer(): + db.session.commit() + + def update_client(client_id: int, data) -> Client: # TODO: Possible reuse somewhere else ... - client = get_customer(client_id) + customer = get_customer(client_id) - if not client: + if not customer: raise ElementNotFoundException('No Customer found with this uuid.') exists = Client.query.filter( @@ -177,16 +181,16 @@ def update_client(client_id: int, data) -> Client: if exists: raise marshmallow.exceptions.ValidationError( - "Customer already exists", - field_name="customer_name" + 'Customer already exists', + field_name='customer_name' ) client_schema = CustomerSchema() - client_schema.load(data, instance=client) + client_schema.load(data, instance=customer) - db.session.commit() + update_customer() - return client + return customer def delete_client(client_id: int) -> None: @@ -198,10 +202,8 @@ def delete_client(client_id: int) -> None: raise ElementNotFoundException('No Customer found with this uuid.') try: - db.session.delete(client) db.session.commit() - except Exception: raise ElementInUseException('A currently referenced customer cannot be deleted') diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index 1615fa36f..512f5f76a 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -94,3 +94,12 @@ def test_get_customer_should_return_405_when_user_has_no_permission_to_read_cust user = self._subject.create_dummy_user() response = user.get(f'/api/v2/manage/customers/{identifier}') self.assertEqual(403, response.status_code) + + def test_put_customer_should_return_200(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + body = {'customer_name': 'new name'} + response = self._subject.update(f'/api/v2/manage/customers/{identifier}', body) + self.assertEqual(200, response.status_code) From b400f726f0d44d3c87b510545e4490e2b992bca8 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:44:58 +0100 Subject: [PATCH 086/286] PUT /api/v2/manage/customers/{identifier} should return 400 when another customer with the same name exists --- .../rest/v2/manage_routes/customers.py | 2 ++ source/app/business/customers.py | 2 +- source/app/schema/marshables.py | 25 +++++++++++-------- tests/tests_rest_customers.py | 25 +++++++++++++++++++ 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index 1e3f0bb2f..eaeb9024c 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -61,6 +61,7 @@ def update(self, identifier): try: customer = customers_get(identifier) request_data = request.get_json() + request_data['customer_id'] = identifier updated_customer = self._schema.load(request_data, instance=customer) customers_update() result = self._schema.dump(updated_customer) @@ -87,6 +88,7 @@ def create_customer(): def get_customer(identifier): return customers.read(identifier) + @customers_blueprint.put('/') @ac_api_requires(Permissions.customers_write) def put_customer(identifier): diff --git a/source/app/business/customers.py b/source/app/business/customers.py index 897f5e0bd..043d56eb5 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -52,4 +52,4 @@ def customers_get_by_name(name) -> Client: def customers_update(): - update_customer() \ No newline at end of file + update_customer() diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index d3d526826..c459cc576 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -629,11 +629,11 @@ def verify_unique(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: """ assert_type_mml(input_var=data.asset_name, - field_name="asset_name", + field_name='asset_name', type=str) assert_type_mml(input_var=data.asset_id, - field_name="asset_id", + field_name='asset_id', type=int, allow_none=True) @@ -1786,6 +1786,18 @@ class Meta: exclude = ['name', 'client_id', 'description', 'sla'] unknown = EXCLUDE + @pre_load + def verify_unique_name(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: + if 'customer_name' not in data: + return data + customer = Client.query.filter( + func.upper(Client.name) == data['customer_name'].upper(), + Client.client_id != data.get('customer_id') + ).first() + if customer: + raise ValidationError('Customer already exists', field_name='customer_name') + return data + @post_load def verify_unique(self, data: Client, **kwargs: Any) -> Client: """Verifies that the customer name is unique. @@ -1812,13 +1824,6 @@ def verify_unique(self, data: Client, **kwargs: Any) -> Client: type=int, allow_none=True) - client = Client.query.filter( - func.upper(Client.name) == data.name.upper(), - Client.client_id != data.client_id - ).first() - if client: - raise ValidationError("Customer already exists", field_name="customer_name") - return data @post_load @@ -2159,7 +2164,7 @@ def verify_unique(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: for organisation in organisations: if data.get('org_id') is None or organisation.org_id != data.get('org_id'): - raise ValidationError("Organisation name already exists", field_name="org_name") + raise ValidationError('Organisation name already exists', field_name='org_name') return data diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index 512f5f76a..2a374d837 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -59,6 +59,10 @@ def test_create_customer_should_return_400_when_another_customer_with_the_same_n response = self._subject.create('/api/v2/manage/customers', body) self.assertEqual(400, response.status_code) + def test_create_customer_should_return_400_when_field_customer_name_is_not_provided(self): + response = self._subject.create('/api/v2/manage/customers', {}) + self.assertEqual(400, response.status_code) + def test_create_customer_should_add_an_activity(self): body = {'customer_name': 'customer_name'} self._subject.create('/api/v2/manage/customers', body) @@ -103,3 +107,24 @@ def test_put_customer_should_return_200(self): body = {'customer_name': 'new name'} response = self._subject.update(f'/api/v2/manage/customers/{identifier}', body) self.assertEqual(200, response.status_code) + + def test_put_customer_should_return_400_when_another_customer_with_the_same_name_already_exists(self): + body = {'customer_name': 'already existing name'} + self._subject.create('/api/v2/manage/customers', body).json() + + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + body = {'customer_name': 'already existing name'} + response = self._subject.update(f'/api/v2/manage/customers/{identifier}', body) + self.assertEqual(400, response.status_code) + + def test_put_customer_should_return_200_when_updating_with_same_name(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + body = {'customer_name': 'customer'} + response = self._subject.update(f'/api/v2/manage/customers/{identifier}', body) + self.assertEqual(200, response.status_code) From e6625f15a29b1fa9b942136dc60e3a5a376a54d9 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:10:57 +0100 Subject: [PATCH 087/286] Moved database code out of marshables into the persistence layer --- .../rest/manage/manage_customers_routes.py | 24 +++++-- source/app/business/customers.py | 9 +++ source/app/datamgmt/client/client_db.py | 68 +++++++------------ source/app/schema/marshables.py | 9 ++- 4 files changed, 56 insertions(+), 54 deletions(-) diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index 817cdf86e..9c8726ae0 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -34,6 +34,7 @@ from app.datamgmt.client.client_db import get_client_cases from app.datamgmt.client.client_db import get_client_contacts from app.datamgmt.client.client_db import get_client_list +from app.datamgmt.client.client_db import get_client_contact from app.datamgmt.client.client_db import update_client from app.datamgmt.client.client_db import update_contact from app.datamgmt.exceptions.ElementExceptions import ElementInUseException @@ -86,8 +87,13 @@ def customer_update_contact(client_id, contact_id): return response_error(f"Invalid Customer ID {client_id}") try: + data = request.json + contact = get_client_contact(client_id, contact_id) + data['client_id'] = client_id + contact_schema = ContactSchema() + contact_schema.load(data, instance=contact) - contact = update_contact(request.json, contact_id, client_id) + update_contact() except ValidationError as e: return response_error(msg='Error update contact', data=e.messages) @@ -113,10 +119,14 @@ def customer_add_contact(client_id): if not get_customer(client_id): return response_error(f"Invalid Customer ID {client_id}") + contact_schema = ContactSchema() try: + data = request.json + data['client_id'] = client_id + contact = contact_schema.load(data) - contact = create_contact(request.json, client_id) + create_contact(contact) except ValidationError as e: return response_error(msg='Error adding contact', data=e.messages) @@ -128,7 +138,6 @@ def customer_add_contact(client_id): track_activity(f"Added contact {contact.contact_name}", ctx_less=True) # Return the customer - contact_schema = ContactSchema() return response_success("Added successfully", data=contact_schema.dump(contact)) @@ -223,11 +232,13 @@ def view_customers(client_id): if not request.is_json: return response_error("Invalid request") + client_schema = CustomerSchema() try: - client = update_client(client_id, request.json) + customer = get_customer(client_id) + if not customer: + raise response_error('Invalid Customer ID') - except ElementNotFoundException: - return response_error('Invalid Customer ID') + client = update_client(client_schema, customer, request.json) except ValidationError as e: return response_error("", data=e.messages) @@ -236,7 +247,6 @@ def view_customers(client_id): print(traceback.format_exc()) return response_error(f'An error occurred during Customer update. {e}') - client_schema = CustomerSchema() return response_success("Customer updated", client_schema.dump(client)) diff --git a/source/app/business/customers.py b/source/app/business/customers.py index 043d56eb5..fed142715 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -51,5 +51,14 @@ def customers_get_by_name(name) -> Client: return customer +def customers_exists_another_with_same_name(identifier, name: str) -> bool: + customer = get_customer_by_name(name, case_insensitive=True) + if not customer: + return False + if customer.client_id == identifier: + return False + return True + + def customers_update(): update_customer() diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index 521ac5c54..c93fccfa6 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -30,8 +30,22 @@ from app.models.models import Contact from app.models.authorization import User from app.models.authorization import UserClient -from app.schema.marshables import ContactSchema -from app.schema.marshables import CustomerSchema + + +# TODO most probably rename into add (or save?) and make more generic +def create_contact(contact): + db.session.add(contact) + db.session.commit() + + +# TODO most probably rename into update (or save?) and make more generic, maybe just use the preceding method? +def update_contact(): + db.session.commit() + + +# TODO same +def update_customer(): + db.session.commit() def get_client_list(current_user_id: int = None, @@ -141,41 +155,9 @@ def delete_contact(contact_id: int) -> None: raise ElementInUseException('A currently referenced contact cannot be deleted') -def create_contact(data, customer_id) -> Contact: - data['client_id'] = customer_id - contact_schema = ContactSchema() - contact = contact_schema.load(data) - - db.session.add(contact) - db.session.commit() - - return contact - - -def update_contact(data, contact_id, customer_id) -> Contact: - contact = get_client_contact(customer_id, contact_id) - data['client_id'] = customer_id - contact_schema = ContactSchema() - contact_schema.load(data, instance=contact) - - db.session.commit() - - return contact - - -def update_customer(): - db.session.commit() - - -def update_client(client_id: int, data) -> Client: - # TODO: Possible reuse somewhere else ... - customer = get_customer(client_id) - - if not customer: - raise ElementNotFoundException('No Customer found with this uuid.') - +def update_client(schema, customer: Client, data): exists = Client.query.filter( - Client.client_id != client_id, + Client.client_id != customer.client_id, func.lower(Client.name) == data.get('customer_name').lower() ).first() @@ -185,13 +167,10 @@ def update_client(client_id: int, data) -> Client: field_name='customer_name' ) - client_schema = CustomerSchema() - client_schema.load(data, instance=customer) + schema.load(data, instance=customer) update_customer() - return customer - def delete_client(client_id: int) -> None: client = Client.query.filter( @@ -216,5 +195,10 @@ def get_case_client(case_id: int) -> Client: return client -def get_customer_by_name(name) -> Client: - return db.session.query(Client).filter_by(name=name).first() +def get_customer_by_name(name, case_insensitive=False) -> Client: + query = db.session.query(Client) + if case_insensitive: + query = query.filter(func.upper(Client.name) == name.upper()) + else: + query = query.filter_by(name=name) + return query.first() diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index c459cc576..00cc68810 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -42,6 +42,7 @@ from typing import Tuple from typing import Union from werkzeug.datastructures import FileStorage +from app.business.customers import customers_exists_another_with_same_name from app import db from app import ma from app.blueprints.iris_user import iris_current_user @@ -1790,11 +1791,9 @@ class Meta: def verify_unique_name(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: if 'customer_name' not in data: return data - customer = Client.query.filter( - func.upper(Client.name) == data['customer_name'].upper(), - Client.client_id != data.get('customer_id') - ).first() - if customer: + identifier = data.get('customer_id') + name = data['customer_name'] + if customers_exists_another_with_same_name(identifier, name): raise ValidationError('Customer already exists', field_name='customer_name') return data From 39ac885a0ee1d97a7ed654c1895a9418260e761c Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:20:07 +0100 Subject: [PATCH 088/286] Added some ignored files to vulture --- .vulture.ignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vulture.ignore b/.vulture.ignore index 746a76dc2..b80f08fb4 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -764,6 +764,7 @@ create_iocs_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/sou get_ioc_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:127) update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:133) get_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/manage_routes/customers.py:71) +put_customer # unused function (source/app/blueprints/rest/v2/manage_routes/customers.py:92) put_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/manage_routes/users.py:109) get_notes_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:116) create_notes_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:122) @@ -1150,6 +1151,7 @@ _.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/ Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1783) load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1785) exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1786) +_.verify_unique_name # unused method (source/app/schema/marshables.py:1790) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1824) log_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1864) Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1866) From 33a4a4e4426b136f02573e5965ea12ca6fd5b4c7 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:34:26 +0100 Subject: [PATCH 089/286] Improved configuration of vulture somewhat --- .vulture.ignore | 88 ---------------------- pyproject.toml | 1 + source/app/blueprints/rest/v2/dashboard.py | 4 +- 3 files changed, 3 insertions(+), 90 deletions(-) diff --git a/.vulture.ignore b/.vulture.ignore index b80f08fb4..ecff68ef5 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -692,93 +692,6 @@ user_is_admin # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/ap download_case_activity # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/reports_route.py:41) generate_report # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/reports_route.py:68) search_file_post # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/search_routes.py:30) -alerts_list_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts.py:243) -create_alert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts.py:249) -get_alert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts.py:255) -update_alert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts.py:261) -create_alert_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_filters.py:110) -get_alert_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_filters.py:116) -update_alert_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_filters.py:122) -delete_alert_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_filters.py:128) -get_alerts_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:102) -create_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:108) -read_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:114) -update_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:120) -delete_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/alerts_routes/comments.py:126) -get_assets_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:115) -create_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:121) -get_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:127) -update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:133) -delete_alerts_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/assets_routes/comments.py:139) -logout # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/auth.py:89) -refresh_token_endpoint # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/auth.py:127) -case_list_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/assets.py:160) -add_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/assets.py:166) -update_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/assets.py:178) -create_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/events.py:158) -get_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/events.py:164) -update_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/events.py:170) -get_evidences # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:160) -create_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:166) -get_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:172) -update_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:178) -delete_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/evidences.py:184) -get_case_iocs # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:166) -add_ioc_to_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:172) -get_case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:178) -update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:184) -delete_case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/iocs.py:190) -create_note # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes.py:147) -update_note # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes.py:159) -create_note_directory # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:148) -get_note_directory # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:154) -update_note_directory # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:160) -delete_note_directory # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:166) -get_note_directory_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/notes_directories.py:172) -case_get_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:158) -add_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:164) -get_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:170) -update_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:176) -delete_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/case_routes/tasks.py:182) -get_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:188) -create_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:194) -case_routes_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:200) -rest_v2_cases_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:206) -case_routes_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/cases.py:212) -list_own_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/dashboard.py:39) -list_own_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/dashboard.py:50) -list_own_reviews # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/dashboard.py:59) -get_event_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/events_routes/comments.py:116) -create_event_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/events_routes/comments.py:122) -get_event_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/events_routes/comments.py:128) -update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/events_routes/comments.py:134) -get_evidence_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/evidences_routes/comments.py:117) -create_evidence_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/evidences_routes/comments.py:123) -get_evidence_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/evidences_routes/comments.py:129) -update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/evidences_routes/comments.py:135) -get_case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs.py:109) -update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs.py:115) -delete_case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs.py:121) -get_iocs_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:115) -create_iocs_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:121) -get_ioc_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:127) -update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/iocs_routes/comments.py:133) -get_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/manage_routes/customers.py:71) -put_customer # unused function (source/app/blueprints/rest/v2/manage_routes/customers.py:92) -put_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/manage_routes/users.py:109) -get_notes_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:116) -create_notes_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:122) -get_note_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:128) -update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/notes_routes/comments.py:134) -get_profile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/profile.py:60) -update_profile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/profile.py:66) -manage_list_tags # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tags.py:66) -get_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks.py:76) -delete_case_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks.py:82) -get_tasks_comments # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks_routes/comments.py:116) -create_tasks_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks_routes/comments.py:122) -get_task_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks_routes/comments.py:128) -update_assets_comment # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/v2/tasks_routes/comments.py:134) _.note_creationdate # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/business/notes.py:46) _.validate_config # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:52) _.config_key_vault # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:59) @@ -1151,7 +1064,6 @@ _.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/ Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1783) load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1785) exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1786) -_.verify_unique_name # unused method (source/app/schema/marshables.py:1790) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1824) log_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1864) Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1866) diff --git a/pyproject.toml b/pyproject.toml index 4ae83ff47..3d0b7edcf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] paths = ["source/app", ".vulture.ignore"] +ignore_decorators = ["@*.post", "@*.get", "@*.put", "@*.delete", "@pre_load"] [tool.importlinter] root_package = "app" diff --git a/source/app/blueprints/rest/v2/dashboard.py b/source/app/blueprints/rest/v2/dashboard.py index 2bed0ad3e..5eda20964 100644 --- a/source/app/blueprints/rest/v2/dashboard.py +++ b/source/app/blueprints/rest/v2/dashboard.py @@ -47,7 +47,7 @@ def list_own_cases(): # TODO this endpoint does not adhere to the conventions (verb in URL). # We should rather have /api/v2/tasks? -@dashboard_blueprint.route('/tasks/list', methods=['GET']) +@dashboard_blueprint.get('/tasks/list') @ac_api_requires() def list_own_tasks(): ct = tasks_filter_by_user() @@ -56,7 +56,7 @@ def list_own_tasks(): # TODO this endpoint does not adhere to the conventions (verb in URL). # We should rather have /api/v2/reviews? -@dashboard_blueprint.route('/reviews/list', methods=['GET']) +@dashboard_blueprint.get('/reviews/list') @ac_api_requires() def list_own_reviews(): reviews = cases_filter_by_reviewer(iris_current_user) From de86e58a335a55943fbe0382e194021b1b51e5ac Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:37:51 +0100 Subject: [PATCH 090/286] Improved configuration of vulture somewhat --- .vulture.ignore | 297 ------------------ pyproject.toml | 2 +- .../app/blueprints/rest/activities_routes.py | 2 +- 3 files changed, 2 insertions(+), 299 deletions(-) diff --git a/.vulture.ignore b/.vulture.ignore index ecff68ef5..b9c5b697b 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -395,303 +395,6 @@ get_overview # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app user_settings # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/profile/profile_routes.py:35) update_pwd_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/profile/profile_routes.py:47) search_file_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/search/search_routes.py:34) -list_activities # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/activities_routes.py:30) -list_all_activities # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/activities_routes.py:43) -alerts_list_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:74) -alerts_add_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:195) -alerts_get_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:225) -alerts_similarities_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:259) -alerts_update_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:303) -alerts_batch_update_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:393) -alerts_batch_delete_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:469) -alerts_delete_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:514) -alerts_escalate_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:561) -alerts_merge_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:632) -alerts_unmerge_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:699) -alerts_batch_merge_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:755) -alerts_batch_escalate_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:832) -alert_comments_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:907) -alert_comment_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:936) -alert_comment_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:969) -alert_comment_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:999) -case_comment_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/alerts_routes.py:1023) -api_ping # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/api_routes.py:28) -api_version # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/api_routes.py:34) -case_filter_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:65) -case_list_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:107) -case_assets_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:152) -deprecated_add_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:162) -case_upload_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:180) -deprecated_asset_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:279) -asset_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:298) -deprecated_asset_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:322) -case_comment_asset_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:338) -case_comment_asset_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:350) -case_comment_asset_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:387) -case_comment_asset_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:399) -case_comment_asset_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_assets_routes.py:406) -case_list_rfiles # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:55) -case_rfiles_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:70) -case_add_rfile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:80) -case_get_evidence # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:99) -case_edit_rfile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:112) -case_delete_rfile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:138) -case_comment_evidence_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:152) -case_comment_evidence_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:164) -case_comment_evidence_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:201) -case_comment_evidence_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:213) -case_comment_evidence_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_evidences_routes.py:220) -case_graph_get_data # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_graphs_routes.py:34) -case_list_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:66) -case_ioc_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:93) -deprecated_case_add_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:103) -case_upload_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:122) -deprecated_case_delete_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:209) -deprecated_case_view_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:229) -case_update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:243) -case_comment_ioc_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:269) -case_comment_ioc_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:281) -case_comment_ioc_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:318) -case_comment_ioc_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:330) -case_comment_ioc_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_ioc_routes.py:337) -case_note_detail # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:64) -case_note_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:114) -case_note_save # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:132) -case_note_list_history # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:155) -case_note_revision # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:171) -case_note_revision_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:187) -case_note_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:201) -case_directory_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:226) -case_directory_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:254) -case_directory_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:289) -case_load_notes_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:312) -case_notes_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:320) -case_search_notes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:330) -case_add_notes_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:344) -case_delete_notes_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:352) -case_get_notes_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:360) -case_filter_notes_directories # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:368) -case_edit_notes_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:381) -case_comment_note_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:389) -case_comment_note_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:401) -case_comment_note_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:438) -case_comment_note_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:450) -case_comment_note_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_notes_routes.py:457) -case_routes_exists # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:61) -desc_fetch # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:72) -summary_fetch # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:98) -activity_fetch # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:107) -export_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:116) -meta_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:123) -case_add_tasklog # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:131) -case_get_users # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:150) -group_cac_set_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:160) -user_cac_set_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:203) -case_update_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:253) -case_review # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_routes.py:283) -case_get_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:57) -case_get_tasks_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:78) -case_task_status_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:88) -deprecated_case_add_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:107) -deprecated_case_task_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:134) -deprecated_case_edit_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:148) -deprecated_case_delete_task # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:178) -case_comment_task_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:191) -case_comment_task_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:204) -case_comment_task_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:242) -case_comment_task_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:256) -case_comment_task_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_tasks_routes.py:264) -case_comments_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:85) -case_comment_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:97) -case_comment_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:111) -case_comment_edit # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:123) -case_comment_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:130) -case_get_timeline_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:169) -case_getgraph_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:179) -case_getgraph # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:206) -case_gettimeline_api_nofilter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:237) -case_filter_timeline # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:331) -case_delete_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:625) -event_flag # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:641) -event_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:657) -case_edit_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:680) -case_add_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:717) -case_duplicate_event # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:749) -case_event_date_convert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:807) -case_events_upload_csv # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/case/case_timeline_routes.py:832) -cases_context_search # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/context_routes.py:38) -set_ctx # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/context_routes.py:50) -logout # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:72) -get_cases_charts # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:108) -get_gtasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:137) -view_gtask # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:155) -utask_statusupdate # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:165) -add_gtask # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:202) -edit_gtask # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:238) -gtask_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:275) -list_own_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:299) -get_utasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:311) -get_reviews # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dashboard_routes.py:329) -datastore_list_tree # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:58) -datastore_list_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:67) -datastore_info_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:91) -datastore_update_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:106) -datastore_move_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:154) -datastore_move_folder # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:176) -datastore_view_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:204) -datastore_add_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:228) -_.added_by_user_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:242) -_.file_date_added # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:243) -datastore_add_interactive_file # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:279) -datastore_add_folder # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:312) -datastore_rename_folder # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:336) -datastore_delete_folder_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:363) -datastore_delete_file_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/datastore_routes.py:375) -dim_hooks_call # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dim_tasks_routes.py:45) -list_dim_hook_options_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dim_tasks_routes.py:152) -list_dim_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/dim_tasks_routes.py:172) -filters_add_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:36) -filters_update_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:70) -filters_delete_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:104) -filters_get_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:134) -filters_list_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/filters_routes.py:161) -manage_ac_compute_effective_all_ac # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_access_control_routes.py:33) -manage_ac_compute_effective_ac # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_access_control_routes.py:42) -manage_ac_reset_mfa # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_access_control_routes.py:51) -manage_ac_audit_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_access_control_routes.py:60) -list_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:35) -get_case_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:53) -search_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:69) -list_alert_resolution # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:91) -get_case_alert_resolution # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:109) -search_alert_resolution # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_alerts_status_routes.py:125) -list_anastatus # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:34) -list_compr_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:47) -list_outcome_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:55) -view_anastatus # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:72) -search_analysis_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_analysis_status_routes.py:88) -manage_assets_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_routes.py:33) -list_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:41) -view_asset_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:64) -view_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:83) -add_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:114) -delete_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:144) -search_assets_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_assets_type_routes.py:175) -list_attributes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_attributes_routes.py:34) -update_attribute # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_attributes_routes.py:52) -list_case_classifications # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:39) -get_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:56) -update_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:76) -add_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:112) -delete_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:142) -search_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_classifications_routes.py:166) -list_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:39) -get_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:54) -update_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:73) -add_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:111) -delete_case_state # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_state.py:141) -list_case_templates # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_templates_routes.py:42) -add_case_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_templates_routes.py:56) -update_case_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_templates_routes.py:94) -delete_case_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_case_templates_routes.py:136) -get_case_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:70) -manage_case_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:84) -api_delete_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:148) -api_reopen_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:162) -api_case_close # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:199) -api_add_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:244) -api_list_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:262) -update_case_info # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:270) -update_case_files # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:307) -manage_cases_uploadfiles # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_cases_routes.py:367) -list_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:54) -view_customer # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:64) -customer_update_contact # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:77) -customer_add_contact # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:106) -get_customer_case_stats # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:135) -view_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:219) -add_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:243) -delete_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:270) -delete_contact_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_customers_routes.py:292) -list_event_categories # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_event_categories_routes.py:31) -search_event_category # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_event_categories_routes.py:62) -list_evidence_types # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:39) -get_evidence_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:53) -update_case_classification # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:73) -add_evidence_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:108) -delete_evidence_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_evidence_types_routes.py:138) -manage_groups_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:59) -manage_groups_add # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:67) -manage_groups_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:94) -manage_groups_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:132) -manage_groups_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:152) -manage_groups_members_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:165) -manage_groups_members_delete # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:192) -manage_groups_cac_add_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:217) -manage_groups_cac_delete_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_groups.py:263) -list_ioc_types # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:38) -get_ioc_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:46) -add_ioc_type_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:57) -remove_ioc_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:79) -update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:100) -search_ioc_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_ioc_types_routes.py:126) -manage_modules_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:49) -add_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:57) -update_module_param # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:95) -enable_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:121) -disable_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:140) -view_delete_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:150) -export_mod_config # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:164) -import_mod_config # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:178) -view_modules_hook # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:209) -site_map # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_modules_routes.py:219) -manage_make_db_backup # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_server_settings_routes.py:41) -manage_update_settings # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_server_settings_routes.py:55) -list_severities # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_severities_routes.py:33) -get_case_alert_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_severities_routes.py:48) -search_analysis_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_severities_routes.py:63) -manage_tags_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_tags.py:34) -manage_tags_suggest # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_tags.py:74) -list_task_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_task_status_routes.py:29) -view_task_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_task_status_routes.py:44) -report_templates_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:55) -add_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:87) -_.created_by_user_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:99) -download_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:140) -delete_template # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_templates_routes.py:154) -list_tlp_types # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_tlps_routes.py:29) -get_tlp_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_tlps_routes.py:37) -manage_users_list # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:61) -manage_users_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:70) -add_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:111) -view_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:144) -manage_user_group_ # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:157) -manage_user_customers_ # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:186) -manage_user_cac_add_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:210) -manage_user_cac_delete_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:246) -update_user_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:285) -deactivate_user_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:316) -activate_user_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:338) -renew_user_api_key # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:357) -view_delete_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:377) -exists_user_restricted # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:408) -lookup_name_restricted # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:425) -manage_users_list_restricted # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/manage/manage_users.py:444) -get_overview_filter # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/overview_routes.py:30) -user_renew_api # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:48) -user_has_permission # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:60) -update_user_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:83) -profile_set_theme # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:115) -profile_set_deletion_prompt # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:133) -_.has_deletion_confirmation # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:145) -profile_set_minisidebar # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:151) -_.has_mini_sidebar # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:163) -profile_refresh_permissions_and_ac # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:169) -profile_whoami # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:183) -user_is_admin # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/profile_routes.py:202) -download_case_activity # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/reports_route.py:41) -generate_report # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/reports_route.py:68) -search_file_post # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/rest/search_routes.py:30) _.note_creationdate # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/business/notes.py:46) _.validate_config # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:52) _.config_key_vault # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:59) diff --git a/pyproject.toml b/pyproject.toml index 3d0b7edcf..0393f1621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] paths = ["source/app", ".vulture.ignore"] -ignore_decorators = ["@*.post", "@*.get", "@*.put", "@*.delete", "@pre_load"] +ignore_decorators = ["@*.route", "@*.post", "@*.get", "@*.put", "@*.delete", "@pre_load"] [tool.importlinter] root_package = "app" diff --git a/source/app/blueprints/rest/activities_routes.py b/source/app/blueprints/rest/activities_routes.py index 1b8fc4c5a..b9ed70a71 100644 --- a/source/app/blueprints/rest/activities_routes.py +++ b/source/app/blueprints/rest/activities_routes.py @@ -27,7 +27,7 @@ activities_rest_blueprint = Blueprint('activities_rest', __name__) -@activities_rest_blueprint.route('/activities/list', methods=['GET']) +@activities_rest_blueprint.get('/activities/list') @ac_api_requires(Permissions.activities_read, Permissions.all_activities_read) def list_activities(): # Get User activities from database From f25c4424384148ff4d06db3c1217ecc9dee4d3bd Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:01:10 +0100 Subject: [PATCH 091/286] Improved configuration of vulture somewhat --- .vulture.ignore | 383 +++--------------------------------------------- pyproject.toml | 2 +- 2 files changed, 18 insertions(+), 367 deletions(-) diff --git a/.vulture.ignore b/.vulture.ignore index b9c5b697b..431a2c09f 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -1,239 +1,23 @@ EvidenceStorage # Do not remove: this is used by package iris-module-interface (see https://github.com/dfir-iris/iris-module-interface, iris_interface/IrisModuleInterface.py) -ReverseProxied # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:44) -_.autoescape # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:99) -dropzone # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:115) -shutdown_session # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:148) +######### Alembic variables revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:14) down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:15) branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:16) depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:17) downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:28) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/0db700644a4f_add_tags_to_assets.py:63) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/10a7616f3cc7_add_module_types.py:43) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11aa5b725b8e_add_user_mfa_secrets.py:40) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:17) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:18) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:19) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:20) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/11e066542a88_add_deletion_confirmation_option.py:31) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/1df4adfa3160_add_customer_extended_fields.py:50) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:18) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:19) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:20) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:21) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20447ecb2245_objects_uuid_field.py:182) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/20a9c0fd56e1_add_parent_event_in_events.py:37) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:18) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:19) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:20) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:21) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2604f6962838_add_case_state.py:55) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2a4a8330b908_adding_ioc_and_assets_enrichments.py:31) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/2df770a4989c_add_objects_attributes.py:42) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3204e9116233_add_event_flag.py:30) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:13) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:14) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:15) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:16) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/35c095f8be2b_case_templates_note_groups_to_.py:27) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:16) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:17) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:18) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:19) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3715d4fac4de_add_link_between_case_and_ioc.py:347) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:12) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:13) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:14) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:15) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/3a4d4f15bd69_rename_opened_to_open.py:24) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py:50) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:12) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:13) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:14) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:15) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/50f28953a485_add_uniqueness_to_tags_table.py:73) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:14) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:15) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:16) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:17) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/65168cb6cc90_reviewer_in_case.py:38) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/6a3b3b627d45_add_ioc_type.py:79) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/79a9a54e8f9d_add_server_settings_updates_info.py:57) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:12) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:13) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:14) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:15) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/7cc588444b79_migrate_user_int_to_big_int.py:27) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/874ba5e5da44_add_task_log_api_field.py:43) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:16) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:17) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:18) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:19) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/875edc4adb40_migrate_task_assignee.py:46) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/92ecbf0f6d10_add_user_external_id.py:41) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:14) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:15) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:16) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:17) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/9e4947a207a6_add_modification_history_to_case_objects.py:47) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:16) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:17) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:18) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:19) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a3eb60654ec4_add_cases_status.py:33) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/a929ef458490_add_activity_no_display_field.py:41) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py:93) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/afcff5ebcf7c_added_force_confirmation_before_delete_.py:37) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/b664ca1203a4_add_dark_mode.py:41) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c29ef01617f5_migrate_notes_directories.py:56) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:16) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:17) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:18) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:19) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c773a35c280f_update_tasks_status.py:118) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:12) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:13) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:14) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:15) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c832bd69f827_evidence_file_size_int_to_bigint.py:26) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:16) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:17) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:18) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:19) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/c959c298ca00_add_classification_history_closing_note_.py:125) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ca93d4b54571_add_created_by_in_events.py:29) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py:58) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d207b4d13385_add_severity_to_cases.py:51) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:14) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:15) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:16) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:17) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py:54) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:14) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:15) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:16) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:17) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/d6c49c5435c2_add_evidence_type_to_evidences.py:67) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:14) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:15) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:16) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:17) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/db93d5c4c0aa_add_service_account_and_minibar_in_users.py:32) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e0df2de997cc_add_namespace_to_tags.py:67) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:14) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:15) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:16) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:17) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/e33dd011fb87_resolution_status_in_alerts.py:29) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:14) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:15) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:16) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:17) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/f727badcc4e1_add_alert_in_comments.py:32) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:22) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:23) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:24) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:25) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/fcc375ed37d1_access_control_migration.py:239) -revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:15) -down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:16) -branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:17) -depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:18) -downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/ff917e2ab02e_asset_type_custom_icons.py:55) +######### marshables +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:183) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:185) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:186) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:271) +######### +ReverseProxied # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:44) +_.autoescape # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:99) +dropzone # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:115) _.token_user # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/access_controls.py:361) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:51) interfaces # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:53) _.resolve_iocs # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:60) _.resolve_case # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:71) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:78) total_count # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:81) _.resolve_total_count # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:84) Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:91) @@ -251,8 +35,6 @@ ioc_delete # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/b case_create # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:96) case_delete # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:97) case_update # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:98) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:42) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:47) total_count # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:50) _.resolve_total_count # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:52) Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:59) @@ -660,194 +442,64 @@ worker # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/model retries # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:836) queue # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:837) _._register_modules_pipelines # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/post_init.py:1459) -POSTGRES_BIGINT_MAX # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:106) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:183) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:185) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:186) + _.verify_directory_name # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:204) user_roles_str # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:257) user_isadmin # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:262) user_primary_organisation_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:264) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:267) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:269) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:270) -exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:271) _.verify_username # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:274) _.verify_email # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:314) _.verify_password # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:354) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:418) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:420) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:421) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:429) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:431) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:432) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:446) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:448) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:449) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:482) CaseAddNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:509) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:552) CaseGroupNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:579) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:587) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:589) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:607) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:609) asset_enrichment # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:687) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:693) sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:695) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:696) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:697) -_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:718) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:763) created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:800) _.validate_string_or_list_of_dict # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:840) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:889) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:891) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:923) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:925) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:926) _.get_link # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:944) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:951) sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:953) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:954) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:955) -_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:958) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1004) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1043) sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1045) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1046) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1047) -_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1050) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1096) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1132) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1134) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1135) -exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1136) event_comments_map # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1157) children # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1159) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1161) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1163) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1164) -_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1195) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1268) _.get_event_category_id # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1294) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1308) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1310) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1311) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1326) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1328) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1329) _.file_date_added # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1369) _.added_by_user_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1370) http_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1476) https_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1477) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1480) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1482) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1501) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1503) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1518) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1520) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1559) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1561) case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1602) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1611) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1613) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1614) -exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1615) _.classification_filter # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1618) _.verify_customer # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1637) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1666) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1711) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1713) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1728) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1730) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1731) -exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1732) -_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1735) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1783) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1785) -exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1786) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1824) log_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1864) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1866) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1867) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1879) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1881) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1893) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1895) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1913) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1915) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1916) -_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1919) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1953) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1997) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1999) include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2000) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2001) _.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2004) group_permissions_list # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2050) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2053) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2055) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2056) _.parse_permissions # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2091) AuthorizationOrganisationSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2119) org_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2127) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2129) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2131) has_deletion_confirmation # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2179) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2181) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2183) -exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2184) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2231) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2233) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2245) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2247) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2259) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2261) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2273) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2275) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2295) include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2297) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2298) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2299) -_.verify_data # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2302) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2323) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2325) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2326) include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2327) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2332) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2334) ModuleHooksSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2338) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2345) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2347) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2348) include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2349) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2354) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2356) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2357) include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2358) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2363) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2365) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2366) include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2367) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2374) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2376) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2377) include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2378) case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2399) reviewer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2410) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2416) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2418) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2419) -exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2420) _.classification_filter # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2423) _.verify_customer # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2442) reviewer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2481) _.get_status_name # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2485) _.get_protagonists # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2488) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2506) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2508) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2509) include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2510) user_roles_str # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2522) user_api_key # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2528) @@ -856,11 +508,10 @@ user_groups # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/ user_customers # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2535) user_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2537) user_primary_organisation_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2538) -Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2540) -load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2542) -include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2543) -exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2544) + +######### Arguments of fields.Method _.get_user_primary_organisation # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2548) -_.verify_username # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2554) -_.verify_email # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2594) -_.verify_password # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2634) +######### Probably dead methods which need to be removed? +######### Vulture false positive? Investigate and open an issue in vulture? +POSTGRES_BIGINT_MAX # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:106) + diff --git a/pyproject.toml b/pyproject.toml index 0393f1621..fbd07ecad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] paths = ["source/app", ".vulture.ignore"] -ignore_decorators = ["@*.route", "@*.post", "@*.get", "@*.put", "@*.delete", "@pre_load"] +ignore_decorators = ["@*.route", "@app.*", "@*.post", "@*.get", "@*.put", "@*.delete", "@pre_load"] [tool.importlinter] root_package = "app" From de3b44e23a46e797d497ebae12b33b7693b856bd Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:12:41 +0100 Subject: [PATCH 092/286] Organized vulture ignore file a bit more --- .vulture.ignore | 72 +++++++++++++++---------------------------------- pyproject.toml | 2 +- 2 files changed, 23 insertions(+), 51 deletions(-) diff --git a/.vulture.ignore b/.vulture.ignore index 431a2c09f..251f93b89 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -10,6 +10,8 @@ Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/mar load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:185) include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:186) exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:271) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2000) +sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:695) ######### ReverseProxied # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:44) _.autoescape # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:99) @@ -364,6 +366,7 @@ notify_server_has_updated # unused function (/home/ubuntu/code/dfir-iris/iris-w inner_init_server_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:78) updates_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:79) get_release_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:117) + alert_uuid # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:50) alert_source_link # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:56) alert_context # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:60) @@ -400,8 +403,6 @@ created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/sour contact_uuid # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:250) object_updated_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:294) updated_by # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:300) -CaseGraphAssets # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:321) -CaseGraphLinks # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:333) source_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:338) dest_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:339) created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:367) @@ -410,12 +411,10 @@ file_date_added # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/ added_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:416) data_parent # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:422) ioc_asset_link_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:439) -CasesAssetsExt # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:454) asset_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:460) note_creationdate # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:474) group_user # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:521) link_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:533) -CaseKanban # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:543) kanban_data # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:547) acquisition_date # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:559) chain_of_custody # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:569) @@ -434,7 +433,6 @@ added_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/ retry_on_fail # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:749) max_retry # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:750) wait_till_return # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:752) -IrisReport # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:758) filter_name # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:806) filter_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:807) filter_data # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:808) @@ -443,64 +441,22 @@ retries # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/mode queue # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:837) _._register_modules_pipelines # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/post_init.py:1459) -_.verify_directory_name # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:204) user_roles_str # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:257) user_isadmin # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:262) user_primary_organisation_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:264) -_.verify_username # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:274) -_.verify_email # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:314) -_.verify_password # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:354) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:482) -CaseAddNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:509) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:552) -CaseGroupNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:579) asset_enrichment # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:687) -sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:695) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:763) created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:800) -_.validate_string_or_list_of_dict # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:840) -_.get_link # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:944) -sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:953) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1004) -sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1045) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1096) event_comments_map # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1157) children # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1159) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1268) -_.get_event_category_id # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1294) -_.file_date_added # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1369) -_.added_by_user_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1370) http_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1476) https_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1477) case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1602) -_.classification_filter # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1618) -_.verify_customer # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1637) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1666) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1824) log_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1864) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1953) -include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2000) -_.custom_attributes_merge # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2004) group_permissions_list # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2050) -_.parse_permissions # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2091) -AuthorizationOrganisationSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2119) org_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2127) has_deletion_confirmation # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2179) -include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2297) -include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2327) -ModuleHooksSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2338) -include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2349) -include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2358) -include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2367) -include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2378) case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2399) reviewer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2410) -_.classification_filter # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2423) -_.verify_customer # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2442) -reviewer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2481) -_.get_status_name # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2485) -_.get_protagonists # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2488) -include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2510) user_roles_str # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2522) user_api_key # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2528) user_isadmin # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2530) @@ -509,9 +465,25 @@ user_customers # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/a user_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2537) user_primary_organisation_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2538) -######### Arguments of fields.Method +######### Arguments of fields.Method/ma.Method _.get_user_primary_organisation # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2548) -######### Probably dead methods which need to be removed? -######### Vulture false positive? Investigate and open an issue in vulture? +_.get_link # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:944) +_.get_event_category_id # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1294) +_.get_status_name # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2485) +_.get_protagonists # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2488) + +######### Vulture false positive? Investigate and open issues in vulture? POSTGRES_BIGINT_MAX # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:106) +_.validate_string_or_list_of_dict # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:840) + +######### Dead code which needs to be removed? +CaseGraphAssets # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:321) +CaseGraphLinks # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:333) +CasesAssetsExt # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:454) +CaseKanban # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:543) +IrisReport # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:758) +CaseAddNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:509) +CaseGroupNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:579) +AuthorizationOrganisationSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2119) +ModuleHooksSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2338) diff --git a/pyproject.toml b/pyproject.toml index fbd07ecad..debff879e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] paths = ["source/app", ".vulture.ignore"] -ignore_decorators = ["@*.route", "@app.*", "@*.post", "@*.get", "@*.put", "@*.delete", "@pre_load"] +ignore_decorators = ["@*.route", "@app.*", "@*.post", "@*.get", "@*.put", "@*.delete", "@pre_load", "@post_load"] [tool.importlinter] root_package = "app" From c4a488115416cbe1f3f8053f06a7f7166d515c1f Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:16:05 +0100 Subject: [PATCH 093/286] Simplified vulture ignore file somewhat --- .vulture.ignore | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/.vulture.ignore b/.vulture.ignore index 251f93b89..cf594ad7c 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -54,15 +54,11 @@ case_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/ add_asset_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:64) asset_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:83) _.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:101) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:104) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:105) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:109) case_comment_asset_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:116) case_graph # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_graphs_routes.py:34) case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:46) case_add_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:61) case_view_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:76) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:90) case_comment_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:102) case_notes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_notes_routes.py:37) case_comment_note_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_notes_routes.py:55) @@ -77,24 +73,19 @@ case_md_helper # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/a case_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:44) case_add_task_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:56) case_task_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:72) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:88) case_comment_task_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:97) case_timeline # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:49) case_getgraph_page # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:61) case_comment_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:70) event_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:84) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:95) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:98) case_filter_help_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:120) case_add_event_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:129) add_gtask_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dashboard/dashboard_routes.py:77) edit_gtask_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dashboard/dashboard_routes.py:93) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dashboard/dashboard_routes.py:106) datastore_add_file_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:38) datastore_add_multi_files_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:54) datastore_filter_help_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:70) datastore_update_file_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:79) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:96) datastore_info_file_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:103) demo_landing # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/demo_landing/demo_landing.py:35) dim_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dim_tasks/dim_tasks.py:38) @@ -107,22 +98,13 @@ manage_ac_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/ manage_ac_audit_user_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_access_control.py:47) manage_ac_audit_users_page # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_access_control.py:59) view_assets_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:38) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:49) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:50) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:51) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:52) add_assets_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:59) manage_attributes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_attributes_routes.py:36) attributes_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_attributes_routes.py:47) attributes_preview # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_attributes_routes.py:65) update_case_classification_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:37) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:60) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:61) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:62) add_case_classification_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:68) update_case_state_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:37) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:60) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:61) add_case_state_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:67) manage_case_templates # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:36) case_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:47) @@ -136,27 +118,15 @@ manage_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source view_customer_page # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:58) customer_add_contact_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:76) customer_edit_contact_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:88) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:100) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:101) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:102) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:103) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:105) view_customer_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:110) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:122) add_customers_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:130) update_evidence_type_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:37) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:60) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:61) add_evidence_type_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:67) manage_groups_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:41) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:54) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:55) manage_groups_add_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:60) manage_groups_members_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:73) manage_groups_cac_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:88) view_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:35) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:46) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:47) add_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:55) manage_modules_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:41) add_module_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:52) @@ -168,10 +138,6 @@ manage_report_templates # unused function (/home/ubuntu/code/dfir-iris/iris-web add_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_templates_routes.py:51) add_user_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:41) view_user_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:55) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:69) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:70) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:71) -_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:72) manage_user_group_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:80) manage_user_customers_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:95) manage_user_cac_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:112) From 97ee448234477bfcc8447086eb154033be02216b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:17:51 +0100 Subject: [PATCH 094/286] Deprecated PUT /manage/customers/update/ --- 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 9c8726ae0..6bf09c76a 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -226,6 +226,7 @@ def get_customer_case_stats(client_id): @manage_customers_rest_blueprint.route('/manage/customers/update/', methods=['POST']) +@endpoint_deprecated('PUT', '/api/v2/manage/customers/{identifier}') @ac_api_requires(Permissions.customers_write) @ac_api_requires_client_access() def view_customers(client_id): From 48c120987b9efafe3e7fbc1d88e0f1c820a72785 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:41:21 +0100 Subject: [PATCH 095/286] Removed one occurence of ElementNotFoundException (use ObjectNotFoundException instead) --- .../blueprints/rest/manage/manage_customers_routes.py | 9 +++++---- source/app/datamgmt/client/client_db.py | 11 ++--------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index 6bf09c76a..1936c203c 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -25,6 +25,8 @@ 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.business.customers import customers_get +from app.business.errors import ObjectNotFoundError 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 @@ -283,10 +285,9 @@ def add_customers(): @ac_api_requires_client_access() def delete_customers(client_id): try: - - delete_client(client_id) - - except ElementNotFoundException: + customer = customers_get(client_id) + delete_client(customer) + except ObjectNotFoundError: return response_error('Invalid Customer ID') except ElementInUseException: diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index c93fccfa6..c846eed0a 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -172,16 +172,9 @@ def update_client(schema, customer: Client, data): update_customer() -def delete_client(client_id: int) -> None: - client = Client.query.filter( - Client.client_id == client_id - ).first() - - if not client: - raise ElementNotFoundException('No Customer found with this uuid.') - +def delete_client(customer: Client) -> None: try: - db.session.delete(client) + db.session.delete(customer) db.session.commit() except Exception: raise ElementInUseException('A currently referenced customer cannot be deleted') From ee14b4ad17d866f0f82ba77954ba9b9e971abcb3 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:44:10 +0100 Subject: [PATCH 096/286] Removed unnecessary parameter --- .../blueprints/pages/manage/manage_customers_routes.py | 2 +- .../blueprints/rest/manage/manage_customers_routes.py | 10 +++++----- source/app/datamgmt/client/client_db.py | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/source/app/blueprints/pages/manage/manage_customers_routes.py b/source/app/blueprints/pages/manage/manage_customers_routes.py index e13e8716e..03464f052 100644 --- a/source/app/blueprints/pages/manage/manage_customers_routes.py +++ b/source/app/blueprints/pages/manage/manage_customers_routes.py @@ -92,7 +92,7 @@ def customer_edit_contact_modal(client_id, contact_id, caseid, url_redir): if url_redir: return redirect(url_for('manage_customers.manage_customers', cid=caseid)) - contact = get_client_contact(client_id, contact_id) + contact = get_client_contact(contact_id) if not contact: return response_error(f"Invalid Contact ID {contact_id}") diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index 1936c203c..15c681612 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -83,14 +83,14 @@ def view_customer(client_id): def customer_update_contact(client_id, contact_id): if not request.is_json: - return response_error("Invalid request") + return response_error('Invalid request') if not get_customer(client_id): - return response_error(f"Invalid Customer ID {client_id}") + return response_error(f'Invalid Customer ID {client_id}') try: data = request.json - contact = get_client_contact(client_id, contact_id) + contact = get_client_contact(contact_id) data['client_id'] = client_id contact_schema = ContactSchema() contact_schema.load(data, instance=contact) @@ -104,11 +104,11 @@ def customer_update_contact(client_id, contact_id): print(traceback.format_exc()) return response_error(f'An error occurred during contact update. {e}') - track_activity(f"Updated contact {contact.contact_name}", ctx_less=True) + track_activity(f'Updated contact {contact.contact_name}', ctx_less=True) # Return the customer contact_schema = ContactSchema() - return response_success("Added successfully", data=contact_schema.dump(contact)) + return response_success('Added successfully', data=contact_schema.dump(contact)) @manage_customers_rest_blueprint.route('/manage/customers//contacts/add', methods=['POST']) diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index c846eed0a..81dd6bbda 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -129,9 +129,8 @@ def get_client_contacts(client_id: int) -> List[Contact]: return contacts -def get_client_contact(client_id: int, contact_id: int) -> Contact: +def get_client_contact(contact_id: int) -> Contact: contact = Contact.query.filter( - Contact.client_id == client_id, Contact.id == contact_id ).first() From ed60317af3058d10319f01884f7784b171ca190d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:49:49 +0100 Subject: [PATCH 097/286] Removed exception ElementNotFoundException --- .../rest/manage/manage_customers_routes.py | 7 +++-- source/app/business/customers_contacts.py | 30 +++++++++++++++++++ source/app/datamgmt/client/client_db.py | 10 +------ .../datamgmt/exceptions/ElementExceptions.py | 4 --- 4 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 source/app/business/customers_contacts.py diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index 15c681612..7b981c1f2 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -26,6 +26,7 @@ from app.blueprints.access_controls import ac_api_requires from app.blueprints.iris_user import iris_current_user from app.business.customers import customers_get +from app.business.customers_contacts import customers_contacts_get from app.business.errors import ObjectNotFoundError from app.datamgmt.client.client_db import create_customer from app.datamgmt.client.client_db import create_contact @@ -40,7 +41,6 @@ from app.datamgmt.client.client_db import update_client from app.datamgmt.client.client_db import update_contact from app.datamgmt.exceptions.ElementExceptions import ElementInUseException -from app.datamgmt.exceptions.ElementExceptions import ElementNotFoundException from app.datamgmt.manage.manage_users_db import add_user_to_customer from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions @@ -306,10 +306,11 @@ def delete_customers(client_id): @ac_api_requires_client_access() def delete_contact_route(client_id, contact_id): try: + contact = customers_contacts_get(contact_id) - delete_contact(contact_id) + delete_contact(contact) - except ElementNotFoundException: + except ObjectNotFoundError: return response_error('Invalid contact ID') except ElementInUseException: diff --git a/source/app/business/customers_contacts.py b/source/app/business/customers_contacts.py new file mode 100644 index 000000000..80a1e0717 --- /dev/null +++ b/source/app/business/customers_contacts.py @@ -0,0 +1,30 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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.models.models import Contact +from app.business.errors import ObjectNotFoundError +from app.datamgmt.client.client_db import get_client_contact + + +def customers_contacts_get(identifier) -> Contact: + contact = get_client_contact(identifier) + if not contact: + raise ObjectNotFoundError() + return contact + + diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index 81dd6bbda..c06f40f56 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -24,7 +24,6 @@ from app import db from app.datamgmt.exceptions.ElementExceptions import ElementInUseException -from app.datamgmt.exceptions.ElementExceptions import ElementNotFoundException from app.models.cases import Cases from app.models.models import Client from app.models.models import Contact @@ -137,14 +136,7 @@ def get_client_contact(contact_id: int) -> Contact: return contact -def delete_contact(contact_id: int) -> None: - contact = Contact.query.filter( - Contact.id == contact_id - ).first() - - if not contact: - raise ElementNotFoundException('No Contact found with this uuid.') - +def delete_contact(contact: Contact): try: db.session.delete(contact) diff --git a/source/app/datamgmt/exceptions/ElementExceptions.py b/source/app/datamgmt/exceptions/ElementExceptions.py index 52614d4dc..d130da12d 100644 --- a/source/app/datamgmt/exceptions/ElementExceptions.py +++ b/source/app/datamgmt/exceptions/ElementExceptions.py @@ -16,9 +16,5 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -class ElementNotFoundException(Exception): - pass - - class ElementInUseException(Exception): pass From c5c4dd14b780c7e1defcd242b2da7949c30cc87f Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 14:43:29 +0100 Subject: [PATCH 098/286] Fixed some deepsource warnings --- source/app/blueprints/rest/manage/manage_customers_routes.py | 4 ++-- source/app/blueprints/rest/v2/alerts_filters.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index 7b981c1f2..73e7dfe49 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -241,7 +241,7 @@ def view_customers(client_id): if not customer: raise response_error('Invalid Customer ID') - client = update_client(client_schema, customer, request.json) + update_client(client_schema, customer, request.json) except ValidationError as e: return response_error("", data=e.messages) @@ -250,7 +250,7 @@ def view_customers(client_id): print(traceback.format_exc()) return response_error(f'An error occurred during Customer update. {e}') - return response_success("Customer updated", client_schema.dump(client)) + return response_success("Customer updated", client_schema.dump(customer)) @manage_customers_rest_blueprint.route('/manage/customers/add', methods=['POST']) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 5fcf2bad1..57f40a1a1 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -90,7 +90,8 @@ def put(self, identifier): except BusinessProcessingError as e: return response_api_error(e.get_message(), data=e.get_data()) - def delete(self, identifier): + @staticmethod + def delete(identifier): try: saved_filter = alert_filter_get(identifier) alert_filter_delete(saved_filter) From 2105ca1cbb0ccdcf946ed6639391f3e5f9665fd8 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:39:34 +0100 Subject: [PATCH 099/286] Added E20* ruff rules, and fixed warnings --- pyproject.toml | 2 +- .../rest/v2/manage_routes/groups.py | 2 +- .../blueprints/rest/v2/manage_routes/users.py | 2 +- .../app/iris_engine/access_control/utils.py | 2 +- source/app/iris_engine/updater/updater.py | 2 +- tests/tests_rest_alerts_filters.py | 37 +++++++++---------- tests/tests_rest_assets.py | 2 +- tests/tests_rest_cases.py | 12 +++--- tests/tests_rest_groups.py | 2 +- tests/tests_rest_tasks.py | 4 +- 10 files changed, 33 insertions(+), 34 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index debff879e..765505e7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] +select = ["ARG003", "ARG005", "B00", "E101", "E20", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] diff --git a/source/app/blueprints/rest/v2/manage_routes/groups.py b/source/app/blueprints/rest/v2/manage_routes/groups.py index 12df18c42..c4040f4bf 100644 --- a/source/app/blueprints/rest/v2/manage_routes/groups.py +++ b/source/app/blueprints/rest/v2/manage_routes/groups.py @@ -81,7 +81,7 @@ def update(self, identifier): return response_api_not_found() def delete(self, identifier): - try : + try: group = groups_get(identifier) groups_delete(iris_current_user, group) return response_api_deleted() diff --git a/source/app/blueprints/rest/v2/manage_routes/users.py b/source/app/blueprints/rest/v2/manage_routes/users.py index ac267997a..dcd5973cc 100644 --- a/source/app/blueprints/rest/v2/manage_routes/users.py +++ b/source/app/blueprints/rest/v2/manage_routes/users.py @@ -79,7 +79,7 @@ def update(self, identifier): return response_api_not_found() def delete(self, identifier): - try : + try: user = users_get(identifier) users_delete(user) return response_api_deleted() diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index 6907b0e18..6e7f132f4 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -369,7 +369,7 @@ def ac_set_new_case_access(user, case_id, customer_id): # Add customer permissions for all users belonging to the customer if customer_id: users_client = get_user_access_levels_by_customer(customer_id) - users_map = { u.user_id: u.access_level for u in users_client } + users_map = {u.user_id: u.access_level for u in users_client} ac_add_user_effective_access_from_map(users_map, case_id) diff --git a/source/app/iris_engine/updater/updater.py b/source/app/iris_engine/updater/updater.py index d3b068dfd..a331d92f9 100644 --- a/source/app/iris_engine/updater/updater.py +++ b/source/app/iris_engine/updater/updater.py @@ -324,7 +324,7 @@ def call_ext_updater(update_archive, scope, need_reboot): '1' if need_reboot else '0', # Do we need to restart the app '&']) - except Exception as e : + except Exception as e: log.error(str(e)) return False diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 9ed244f1b..8fb0c1f31 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -36,7 +36,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_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -76,7 +76,7 @@ def test_create_alert_filter_should_return_filter_type(self): 'filter_type': filter_type, 'filter_name': 'filter name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -106,7 +106,7 @@ def test_create_alert_filter_should_return_filter_name(self): 'filter_type': 'alerts', 'filter_name': filter_name, 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -136,7 +136,7 @@ def test_create_alert_filter_should_return_in_filter_data_alert_title(self): 'filter_type': 'alerts', 'filter_name': 'filter_name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': alert_title, 'alert_description': '', 'alert_source': '', @@ -165,7 +165,7 @@ def test_get_alert_filter_should_return_200(self): 'filter_type': 'alerts', 'filter_name': 'filter name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -198,7 +198,7 @@ def test_get_alert_filter_should_return_filter_name(self): 'filter_type': 'alerts', 'filter_name': filter_name, 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -235,7 +235,7 @@ def test_get_alert_filter_should_return_404_when_user_has_not_created_alert_filt 'filter_type': 'alerts', 'filter_name': 'filter_name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -267,7 +267,7 @@ def test_update_alert_filter_should_return_200(self): 'filter_type': 'alerts', 'filter_name': 'filter name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -303,7 +303,7 @@ def test_update_alert_filter_should_return_filter_name(self): 'filter_type': 'alerts', 'filter_name': 'old name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -339,7 +339,7 @@ def test_update_alert_filter_should_return_filter_description(self): 'filter_type': 'alerts', 'filter_name': 'old name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -375,7 +375,7 @@ def test_update_alert_filter_should_return_filter_type(self): 'filter_type': 'alerts', 'filter_name': 'old name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -411,7 +411,7 @@ def test_update_alert_filter_should_return_filter_data_alert_title(self): 'filter_type': 'alerts', 'filter_name': 'old name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -435,7 +435,7 @@ def test_update_alert_filter_should_return_filter_data_alert_title(self): response = self._subject.create('/api/v2/alerts-filters', body).json() identifier = response['filter_id'] body = { - 'filter_data': { 'alert_title' : alert_title }, + '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']) @@ -446,7 +446,7 @@ def test_update_alert_filter_should_return_404_when_alert_filter_is_not_found(se 'filter_type': 'alerts', 'filter_name': 'old name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -463,13 +463,12 @@ def test_update_alert_filter_should_return_404_when_alert_filter_is_not_found(se 'source_reference': '', 'case_id': '', 'custom_conditions': '', - } } response = self._subject.create('/api/v2/alerts-filters', body).json() body = { - 'filter_data': { 'alert_title' : 'alert_title' }, + '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) @@ -480,7 +479,7 @@ def test_delete_alert_filter_should_return_204(self): 'filter_type': 'alerts', 'filter_name': 'old name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -516,7 +515,7 @@ def test_get_alert_filter_should_return_404_after_delete_alert_filter(self): 'filter_type': 'alerts', 'filter_name': 'old name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', @@ -549,7 +548,7 @@ def test_update_alert_filter_should_return_400(self): 'filter_type': 'alerts', 'filter_name': 'filter name', 'filter_description': 'filter description', - 'filter_data' : { + 'filter_data': { 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', diff --git a/tests/tests_rest_assets.py b/tests/tests_rest_assets.py index ff736e003..7c5a1c667 100644 --- a/tests/tests_rest_assets.py +++ b/tests/tests_rest_assets.py @@ -270,7 +270,7 @@ def test_get_assets_should_accept_order_by_query_parameter(self): self._subject.create(f'/api/v2/cases/{case_identifier}/assets', body).json() body = {'asset_type_id': 1, 'asset_name': 'asset1'} self._subject.create(f'/api/v2/cases/{case_identifier}/assets', body).json() - response = self._subject.get(f'/api/v2/cases/{case_identifier}/assets', { 'order_by': 'asset_name' }).json() + response = self._subject.get(f'/api/v2/cases/{case_identifier}/assets', {'order_by': 'asset_name'}).json() self.assertEqual('asset1', response['data'][0]['asset_name']) def test_get_assets_should_return_200_when_user_has_read_only_access_to_case(self): diff --git a/tests/tests_rest_cases.py b/tests/tests_rest_cases.py index 3d3781ac7..77095493d 100644 --- a/tests/tests_rest_cases.py +++ b/tests/tests_rest_cases.py @@ -196,33 +196,33 @@ def test_create_case_should_return_field_case_customer(self): def test_update_case_should_return_200(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'case_name': 'new name' }) + response = self._subject.update(f'/api/v2/cases/{identifier}', {'case_name': 'new name'}) self.assertEqual(200, response.status_code) def test_update_case_should_allow_to_update_severity(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'severity_id': 5 }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'severity_id': 5}).json() self.assertEqual(5, response['severity_id']) def test_update_case_should_allow_to_update_classification(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'classification_id': 3 }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'classification_id': 3}).json() self.assertEqual(3, response['classification_id']) def test_update_case_should_allow_to_update_owner(self): user = self._subject.create_dummy_user() identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'owner_id': user.get_identifier() }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'owner_id': user.get_identifier()}).json() self.assertEqual(user.get_identifier(), response['owner']['id']) def test_update_case_should_allow_to_update_state(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'state_id': 2 }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'state_id': 2}).json() self.assertEqual(2, response['state']['state_id']) def test_update_case_should_allow_to_update_status(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'status_id': 2 }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'status_id': 2}).json() self.assertEqual(2, response['status_id']) def test_update_case_should_allow_to_update_customer(self): diff --git a/tests/tests_rest_groups.py b/tests/tests_rest_groups.py index 048970893..ce240bcbd 100644 --- a/tests/tests_rest_groups.py +++ b/tests/tests_rest_groups.py @@ -118,7 +118,7 @@ def test_update_group_should_return_field_group_auto_follow(self): body = {'group_name': 'name', 'group_description': 'description'} response = self._subject.create('/api/v2/manage/groups', body).json() identifier = response['group_id'] - body = {'group_name': 'new_name', 'group_description': 'new_description', 'group_permissions': 1, 'group_auto_follow' : True} + body = {'group_name': 'new_name', 'group_description': 'new_description', 'group_permissions': 1, 'group_auto_follow': True} response = self._subject.update(f'/api/v2/manage/groups/{identifier}', body).json() self.assertEqual(True, response['group_auto_follow']) diff --git a/tests/tests_rest_tasks.py b/tests/tests_rest_tasks.py index 3417ec82f..61e301b16 100644 --- a/tests/tests_rest_tasks.py +++ b/tests/tests_rest_tasks.py @@ -177,7 +177,7 @@ def test_get_tasks_should_honour_per_page_pagination_parameter(self): self._subject.create(f'/api/v2/cases/{case_identifier}/tasks', body).json() body = {'task_assignees_id': [], 'task_status_id': 1, 'task_title': 'task3'} self._subject.create(f'/api/v2/cases/{case_identifier}/tasks', body).json() - response = self._subject.get(f'/api/v2/cases/{case_identifier}/tasks', { 'per_page': 2 }).json() + response = self._subject.get(f'/api/v2/cases/{case_identifier}/tasks', {'per_page': 2}).json() self.assertEqual(2, len(response['data'])) def test_get_tasks_should_return_current_page(self): @@ -188,7 +188,7 @@ def test_get_tasks_should_return_current_page(self): self._subject.create(f'/api/v2/cases/{case_identifier}/tasks', body).json() body = {'task_assignees_id': [], 'task_status_id': 1, 'task_title': 'task3'} self._subject.create(f'/api/v2/cases/{case_identifier}/tasks', body).json() - response = self._subject.get(f'/api/v2/cases/{case_identifier}/tasks', { 'page': 2, 'per_page': 2 }).json() + response = self._subject.get(f'/api/v2/cases/{case_identifier}/tasks', {'page': 2, 'per_page': 2}).json() self.assertEqual(2, response['current_page']) def test_get_tasks_should_return_correct_task_uuid(self): From e25ba9849024e0861b4ea839119facf5f0cf2cc4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:42:23 +0100 Subject: [PATCH 100/286] Added ruff rule W391 --- pyproject.toml | 2 +- source/app/alembic/versions/10a7616f3cc7_add_module_types.py | 1 - .../app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py | 1 - .../alembic/versions/cd519d2d24df_password_policy_edition.py | 1 - source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py | 1 - source/app/blueprints/graphql/sliced_result.py | 1 - source/app/blueprints/rest/parsing.py | 1 - source/app/business/customers_contacts.py | 2 -- source/app/datamgmt/dashboard/dashboard_db.py | 2 -- source/app/datamgmt/datastore/datastore_db.py | 1 - source/app/datamgmt/manage/manage_tags_db.py | 1 - source/app/iris_engine/updater/__init__.py | 1 - source/app/iris_engine/utils/__init__.py | 1 - source/run.py | 1 - tests/tests_rest_notes.py | 1 - 15 files changed, 1 insertion(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 765505e7e..28c140756 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E20", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29"] +select = ["ARG003", "ARG005", "B00", "E101", "E20", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] diff --git a/source/app/alembic/versions/10a7616f3cc7_add_module_types.py b/source/app/alembic/versions/10a7616f3cc7_add_module_types.py index f274ebabf..acad25b8a 100644 --- a/source/app/alembic/versions/10a7616f3cc7_add_module_types.py +++ b/source/app/alembic/versions/10a7616f3cc7_add_module_types.py @@ -42,4 +42,3 @@ def upgrade(): def downgrade(): pass - diff --git a/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py b/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py index a39f7adf2..837bf8cc4 100644 --- a/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py +++ b/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py @@ -93,4 +93,3 @@ def upgrade(): def downgrade(): op.drop_column('ioc_type', 'type_validation_regex') op.drop_column('ioc_type', 'type_validation_expect') - diff --git a/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py b/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py index 69907fff9..d1a0a566f 100644 --- a/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py +++ b/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py @@ -57,4 +57,3 @@ def upgrade(): def downgrade(): pass - diff --git a/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py b/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py index 2da92b175..a2010b580 100644 --- a/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py +++ b/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py @@ -61,4 +61,3 @@ def downgrade(): # Drop AlertSimilarity table op.drop_table('alert_similarity') - diff --git a/source/app/blueprints/graphql/sliced_result.py b/source/app/blueprints/graphql/sliced_result.py index dc5e8ab78..bdd3933e0 100644 --- a/source/app/blueprints/graphql/sliced_result.py +++ b/source/app/blueprints/graphql/sliced_result.py @@ -29,4 +29,3 @@ def __getitem__(self, index: slice) -> any: def __len__(self) -> int: return self._total - diff --git a/source/app/blueprints/rest/parsing.py b/source/app/blueprints/rest/parsing.py index 65675d17f..2f8c3e8f6 100644 --- a/source/app/blueprints/rest/parsing.py +++ b/source/app/blueprints/rest/parsing.py @@ -51,4 +51,3 @@ def parse_pagination_parameters(request: Request, default_order_by=None, default direction = arguments.get('sort_dir', default_direction, type=str) return PaginationParameters(page, per_page, order_by, direction) - diff --git a/source/app/business/customers_contacts.py b/source/app/business/customers_contacts.py index 80a1e0717..1583cb0e9 100644 --- a/source/app/business/customers_contacts.py +++ b/source/app/business/customers_contacts.py @@ -26,5 +26,3 @@ def customers_contacts_get(identifier) -> Contact: if not contact: raise ObjectNotFoundError() return contact - - diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index 8f916ccf8..9ae0e2fcd 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -181,5 +181,3 @@ def list_user_cases(user_identifier, show_all=False): Cases.owner_id == user_identifier, Cases.close_date == None ).all() - - diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 666810ea7..87e5d3b94 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -559,4 +559,3 @@ def datastore_filter_tree(filter_d, caseid): datastore_iter_tree(dpath_parent_id, path_node, droot_children) return path_tree, 'Success' - diff --git a/source/app/datamgmt/manage/manage_tags_db.py b/source/app/datamgmt/manage/manage_tags_db.py index a7befe2e6..26f77bde8 100644 --- a/source/app/datamgmt/manage/manage_tags_db.py +++ b/source/app/datamgmt/manage/manage_tags_db.py @@ -89,4 +89,3 @@ def add_db_tag(tag_title, tag_namespace=None): raise e return tag - diff --git a/source/app/iris_engine/updater/__init__.py b/source/app/iris_engine/updater/__init__.py index 139597f9c..8b1378917 100644 --- a/source/app/iris_engine/updater/__init__.py +++ b/source/app/iris_engine/updater/__init__.py @@ -1,2 +1 @@ - diff --git a/source/app/iris_engine/utils/__init__.py b/source/app/iris_engine/utils/__init__.py index 2e0379bb3..0edbd4a50 100644 --- a/source/app/iris_engine/utils/__init__.py +++ b/source/app/iris_engine/utils/__init__.py @@ -15,4 +15,3 @@ # 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. - diff --git a/source/run.py b/source/run.py index 32dbe6204..8a59458b6 100644 --- a/source/run.py +++ b/source/run.py @@ -28,4 +28,3 @@ if __name__ == "__main__": socket_io.run(app, host='127.0.0.1', port=8000, debug=True) - diff --git a/tests/tests_rest_notes.py b/tests/tests_rest_notes.py index adc011949..8b4f7c881 100644 --- a/tests/tests_rest_notes.py +++ b/tests/tests_rest_notes.py @@ -263,4 +263,3 @@ def test_socket_io_join_notes_overview_should_not_fail(self): socket_io_client.emit('join-notes-overview', f'case-{case_identifier}-notes') message = socket_io_client.receive() self.assertEqual('administrator', message['user']) - From 20b074c00606064819f07c7b8cb8a58bee54d176 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:45:27 +0100 Subject: [PATCH 101/286] Fixed ruff warnings --- scripts/gunicorn-cfg.py | 1 - tests_database_migration/test_harness/iris.py | 1 - upgrades/upgrade_to_2.0.0.py | 1 - 3 files changed, 3 deletions(-) diff --git a/scripts/gunicorn-cfg.py b/scripts/gunicorn-cfg.py index 9d0b61096..565826fa4 100644 --- a/scripts/gunicorn-cfg.py +++ b/scripts/gunicorn-cfg.py @@ -27,4 +27,3 @@ def worker_exit(server, worker): sys.exit(4) - diff --git a/tests_database_migration/test_harness/iris.py b/tests_database_migration/test_harness/iris.py index c87833603..41e688e1e 100644 --- a/tests_database_migration/test_harness/iris.py +++ b/tests_database_migration/test_harness/iris.py @@ -98,4 +98,3 @@ def clear_database(self): identifier = user['user_id'] self.get(f'/manage/users/deactivate/{identifier}') self.create(f'/manage/users/delete/{identifier}', {}) - diff --git a/upgrades/upgrade_to_2.0.0.py b/upgrades/upgrade_to_2.0.0.py index b49f86d47..3c272bd32 100644 --- a/upgrades/upgrade_to_2.0.0.py +++ b/upgrades/upgrade_to_2.0.0.py @@ -168,4 +168,3 @@ def check(silent=False): if args.check: iu.check() - From 6a55d4615e25bce911f57f5a21e6cf9844d1536b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 09:51:15 +0100 Subject: [PATCH 102/286] Started implmentation of DELETE /api/v2/manage/customers/{identifier} --- .../rest/v2/manage_routes/customers.py | 17 +++++++++++++++++ source/app/business/customers.py | 6 ++++++ tests/iris.py | 4 ++-- tests/tests_rest_customers.py | 13 +++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index eaeb9024c..a7c649b8d 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -24,6 +24,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.access_controls import ac_api_requires from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema @@ -31,6 +32,7 @@ from app.business.customers import customers_create_with_user from app.business.customers import customers_get from app.business.customers import customers_update +from app.business.customers import customers_delete from app.blueprints.iris_user import iris_current_user @@ -71,6 +73,15 @@ def update(self, identifier): except ObjectNotFoundError: return response_api_not_found() + def delete(self, identifier): + try: + customer = customers_get(identifier) + customers_delete(customer) + return response_api_deleted() + + except ObjectNotFoundError: + return response_api_not_found() + customers_blueprint = Blueprint('customers_rest_v2', __name__, url_prefix='/customers') @@ -93,3 +104,9 @@ def get_customer(identifier): @ac_api_requires(Permissions.customers_write) def put_customer(identifier): return customers.update(identifier) + + +@customers_blueprint.delete('/') +@ac_api_requires(Permissions.customers_write) +def delete_user(identifier): + return customers.delete(identifier) diff --git a/source/app/business/customers.py b/source/app/business/customers.py index fed142715..ebe9efc97 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -23,6 +23,7 @@ from app.datamgmt.client.client_db import get_customer from app.datamgmt.client.client_db import get_customer_by_name from app.datamgmt.client.client_db import update_customer +from app.datamgmt.client.client_db import delete_client from app.business.errors import ObjectNotFoundError @@ -62,3 +63,8 @@ def customers_exists_another_with_same_name(identifier, name: str) -> bool: def customers_update(): update_customer() + + +def customers_delete(customer: Client): + delete_client(customer) + track_activity(f'Deleted Customer with ID {customer.client_id}', ctx_less=True) diff --git a/tests/iris.py b/tests/iris.py index ee45b60e0..9a520184f 100644 --- a/tests/iris.py +++ b/tests/iris.py @@ -31,7 +31,7 @@ _ADMINISTRATOR_USER_LOGIN = 'administrator' ADMINISTRATOR_USER_IDENTIFIER = 1 _INITIAL_DEMO_CASE_IDENTIFIER = 1 -_IRIS_INITIAL_CUSTOMER_IDENTIFIER = 1 +IRIS_INITIAL_CUSTOMER_IDENTIFIER = 1 IRIS_PERMISSION_SERVER_ADMINISTRATOR = 0x2 IRIS_PERMISSION_ALERTS_READ = 0x4 @@ -139,7 +139,7 @@ def clear_database(self): identifier = user['user_id'] self.get(f'/manage/users/deactivate/{identifier}') self.delete(f'/api/v2/manage/users/{identifier}') - body = {'customers_membership': [_IRIS_INITIAL_CUSTOMER_IDENTIFIER]} + body = {'customers_membership': [IRIS_INITIAL_CUSTOMER_IDENTIFIER]} self.create(f'/manage/users/{ADMINISTRATOR_USER_IDENTIFIER}/customers/update', body) customers = self.get('/manage/customers/list').json() for customer in customers['data']: diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index 2a374d837..d3b6eabc1 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -20,6 +20,7 @@ from iris import Iris from iris import IRIS_PERMISSION_CUSTOMERS_WRITE from iris import ADMINISTRATOR_USER_IDENTIFIER +from iris import IRIS_INITIAL_CUSTOMER_IDENTIFIER _IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 @@ -128,3 +129,15 @@ def test_put_customer_should_return_200_when_updating_with_same_name(self): body = {'customer_name': 'customer'} response = self._subject.update(f'/api/v2/manage/customers/{identifier}', body) self.assertEqual(200, response.status_code) + + def test_delete_customer_should_return_204(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + # TODO currently, to remove a customer, no user should have any access to it. I am not sure this is the optimum behavior. + body = {'customers_membership': [IRIS_INITIAL_CUSTOMER_IDENTIFIER]} + self._subject.create(f'/manage/users/{ADMINISTRATOR_USER_IDENTIFIER}/customers/update', body) + + response = self._subject.delete(f'/api/v2/manage/customers/{identifier}') + self.assertEqual(204, response.status_code) From 2f01f7944d2b7c43e1f53ddcc3870e3ac563bd97 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:43:50 +0100 Subject: [PATCH 103/286] DELETE /api/v2/manage/customers/{identifier} should return 400 when customer is referenced in a case --- .../blueprints/rest/v2/manage_routes/customers.py | 4 +++- tests/iris.py | 4 ++-- tests/tests_rest_customers.py | 14 ++++++++++++++ 3 files changed, 19 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 a7c649b8d..b221246d3 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -26,6 +26,7 @@ from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import response_api_deleted from app.blueprints.access_controls import ac_api_requires +from app.datamgmt.exceptions.ElementExceptions import ElementInUseException from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema from app.business.errors import ObjectNotFoundError @@ -81,7 +82,8 @@ def delete(self, identifier): except ObjectNotFoundError: return response_api_not_found() - + except ElementInUseException as e: + return response_api_error('Cannot delete a referenced customer') customers_blueprint = Blueprint('customers_rest_v2', __name__, url_prefix='/customers') diff --git a/tests/iris.py b/tests/iris.py index 9a520184f..464aed85e 100644 --- a/tests/iris.py +++ b/tests/iris.py @@ -104,11 +104,11 @@ def create_dummy_customer(self) -> int: response = self.create('/manage/customers/add', {'customer_name': f'customer{uuid4()}'}).json() return response['data']['customer_id'] - def create_dummy_case(self): + def create_dummy_case(self, customer_identifier=IRIS_INITIAL_CUSTOMER_IDENTIFIER): body = { 'case_name': 'case name', 'case_description': 'description', - 'case_customer_id': 1, + 'case_customer_id': customer_identifier, 'case_soc_id': '' } response = self._api.post('/api/v2/cases', body).json() diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index d3b6eabc1..0a3945559 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -141,3 +141,17 @@ def test_delete_customer_should_return_204(self): response = self._subject.delete(f'/api/v2/manage/customers/{identifier}') self.assertEqual(204, response.status_code) + + def test_delete_customer_should_return_400_when_referenced_by_a_customer(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + self._subject.create_dummy_case(identifier) + + # TODO currently, to remove a customer, no user should have any access to it. I am not sure this is the optimum behavior. + body = {'customers_membership': [IRIS_INITIAL_CUSTOMER_IDENTIFIER]} + self._subject.create(f'/manage/users/{ADMINISTRATOR_USER_IDENTIFIER}/customers/update', body) + + response = self._subject.delete(f'/api/v2/manage/customers/{identifier}') + self.assertEqual(400, response.status_code) From 2c4925d2023b15ca835e2fcdbf28988916b52ac4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:44:39 +0100 Subject: [PATCH 104/286] Renamed test --- tests/tests_rest_customers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index 0a3945559..af283ddbc 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -142,7 +142,7 @@ def test_delete_customer_should_return_204(self): response = self._subject.delete(f'/api/v2/manage/customers/{identifier}') self.assertEqual(204, response.status_code) - def test_delete_customer_should_return_400_when_referenced_by_a_customer(self): + def test_delete_customer_should_return_400_when_referenced_in_a_case(self): body = {'customer_name': 'customer'} response = self._subject.create('/api/v2/manage/customers', body).json() identifier = response['customer_id'] From 5cbdaf18b8bc5c3f07a378b09f8f8a15a2ce8253 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:51:30 +0100 Subject: [PATCH 105/286] Moved business error definitions out of business into model --- source/app/blueprints/graphql/cases.py | 2 +- source/app/blueprints/pages/case/case_ioc_routes.py | 2 +- source/app/blueprints/rest/alerts_routes.py | 2 +- source/app/blueprints/rest/case/case_assets_routes.py | 2 +- source/app/blueprints/rest/case/case_evidences_routes.py | 2 +- source/app/blueprints/rest/case/case_ioc_routes.py | 4 ++-- source/app/blueprints/rest/case/case_notes_routes.py | 2 +- source/app/blueprints/rest/case/case_tasks_routes.py | 2 +- source/app/blueprints/rest/case/case_timeline_routes.py | 2 +- source/app/blueprints/rest/case_comments.py | 2 +- source/app/blueprints/rest/manage/manage_cases_routes.py | 2 +- .../app/blueprints/rest/manage/manage_customers_routes.py | 2 +- source/app/blueprints/rest/reports_route.py | 4 ++-- source/app/blueprints/rest/v2/alerts.py | 4 ++-- source/app/blueprints/rest/v2/alerts_filters.py | 4 ++-- source/app/blueprints/rest/v2/alerts_routes/comments.py | 2 +- source/app/blueprints/rest/v2/assets.py | 4 ++-- source/app/blueprints/rest/v2/assets_routes/comments.py | 2 +- source/app/blueprints/rest/v2/auth.py | 2 +- source/app/blueprints/rest/v2/case_routes/assets.py | 4 ++-- source/app/blueprints/rest/v2/case_routes/events.py | 4 ++-- source/app/blueprints/rest/v2/case_routes/evidences.py | 4 ++-- source/app/blueprints/rest/v2/case_routes/iocs.py | 4 ++-- source/app/blueprints/rest/v2/case_routes/notes.py | 4 ++-- .../app/blueprints/rest/v2/case_routes/notes_directories.py | 4 ++-- source/app/blueprints/rest/v2/case_routes/tasks.py | 4 ++-- source/app/blueprints/rest/v2/cases.py | 2 +- source/app/blueprints/rest/v2/events_routes/comments.py | 2 +- source/app/blueprints/rest/v2/evidences_routes/comments.py | 2 +- source/app/blueprints/rest/v2/iocs.py | 4 ++-- source/app/blueprints/rest/v2/iocs_routes/comments.py | 2 +- source/app/blueprints/rest/v2/manage_routes/customers.py | 5 +++-- source/app/blueprints/rest/v2/manage_routes/groups.py | 4 ++-- source/app/blueprints/rest/v2/manage_routes/users.py | 4 ++-- source/app/blueprints/rest/v2/notes_routes/comments.py | 2 +- source/app/blueprints/rest/v2/tags.py | 2 +- source/app/blueprints/rest/v2/tasks.py | 4 ++-- source/app/blueprints/rest/v2/tasks_routes/comments.py | 2 +- source/app/business/alerts.py | 2 +- source/app/business/alerts_filters.py | 2 +- source/app/business/assets.py | 4 ++-- source/app/business/cases.py | 4 ++-- source/app/business/comments.py | 4 ++-- source/app/business/customers.py | 2 +- source/app/business/customers_contacts.py | 2 +- source/app/business/events.py | 4 ++-- source/app/business/evidences.py | 4 ++-- source/app/business/groups.py | 4 ++-- source/app/business/iocs.py | 4 ++-- source/app/business/notes.py | 6 +++--- source/app/business/notes_directories.py | 2 +- source/app/business/reports/reporter.py | 2 +- source/app/business/reports/reports.py | 2 +- source/app/business/tasks.py | 4 ++-- source/app/business/users.py | 4 ++-- source/app/datamgmt/filtering.py | 2 +- source/app/{business => models}/errors.py | 0 source/app/post_init.py | 2 +- 58 files changed, 86 insertions(+), 85 deletions(-) rename source/app/{business => models}/errors.py (100%) diff --git a/source/app/blueprints/graphql/cases.py b/source/app/blueprints/graphql/cases.py index d565ef94c..a82015819 100644 --- a/source/app/blueprints/graphql/cases.py +++ b/source/app/blueprints/graphql/cases.py @@ -36,7 +36,7 @@ from app.business.cases import cases_delete from app.business.cases import cases_update from app.business.cases import cases_get_by_identifier -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.blueprints.graphql.permissions import permissions_check_current_user_has_some_permission from app.blueprints.graphql.permissions import permissions_check_current_user_has_some_case_access from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook diff --git a/source/app/blueprints/pages/case/case_ioc_routes.py b/source/app/blueprints/pages/case/case_ioc_routes.py index 88290be23..1c4013985 100644 --- a/source/app/blueprints/pages/case/case_ioc_routes.py +++ b/source/app/blueprints/pages/case/case_ioc_routes.py @@ -22,7 +22,7 @@ from flask import url_for from app.business.iocs import iocs_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.assets_type import get_assets_types from app.datamgmt.case.case_db import get_case from app.datamgmt.case.case_iocs_db import get_case_iocs_comments_count diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index f1f3b9e16..0f802205a 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -60,7 +60,7 @@ from app.blueprints.responses import response_error from app.util import add_obj_history_entry from app.blueprints.responses import response_success -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.alerts import alerts_create alerts_rest_blueprint = Blueprint('alerts_rest', __name__) diff --git a/source/app/blueprints/rest/case/case_assets_routes.py b/source/app/blueprints/rest/case/case_assets_routes.py index 97c8ea87c..e0200a73f 100644 --- a/source/app/blueprints/rest/case/case_assets_routes.py +++ b/source/app/blueprints/rest/case/case_assets_routes.py @@ -30,7 +30,7 @@ from app.business.assets import assets_get from app.business.assets import assets_update from app.blueprints.iris_user import iris_current_user -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.datamgmt.case.case_assets_db import get_raw_assets from app.datamgmt.case.case_assets_db import get_linked_iocs_finfo_from_asset from app.datamgmt.case.case_assets_db import add_comment_to_asset diff --git a/source/app/blueprints/rest/case/case_evidences_routes.py b/source/app/blueprints/rest/case/case_evidences_routes.py index 6932a3322..93fdb740b 100644 --- a/source/app/blueprints/rest/case/case_evidences_routes.py +++ b/source/app/blueprints/rest/case/case_evidences_routes.py @@ -45,7 +45,7 @@ from app.business.evidences import evidences_create from app.business.evidences import evidences_delete from app.business.evidences import evidences_update -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook diff --git a/source/app/blueprints/rest/case/case_ioc_routes.py b/source/app/blueprints/rest/case/case_ioc_routes.py index 10f0d82b5..feda2c938 100644 --- a/source/app/blueprints/rest/case/case_ioc_routes.py +++ b/source/app/blueprints/rest/case/case_ioc_routes.py @@ -33,8 +33,8 @@ from app.business.iocs import iocs_update from app.business.iocs import iocs_delete from app.business.iocs import iocs_get -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.case_iocs_db import add_comment_to_ioc from app.datamgmt.case.case_iocs_db import add_ioc from app.datamgmt.case.case_iocs_db import delete_ioc_comment diff --git a/source/app/blueprints/rest/case/case_notes_routes.py b/source/app/blueprints/rest/case/case_notes_routes.py index ff5483124..6a0fc5e2a 100644 --- a/source/app/blueprints/rest/case/case_notes_routes.py +++ b/source/app/blueprints/rest/case/case_notes_routes.py @@ -26,7 +26,7 @@ from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.notes import notes_create from app.business.notes import notes_list_revisions from app.business.notes import notes_get_revision diff --git a/source/app/blueprints/rest/case/case_tasks_routes.py b/source/app/blueprints/rest/case/case_tasks_routes.py index 15917e3d5..6078da2cf 100644 --- a/source/app/blueprints/rest/case/case_tasks_routes.py +++ b/source/app/blueprints/rest/case/case_tasks_routes.py @@ -26,7 +26,7 @@ from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.tasks import tasks_delete from app.business.tasks import tasks_create from app.business.tasks import tasks_get diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index a5ef0a5f9..6eea9cd60 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -73,7 +73,7 @@ from app.util import add_obj_history_entry from app.blueprints.responses import response_error from app.blueprints.responses import response_success -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.events import events_create from app.business.events import events_update from app.business.events import events_delete diff --git a/source/app/blueprints/rest/case_comments.py b/source/app/blueprints/rest/case_comments.py index d67ea1aac..cdaf325e8 100644 --- a/source/app/blueprints/rest/case_comments.py +++ b/source/app/blueprints/rest/case_comments.py @@ -24,7 +24,7 @@ from app.blueprints.responses import response_error from app.blueprints.responses import response_success from app.business.comments import comments_update_for_case -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.blueprints.iris_user import iris_current_user diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index e4f7a985a..4c7a52d1c 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -60,7 +60,7 @@ from app.business.cases import cases_update from app.business.cases import cases_create from app.business.cases import cases_get_by_identifier -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook from app.datamgmt.manage.manage_access_control_db import user_has_client_access diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index 73e7dfe49..922726790 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -27,7 +27,7 @@ from app.blueprints.iris_user import iris_current_user from app.business.customers import customers_get from app.business.customers_contacts import customers_contacts_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError 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 diff --git a/source/app/blueprints/rest/reports_route.py b/source/app/blueprints/rest/reports_route.py index 3451b943c..53fdd1115 100644 --- a/source/app/blueprints/rest/reports_route.py +++ b/source/app/blueprints/rest/reports_route.py @@ -22,8 +22,8 @@ from flask import request from flask import send_file -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.business.reports.reports import generate_investigation_report, generate_activities_report from app.models.authorization import CaseAccessLevel diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index 0451f1932..9c3ab98ac 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -41,8 +41,8 @@ from app.business.alerts import alerts_update from app.business.alerts import alerts_delete from app.business.alerts import related_alerts_get -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.access_controls import access_controls_user_has_customer_access diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 57f40a1a1..f2ef77796 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 BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.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 diff --git a/source/app/blueprints/rest/v2/alerts_routes/comments.py b/source/app/blueprints/rest/v2/alerts_routes/comments.py index b7e98c7f9..521d91d8d 100644 --- a/source/app/blueprints/rest/v2/alerts_routes/comments.py +++ b/source/app/blueprints/rest/v2/alerts_routes/comments.py @@ -39,7 +39,7 @@ from app.business.alerts import alerts_get from app.business.alerts import alerts_exists from app.blueprints.iris_user import iris_current_user -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError class CommentsOperations: diff --git a/source/app/blueprints/rest/v2/assets.py b/source/app/blueprints/rest/v2/assets.py index d92b24ab5..5ffc14de2 100644 --- a/source/app/blueprints/rest/v2/assets.py +++ b/source/app/blueprints/rest/v2/assets.py @@ -26,8 +26,8 @@ from app.blueprints.rest.v2.assets_routes.comments import assets_comments_blueprint from app.business.assets import assets_delete from app.business.assets import assets_get -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseAssetsSchema from app.blueprints.access_controls import ac_api_return_access_denied diff --git a/source/app/blueprints/rest/v2/assets_routes/comments.py b/source/app/blueprints/rest/v2/assets_routes/comments.py index c9014b2e8..a52942571 100644 --- a/source/app/blueprints/rest/v2/assets_routes/comments.py +++ b/source/app/blueprints/rest/v2/assets_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_delete_for_asset from app.blueprints.rest.case_comments import case_comment_update from app.business.assets import assets_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.blueprints.iris_user import iris_current_user from app.models.authorization import CaseAccessLevel diff --git a/source/app/blueprints/rest/v2/auth.py b/source/app/blueprints/rest/v2/auth.py index cc36e0529..8a5f4ed01 100644 --- a/source/app/blueprints/rest/v2/auth.py +++ b/source/app/blueprints/rest/v2/auth.py @@ -29,7 +29,7 @@ from app import db from app import oidc_client from app.blueprints.iris_user import iris_current_user -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.logger import logger from app.blueprints.access_controls import is_authentication_ldap from app.blueprints.access_controls import is_authentication_oidc diff --git a/source/app/blueprints/rest/v2/case_routes/assets.py b/source/app/blueprints/rest/v2/case_routes/assets.py index a8263ffa6..a13e21895 100644 --- a/source/app/blueprints/rest/v2/case_routes/assets.py +++ b/source/app/blueprints/rest/v2/case_routes/assets.py @@ -36,8 +36,8 @@ from app.business.assets import assets_get from app.business.assets import assets_update from app.business.assets import assets_delete -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseAssetsSchema diff --git a/source/app/blueprints/rest/v2/case_routes/events.py b/source/app/blueprints/rest/v2/case_routes/events.py index e92e8185a..f47fc4a15 100644 --- a/source/app/blueprints/rest/v2/case_routes/events.py +++ b/source/app/blueprints/rest/v2/case_routes/events.py @@ -34,8 +34,8 @@ from app.business.events import events_delete from app.models.cases import CasesEvent from app.schema.marshables import EventSchema -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.cases import cases_exists from app.iris_engine.utils.collab import notify from app.models.authorization import CaseAccessLevel diff --git a/source/app/blueprints/rest/v2/case_routes/evidences.py b/source/app/blueprints/rest/v2/case_routes/evidences.py index 03d712361..8d40c306c 100644 --- a/source/app/blueprints/rest/v2/case_routes/evidences.py +++ b/source/app/blueprints/rest/v2/case_routes/evidences.py @@ -23,8 +23,8 @@ 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.models.authorization import CaseAccessLevel -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.blueprints.rest.parsing import parse_pagination_parameters from app.blueprints.access_controls import ac_api_return_access_denied from app.blueprints.rest.endpoints import response_api_created diff --git a/source/app/blueprints/rest/v2/case_routes/iocs.py b/source/app/blueprints/rest/v2/case_routes/iocs.py index 64292cebb..903ff5e66 100644 --- a/source/app/blueprints/rest/v2/case_routes/iocs.py +++ b/source/app/blueprints/rest/v2/case_routes/iocs.py @@ -31,8 +31,8 @@ from app.blueprints.rest.endpoints import response_api_paginated from app.blueprints.rest.parsing import parse_pagination_parameters from app.blueprints.rest.parsing import parse_fields_parameters -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.iocs import iocs_create from app.business.iocs import iocs_get from app.business.iocs import iocs_delete diff --git a/source/app/blueprints/rest/v2/case_routes/notes.py b/source/app/blueprints/rest/v2/case_routes/notes.py index ebb6fb61a..6936d4c18 100644 --- a/source/app/blueprints/rest/v2/case_routes/notes.py +++ b/source/app/blueprints/rest/v2/case_routes/notes.py @@ -35,8 +35,8 @@ from app.business.notes import notes_update from app.business.notes import notes_delete from app.business.cases import cases_exists -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook diff --git a/source/app/blueprints/rest/v2/case_routes/notes_directories.py b/source/app/blueprints/rest/v2/case_routes/notes_directories.py index 38359f871..c288dc1ff 100644 --- a/source/app/blueprints/rest/v2/case_routes/notes_directories.py +++ b/source/app/blueprints/rest/v2/case_routes/notes_directories.py @@ -30,8 +30,8 @@ from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import response_api_paginated from app.blueprints.access_controls import ac_api_return_access_denied -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.schema.marshables import CaseNoteDirectorySchema from app.schema.marshables import SearchCaseNoteDirectorySchema from app.business.notes_directories import notes_directories_create diff --git a/source/app/blueprints/rest/v2/case_routes/tasks.py b/source/app/blueprints/rest/v2/case_routes/tasks.py index d3fa1b700..cf1a658a8 100644 --- a/source/app/blueprints/rest/v2/case_routes/tasks.py +++ b/source/app/blueprints/rest/v2/case_routes/tasks.py @@ -30,8 +30,8 @@ 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_api_requires from app.schema.marshables import CaseTaskSchema -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.tasks import tasks_create from app.business.tasks import tasks_get from app.business.tasks import tasks_update diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index 62286641c..cfa7b42f4 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -42,7 +42,7 @@ from app.business.cases import cases_delete from app.business.cases import cases_get_by_identifier from app.business.cases import cases_update -from app.business.errors import BusinessProcessingError, ObjectNotFoundError +from app.models.errors import BusinessProcessingError, ObjectNotFoundError from app.business.cases import cases_filter from app.schema.marshables import CaseSchemaForAPIV2 from app.blueprints.access_controls import ac_api_requires diff --git a/source/app/blueprints/rest/v2/events_routes/comments.py b/source/app/blueprints/rest/v2/events_routes/comments.py index f3a9d45eb..ae428fae7 100644 --- a/source/app/blueprints/rest/v2/events_routes/comments.py +++ b/source/app/blueprints/rest/v2/events_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_get_for_event from app.business.comments import comments_delete_for_event from app.business.events import events_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.models.cases import CasesEvent from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel diff --git a/source/app/blueprints/rest/v2/evidences_routes/comments.py b/source/app/blueprints/rest/v2/evidences_routes/comments.py index 139698ef9..3db44e012 100644 --- a/source/app/blueprints/rest/v2/evidences_routes/comments.py +++ b/source/app/blueprints/rest/v2/evidences_routes/comments.py @@ -36,7 +36,7 @@ from app.business.comments import comments_delete_for_evidence from app.models.models import CaseReceivedFile from app.business.evidences import evidences_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel from app.blueprints.rest.case_comments import case_comment_update diff --git a/source/app/blueprints/rest/v2/iocs.py b/source/app/blueprints/rest/v2/iocs.py index e43231497..1a08d961b 100644 --- a/source/app/blueprints/rest/v2/iocs.py +++ b/source/app/blueprints/rest/v2/iocs.py @@ -25,8 +25,8 @@ from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import response_api_success -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.iocs import iocs_update from app.business.iocs import iocs_delete from app.business.iocs import iocs_get diff --git a/source/app/blueprints/rest/v2/iocs_routes/comments.py b/source/app/blueprints/rest/v2/iocs_routes/comments.py index ffa5e6131..e4088fce4 100644 --- a/source/app/blueprints/rest/v2/iocs_routes/comments.py +++ b/source/app/blueprints/rest/v2/iocs_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_get_for_ioc from app.business.comments import comments_delete_for_ioc from app.business.iocs import iocs_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel from app.blueprints.rest.case_comments import case_comment_update diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index b221246d3..c09304bd3 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -29,7 +29,7 @@ from app.datamgmt.exceptions.ElementExceptions import ElementInUseException from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.business.customers import customers_create_with_user from app.business.customers import customers_get from app.business.customers import customers_update @@ -82,9 +82,10 @@ def delete(self, identifier): except ObjectNotFoundError: return response_api_not_found() - except ElementInUseException as e: + except ElementInUseException: return response_api_error('Cannot delete a referenced customer') + customers_blueprint = Blueprint('customers_rest_v2', __name__, url_prefix='/customers') customers = Customers() diff --git a/source/app/blueprints/rest/v2/manage_routes/groups.py b/source/app/blueprints/rest/v2/manage_routes/groups.py index c4040f4bf..05ba6d56d 100644 --- a/source/app/blueprints/rest/v2/manage_routes/groups.py +++ b/source/app/blueprints/rest/v2/manage_routes/groups.py @@ -33,8 +33,8 @@ from app.business.groups import groups_delete from app.models.authorization import Permissions from app.models.authorization import ac_flag_match_mask -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.blueprints.iris_user import iris_current_user from app.iris_engine.access_control.utils import ac_ldp_group_update diff --git a/source/app/blueprints/rest/v2/manage_routes/users.py b/source/app/blueprints/rest/v2/manage_routes/users.py index dcd5973cc..d59a4b6d6 100644 --- a/source/app/blueprints/rest/v2/manage_routes/users.py +++ b/source/app/blueprints/rest/v2/manage_routes/users.py @@ -28,8 +28,8 @@ from app.blueprints.rest.endpoints import response_api_deleted from app.schema.marshables import UserSchemaForAPIV2 from app.models.authorization import Permissions -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.business.users import users_create from app.business.users import users_get from app.business.users import users_update diff --git a/source/app/blueprints/rest/v2/notes_routes/comments.py b/source/app/blueprints/rest/v2/notes_routes/comments.py index 67b98ea38..534b8c0f0 100644 --- a/source/app/blueprints/rest/v2/notes_routes/comments.py +++ b/source/app/blueprints/rest/v2/notes_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_get_for_note from app.business.comments import comments_delete_for_note from app.business.notes import notes_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel from app.blueprints.rest.case_comments import case_comment_update diff --git a/source/app/blueprints/rest/v2/tags.py b/source/app/blueprints/rest/v2/tags.py index 42475ff54..3b2cef1c2 100644 --- a/source/app/blueprints/rest/v2/tags.py +++ b/source/app/blueprints/rest/v2/tags.py @@ -23,7 +23,7 @@ from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.parsing import parse_fields_parameters from app.blueprints.rest.parsing import parse_pagination_parameters -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.tags import tags_filter from app.schema.marshables import TagsSchema from app.blueprints.access_controls import ac_api_requires diff --git a/source/app/blueprints/rest/v2/tasks.py b/source/app/blueprints/rest/v2/tasks.py index a0ee2755c..527a23b6b 100644 --- a/source/app/blueprints/rest/v2/tasks.py +++ b/source/app/blueprints/rest/v2/tasks.py @@ -27,8 +27,8 @@ from app.blueprints.access_controls import ac_api_return_access_denied from app.business.tasks import tasks_delete from app.business.tasks import tasks_get -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseTaskSchema from app.blueprints.rest.v2.tasks_routes.comments import tasks_comments_blueprint diff --git a/source/app/blueprints/rest/v2/tasks_routes/comments.py b/source/app/blueprints/rest/v2/tasks_routes/comments.py index d18d18529..1e2db5909 100644 --- a/source/app/blueprints/rest/v2/tasks_routes/comments.py +++ b/source/app/blueprints/rest/v2/tasks_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_get_for_task from app.business.comments import comments_delete_for_task from app.business.tasks import tasks_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel from app.blueprints.rest.case_comments import case_comment_update diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index fe9bea312..e23ef8d07 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -35,7 +35,7 @@ from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.util import add_obj_history_entry -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.datamgmt.manage.manage_access_control_db import user_has_client_access diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index 808707355..ef1ddaa60 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -18,7 +18,7 @@ from app import db from app.datamgmt.filters.filters_db import get_filter_by_id -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError def alert_filter_add(new_saved_filter): diff --git a/source/app/business/assets.py b/source/app/business/assets.py index c7a331e3d..11ac26f72 100644 --- a/source/app/business/assets.py +++ b/source/app/business/assets.py @@ -19,8 +19,8 @@ from flask_sqlalchemy.pagination import Pagination from app import db -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.cases import cases_exists from app.datamgmt.states import update_assets_state from app.models.models import CaseAssets diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 38b2b5512..d12d608f2 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -23,8 +23,8 @@ from app.logger import logger from app.util import add_obj_history_entry from app.models.models import ReviewStatusList -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.iocs import iocs_exports_to_json from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity diff --git a/source/app/business/comments.py b/source/app/business/comments.py index e53c4c9d5..5ade5b99e 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -23,8 +23,8 @@ from app import db from app.business.alerts import alerts_exists from app.business.alerts import alerts_get -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.datamgmt.case.case_comments import get_case_comment from app.datamgmt.comments import get_filtered_alert_comments from app.datamgmt.comments import get_filtered_asset_comments diff --git a/source/app/business/customers.py b/source/app/business/customers.py index ebe9efc97..92c750f83 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -24,7 +24,7 @@ from app.datamgmt.client.client_db import get_customer_by_name from app.datamgmt.client.client_db import update_customer from app.datamgmt.client.client_db import delete_client -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError # TODO maybe this method should be removed and always create a customer with at least a user diff --git a/source/app/business/customers_contacts.py b/source/app/business/customers_contacts.py index 1583cb0e9..d7948b3c0 100644 --- a/source/app/business/customers_contacts.py +++ b/source/app/business/customers_contacts.py @@ -17,7 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from app.models.models import Contact -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.datamgmt.client.client_db import get_client_contact diff --git a/source/app/business/events.py b/source/app/business/events.py index 50b103375..d485493ff 100644 --- a/source/app/business/events.py +++ b/source/app/business/events.py @@ -21,12 +21,12 @@ from app import db from app.blueprints.iris_user import iris_current_user from app.models.cases import CasesEvent -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.util import add_obj_history_entry from app.datamgmt.states import update_timeline_state from app.datamgmt.case.case_events_db import save_event_category from app.datamgmt.case.case_events_db import update_event_assets -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.datamgmt.case.case_events_db import update_event_iocs from app.datamgmt.case.case_events_db import get_case_event from app.datamgmt.case.case_events_db import delete_event diff --git a/source/app/business/evidences.py b/source/app/business/evidences.py index 623c18a7c..b531e5083 100644 --- a/source/app/business/evidences.py +++ b/source/app/business/evidences.py @@ -19,8 +19,8 @@ from flask_sqlalchemy.pagination import Pagination from app.blueprints.iris_user import iris_current_user -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.models.models import CaseReceivedFile diff --git a/source/app/business/groups.py b/source/app/business/groups.py index 26e947299..8d5e3eabf 100644 --- a/source/app/business/groups.py +++ b/source/app/business/groups.py @@ -22,8 +22,8 @@ from app.datamgmt.manage.manage_groups_db import get_group_details from app.datamgmt.manage.manage_groups_db import update_group from app.datamgmt.manage.manage_groups_db import delete_group -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.iris_engine.access_control.utils import ac_ldp_group_removal diff --git a/source/app/business/iocs.py b/source/app/business/iocs.py index b68a21f22..d84bf1dd2 100644 --- a/source/app/business/iocs.py +++ b/source/app/business/iocs.py @@ -29,8 +29,8 @@ from app.schema.marshables import IocSchema from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.case_iocs_db import get_ioc from app.util import add_obj_history_entry from app.datamgmt.case.case_iocs_db import get_filtered_iocs diff --git a/source/app/business/notes.py b/source/app/business/notes.py index 9ebaaed08..9d17f647a 100644 --- a/source/app/business/notes.py +++ b/source/app/business/notes.py @@ -22,9 +22,9 @@ from app.datamgmt.persistence_error import PersistenceError from app.blueprints.iris_user import iris_current_user from app.logger import logger -from app.business.errors import BusinessProcessingError -from app.business.errors import UnhandledBusinessError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import UnhandledBusinessError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.case_notes_db import search_notes_in_case from app.datamgmt.case.case_notes_db import get_note from app.datamgmt.case.case_notes_db import update_note_revision diff --git a/source/app/business/notes_directories.py b/source/app/business/notes_directories.py index 73565e65b..e8dd63248 100644 --- a/source/app/business/notes_directories.py +++ b/source/app/business/notes_directories.py @@ -19,7 +19,7 @@ from app import db from app.iris_engine.utils.tracker import track_activity from app.models.models import NoteDirectory -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.case_notes_db import get_directory from app.datamgmt.case.case_notes_db import delete_directory from app.datamgmt.case.case_notes_db import paginate_notes_directories diff --git a/source/app/business/reports/reporter.py b/source/app/business/reports/reporter.py index b53aa0fb8..ec633dce2 100644 --- a/source/app/business/reports/reporter.py +++ b/source/app/business/reports/reporter.py @@ -22,7 +22,7 @@ import os from datetime import datetime -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.blueprints.iris_user import iris_current_user from docx_generator.docx_generator import DocxGenerator from docx_generator.exceptions import rendering_error diff --git a/source/app/business/reports/reports.py b/source/app/business/reports/reports.py index ca8c38f36..6bdfee93b 100644 --- a/source/app/business/reports/reports.py +++ b/source/app/business/reports/reports.py @@ -19,7 +19,7 @@ import base64 import os -from app.business.errors import ObjectNotFoundError, BusinessProcessingError +from app.models.errors import ObjectNotFoundError, BusinessProcessingError from app.business.reports.reporter import IrisMakeMdReport, IrisMakeDocReport from app.datamgmt.case.case_db import get_case from app.iris_engine.module_handler.module_handler import call_modules_hook diff --git a/source/app/business/tasks.py b/source/app/business/tasks.py index 5d88609a1..227fb7245 100644 --- a/source/app/business/tasks.py +++ b/source/app/business/tasks.py @@ -33,8 +33,8 @@ from app.iris_engine.utils.tracker import track_activity from app.models.models import CaseTasks from app.models.pagination_parameters import PaginationParameters -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError def tasks_delete(task: CaseTasks): diff --git a/source/app/business/users.py b/source/app/business/users.py index f10f693c0..0b7b6189a 100644 --- a/source/app/business/users.py +++ b/source/app/business/users.py @@ -18,8 +18,8 @@ from app import db from app.models.authorization import User -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.datamgmt.manage.manage_users_db import get_active_user from app.datamgmt.manage.manage_users_db import get_user_details_return_user from app.datamgmt.manage.manage_users_db import get_active_user_by_login diff --git a/source/app/datamgmt/filtering.py b/source/app/datamgmt/filtering.py index 14150e747..66ff125e6 100644 --- a/source/app/datamgmt/filtering.py +++ b/source/app/datamgmt/filtering.py @@ -20,7 +20,7 @@ from sqlalchemy import String, Text, inspect, or_, not_, and_ from app import app -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.datamgmt.conversions import convert_sort_direction from app.models.pagination_parameters import PaginationParameters from app.datamgmt.authorization import RESTRICTED_USER_FIELDS diff --git a/source/app/business/errors.py b/source/app/models/errors.py similarity index 100% rename from source/app/business/errors.py rename to source/app/models/errors.py diff --git a/source/app/post_init.py b/source/app/post_init.py index 8a858c320..068ada0b4 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -79,7 +79,7 @@ from app.business.customers import customers_get_by_name 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.models.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 From 9ab53e49c86b67f4f4cecd3434667001964e1070 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:06:09 +0100 Subject: [PATCH 106/286] Fixed import constraint --- .../rest/manage/manage_customers_routes.py | 6 +++--- .../rest/v2/manage_routes/customers.py | 6 +++--- source/app/datamgmt/client/client_db.py | 6 +++--- .../datamgmt/exceptions/ElementExceptions.py | 20 ------------------- source/app/datamgmt/exceptions/__init__.py | 0 source/app/models/errors.py | 8 ++++++-- 6 files changed, 15 insertions(+), 31 deletions(-) delete mode 100644 source/app/datamgmt/exceptions/ElementExceptions.py delete mode 100644 source/app/datamgmt/exceptions/__init__.py diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index 922726790..e2c88bdea 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -28,6 +28,7 @@ from app.business.customers import customers_get from app.business.customers_contacts import customers_contacts_get from app.models.errors import ObjectNotFoundError +from app.models.errors import ElementInUseError 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 @@ -40,7 +41,6 @@ from app.datamgmt.client.client_db import get_client_contact from app.datamgmt.client.client_db import update_client from app.datamgmt.client.client_db import update_contact -from app.datamgmt.exceptions.ElementExceptions import ElementInUseException from app.datamgmt.manage.manage_users_db import add_user_to_customer from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions @@ -290,7 +290,7 @@ def delete_customers(client_id): except ObjectNotFoundError: return response_error('Invalid Customer ID') - except ElementInUseException: + except ElementInUseError: return response_error('Cannot delete a referenced customer') except Exception: @@ -313,7 +313,7 @@ def delete_contact_route(client_id, contact_id): except ObjectNotFoundError: return response_error('Invalid contact ID') - except ElementInUseException: + except ElementInUseError: return response_error('Cannot delete a referenced contact') except Exception: diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index c09304bd3..1e430d44a 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -26,10 +26,10 @@ from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import response_api_deleted from app.blueprints.access_controls import ac_api_requires -from app.datamgmt.exceptions.ElementExceptions import ElementInUseException from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema from app.models.errors import ObjectNotFoundError +from app.models.errors import ElementInUseError from app.business.customers import customers_create_with_user from app.business.customers import customers_get from app.business.customers import customers_update @@ -82,8 +82,8 @@ def delete(self, identifier): except ObjectNotFoundError: return response_api_not_found() - except ElementInUseException: - return response_api_error('Cannot delete a referenced customer') + except ElementInUseError as e: + return response_api_error(e.get_message()) customers_blueprint = Blueprint('customers_rest_v2', __name__, url_prefix='/customers') diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index c06f40f56..9122881f7 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -23,7 +23,7 @@ from typing import Optional from app import db -from app.datamgmt.exceptions.ElementExceptions import ElementInUseException +from app.models.errors import ElementInUseError from app.models.cases import Cases from app.models.models import Client from app.models.models import Contact @@ -143,7 +143,7 @@ def delete_contact(contact: Contact): db.session.commit() except Exception: - raise ElementInUseException('A currently referenced contact cannot be deleted') + raise ElementInUseError('A currently referenced contact cannot be deleted') def update_client(schema, customer: Client, data): @@ -168,7 +168,7 @@ def delete_client(customer: Client) -> None: db.session.delete(customer) db.session.commit() except Exception: - raise ElementInUseException('A currently referenced customer cannot be deleted') + raise ElementInUseError('Cannot delete a referenced customer') def get_case_client(case_id: int) -> Client: diff --git a/source/app/datamgmt/exceptions/ElementExceptions.py b/source/app/datamgmt/exceptions/ElementExceptions.py deleted file mode 100644 index d130da12d..000000000 --- a/source/app/datamgmt/exceptions/ElementExceptions.py +++ /dev/null @@ -1,20 +0,0 @@ -# IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net -# -# 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. - -class ElementInUseException(Exception): - pass diff --git a/source/app/datamgmt/exceptions/__init__.py b/source/app/datamgmt/exceptions/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/source/app/models/errors.py b/source/app/models/errors.py index df14c580e..f86a81859 100644 --- a/source/app/models/errors.py +++ b/source/app/models/errors.py @@ -41,7 +41,11 @@ def __init__(self): class UnhandledBusinessError(BusinessProcessingError): def __init__(self, message, data=None): - self._message = message - self._data = data + super().__init__(message, data) logger.exception(message) logger.exception(data) + + +class ElementInUseError(BusinessProcessingError): + + pass From c7c3a0f827001b379cc46580fb0c6623abade146 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:26:41 +0100 Subject: [PATCH 107/286] Use annotations to define endpoints on groups --- source/app/blueprints/rest/v2/manage.py | 3 +- .../rest/v2/manage_routes/groups.py | 36 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/source/app/blueprints/rest/v2/manage.py b/source/app/blueprints/rest/v2/manage.py index 08f024935..f24bd02b5 100644 --- a/source/app/blueprints/rest/v2/manage.py +++ b/source/app/blueprints/rest/v2/manage.py @@ -18,13 +18,12 @@ from flask import Blueprint -from app.blueprints.rest.v2.manage_routes.groups import create_groups_blueprint +from app.blueprints.rest.v2.manage_routes.groups import groups_blueprint from app.blueprints.rest.v2.manage_routes.users import users_blueprint from app.blueprints.rest.v2.manage_routes.customers import customers_blueprint manage_v2_blueprint = Blueprint('manage', __name__, url_prefix='/manage') -groups_blueprint = create_groups_blueprint() manage_v2_blueprint.register_blueprint(groups_blueprint) manage_v2_blueprint.register_blueprint(users_blueprint) manage_v2_blueprint.register_blueprint(customers_blueprint) diff --git a/source/app/blueprints/rest/v2/manage_routes/groups.py b/source/app/blueprints/rest/v2/manage_routes/groups.py index 05ba6d56d..dbd01a182 100644 --- a/source/app/blueprints/rest/v2/manage_routes/groups.py +++ b/source/app/blueprints/rest/v2/manage_routes/groups.py @@ -25,7 +25,7 @@ from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import response_api_deleted -from app.blueprints.access_controls import wrap_with_permission_checks +from app.blueprints.access_controls import ac_api_requires from app.schema.marshables import AuthorizationGroupSchema from app.business.groups import groups_create from app.business.groups import groups_get @@ -92,20 +92,30 @@ def delete(self, identifier): return response_api_error(e.get_message()) -def create_groups_blueprint(): - blueprint = Blueprint('rest_v2_groups', __name__, url_prefix='/groups') - groups = Groups() +groups_blueprint = Blueprint('rest_v2_groups', __name__, url_prefix='/groups') +groups = Groups() - 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.read, Permissions.server_administrator) - blueprint.add_url_rule('/', view_func=get_group, methods=['GET']) +@groups_blueprint.post('') +@ac_api_requires(Permissions.server_administrator) +def create_group(): + return groups.create() - update_group = wrap_with_permission_checks(groups.update, Permissions.server_administrator) - blueprint.add_url_rule('/', view_func=update_group, methods=['PUT']) - delete_group = wrap_with_permission_checks(groups.delete, Permissions.server_administrator) - blueprint.add_url_rule('/', view_func=delete_group, methods=['DELETE']) +@groups_blueprint.get('/') +@ac_api_requires(Permissions.server_administrator) +def read_group(identifier): + return groups.read(identifier) + + +@groups_blueprint.put('/') +@ac_api_requires(Permissions.server_administrator) +def update_group(identifier): + return groups.update(identifier) + + +@groups_blueprint.delete('/') +@ac_api_requires(Permissions.server_administrator) +def update_group(identifier): + return groups.delete(identifier) - return blueprint From 20bb8d9e94eb92643d51dc405b6c9012f6209bb7 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:31:09 +0100 Subject: [PATCH 108/286] Fixed ruff warnings --- source/app/blueprints/rest/v2/manage_routes/groups.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/manage_routes/groups.py b/source/app/blueprints/rest/v2/manage_routes/groups.py index dbd01a182..1a05ed379 100644 --- a/source/app/blueprints/rest/v2/manage_routes/groups.py +++ b/source/app/blueprints/rest/v2/manage_routes/groups.py @@ -116,6 +116,5 @@ def update_group(identifier): @groups_blueprint.delete('/') @ac_api_requires(Permissions.server_administrator) -def update_group(identifier): +def delete_group(identifier): return groups.delete(identifier) - From a9079a73e8846016098161214101eee96b85cd45 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:40:13 +0100 Subject: [PATCH 109/286] Added import constraint: Do not import marshmallow from the persistence layer --- pyproject.toml | 7 +++++++ .../rest/manage/manage_customers_routes.py | 14 +++++++++----- source/app/datamgmt/client/client_db.py | 18 ------------------ 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 28c140756..e6e49b726 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,3 +60,10 @@ source_modules = "app.datamgmt.case" forbidden_modules = "app.iris_engine" allow_indirect_imports = true +[[tool.importlinter.contracts]] +name = "Do not import marshmallow from the persistence layer" +type = "forbidden" +source_modules = "app.datamgmt.client" +forbidden_modules = "marshmallow" +allow_indirect_imports = true + diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index e2c88bdea..89b6618fc 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.business.customers import customers_get +from app.business.customers import customers_get, customers_update from app.business.customers_contacts import customers_contacts_get from app.models.errors import ObjectNotFoundError from app.models.errors import ElementInUseError @@ -39,7 +39,6 @@ from app.datamgmt.client.client_db import get_client_contacts from app.datamgmt.client.client_db import get_client_list from app.datamgmt.client.client_db import get_client_contact -from app.datamgmt.client.client_db import update_client from app.datamgmt.client.client_db import update_contact from app.datamgmt.manage.manage_users_db import add_user_to_customer from app.iris_engine.utils.tracker import track_activity @@ -50,6 +49,7 @@ from app.blueprints.responses import response_error from app.blueprints.responses import response_success from app.blueprints.rest.endpoints import endpoint_deprecated +from app.business.customers import customers_exists_another_with_same_name manage_customers_rest_blueprint = Blueprint('manage_customers_rest', __name__) @@ -241,16 +241,20 @@ def view_customers(client_id): if not customer: raise response_error('Invalid Customer ID') - update_client(client_schema, customer, request.json) + data = request.json + if customers_exists_another_with_same_name(client_id, data.get('customer_name')): + raise ValidationError('Customer already exists', field_name='customer_name') + client_schema.load(data, instance=customer) + customers_update() except ValidationError as e: - return response_error("", data=e.messages) + return response_error('', data=e.messages) except Exception as e: print(traceback.format_exc()) return response_error(f'An error occurred during Customer update. {e}') - return response_success("Customer updated", client_schema.dump(customer)) + return response_success('Customer updated', client_schema.dump(customer)) @manage_customers_rest_blueprint.route('/manage/customers/add', methods=['POST']) diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index 9122881f7..388f920be 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -16,7 +16,6 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import marshmallow from sqlalchemy import func from sqlalchemy import and_ from typing import List @@ -146,23 +145,6 @@ def delete_contact(contact: Contact): raise ElementInUseError('A currently referenced contact cannot be deleted') -def update_client(schema, customer: Client, data): - exists = Client.query.filter( - Client.client_id != customer.client_id, - func.lower(Client.name) == data.get('customer_name').lower() - ).first() - - if exists: - raise marshmallow.exceptions.ValidationError( - 'Customer already exists', - field_name='customer_name' - ) - - schema.load(data, instance=customer) - - update_customer() - - def delete_client(customer: Client) -> None: try: db.session.delete(customer) From ab8c8eedba955b0d0ffbc5456781c971cfc9a3c6 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:59:25 +0100 Subject: [PATCH 110/286] Tyring to move dependence to marshmallow up in the API layer --- .../manage/manage_case_templates_db.py | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/source/app/datamgmt/manage/manage_case_templates_db.py b/source/app/datamgmt/manage/manage_case_templates_db.py index d82975299..af5e1befd 100644 --- a/source/app/datamgmt/manage/manage_case_templates_db.py +++ b/source/app/datamgmt/manage/manage_case_templates_db.py @@ -14,9 +14,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + import marshmallow from datetime import datetime -from typing import List, Optional, Union +from typing import List +from typing import Optional +from typing import Union from app import db from app.datamgmt.case.case_tasks_db import add_task @@ -170,8 +173,8 @@ def case_template_pre_modifier(case_schema: CaseSchema, case_template_id: str): def case_template_populate_tasks(case: Cases, case_template: CaseTemplate): + tasks = [] logs = [] - # Update case tasks for task_template in case_template.tasks: try: # validate before saving @@ -180,31 +183,27 @@ def case_template_populate_tasks(case: Cases, case_template: CaseTemplate): # Remap case task template fields # Set status to "To Do" which is ID 1 mapped_task_template = { - "task_title": task_template['title'], - "task_description": task_template['description'] if task_template.get('description') else "", - "task_tags": ",".join(tag for tag in task_template["tags"]) if task_template.get('tags') else "", - "task_status_id": 1 + 'task_title': task_template['title'], + 'task_description': task_template['description'] if task_template.get('description') else '', + 'task_tags': ','.join(tag for tag in task_template['tags']) if task_template.get('tags') else '', + 'task_status_id': 1 } mapped_task_template = call_modules_hook('on_preload_task_create', data=mapped_task_template, caseid=case.case_id) task = task_schema.load(mapped_task_template) + tasks.append(task) + except marshmallow.exceptions.ValidationError as e: + logs.append(e.messages) - assignee_id_list = [] - - ctask = add_task(task=task, - assignee_id_list=assignee_id_list, - user_id=case.user_id, - caseid=case.case_id - ) - - ctask = call_modules_hook('on_postload_task_create', data=ctask, caseid=case.case_id) + # Update case tasks + for task in tasks: + ctask = add_task(task=task, assignee_id_list=[], user_id=case.user_id, caseid=case.case_id) - if not ctask: - logs.append("Unable to create task for internal reasons") + ctask = call_modules_hook('on_postload_task_create', data=ctask, caseid=case.case_id) - except marshmallow.exceptions.ValidationError as e: - logs.append(e.messages) + if not ctask: + logs.append('Unable to create task for internal reasons') return logs @@ -283,11 +282,11 @@ def case_template_post_modifier(case: Cases, case_template_id: Union[str, int]): case_template = get_case_template_by_id(int(case_template_id)) logs = [] if not case_template: - logs.append(f"Case template {case_template_id} not found") + logs.append(f'Case template {case_template_id} not found') return None, logs # Update summary, we want to append in order not to skip the initial case description - case.description += "\n" + case_template.summary + case.description += '\n' + case_template.summary # Update case tags for tag_str in case_template.tags: From c1fc3a225a4e9f8c4685ad1658e3ed0c28e2cba4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:04:08 +0100 Subject: [PATCH 111/286] Fixed incorrect typing --- source/app/datamgmt/manage/manage_case_templates_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/datamgmt/manage/manage_case_templates_db.py b/source/app/datamgmt/manage/manage_case_templates_db.py index af5e1befd..f022240b8 100644 --- a/source/app/datamgmt/manage/manage_case_templates_db.py +++ b/source/app/datamgmt/manage/manage_case_templates_db.py @@ -158,7 +158,7 @@ def validate_case_template(data: dict, update: bool = False) -> Optional[str]: return str(e) -def case_template_pre_modifier(case_schema: CaseSchema, case_template_id: str): +def case_template_pre_modifier(case_schema: Cases, case_template_id: str): case_template = get_case_template_by_id(int(case_template_id)) if not case_template: return None From 2c769914960c264247bb4292fd2dc9ed0581a081 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:19:54 +0100 Subject: [PATCH 112/286] Removed an marshable import from the persistence layer --- pyproject.toml | 14 +++++------ .../blueprints/rest/manage/manage_groups.py | 12 ++++++--- .../app/datamgmt/manage/manage_groups_db.py | 25 ++++++------------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e6e49b726..d44173337 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,21 +49,21 @@ 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" +source_modules = ["app.datamgmt.manage.manage_case_state_db", "app.datamgmt.manage.manage_groups_db"] forbidden_modules = "app.schema.marshables" allow_indirect_imports = true [[tool.importlinter.contracts]] -name = "Do not import the engine from the persistence layer" +name = "Do not import marshmallow from the persistence layer" type = "forbidden" -source_modules = "app.datamgmt.case" -forbidden_modules = "app.iris_engine" +source_modules = "app.datamgmt.client" +forbidden_modules = "marshmallow" allow_indirect_imports = true [[tool.importlinter.contracts]] -name = "Do not import marshmallow from the persistence layer" +name = "Do not import the engine from the persistence layer" type = "forbidden" -source_modules = "app.datamgmt.client" -forbidden_modules = "marshmallow" +source_modules = "app.datamgmt.case" +forbidden_modules = "app.iris_engine" allow_indirect_imports = true diff --git a/source/app/blueprints/rest/manage/manage_groups.py b/source/app/blueprints/rest/manage/manage_groups.py index 477bfb354..ea02453b3 100644 --- a/source/app/blueprints/rest/manage/manage_groups.py +++ b/source/app/blueprints/rest/manage/manage_groups.py @@ -26,17 +26,18 @@ from app import app from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_groups_db import add_all_cases_access_to_group +from app.datamgmt.manage.manage_groups_db import get_groups_list +from app.datamgmt.manage.manage_groups_db import get_users_by_group_identifiers from app.datamgmt.manage.manage_groups_db import add_case_access_to_group from app.datamgmt.manage.manage_groups_db import delete_group from app.datamgmt.manage.manage_groups_db import get_group from app.datamgmt.manage.manage_groups_db import get_group_details from app.datamgmt.manage.manage_groups_db import get_group_with_members -from app.datamgmt.manage.manage_groups_db import get_groups_list_hr_perms from app.datamgmt.manage.manage_groups_db import remove_cases_access_from_group from app.datamgmt.manage.manage_groups_db import remove_user_from_group from app.datamgmt.manage.manage_groups_db import update_group_members from app.datamgmt.manage.manage_users_db import get_user -from app.iris_engine.access_control.utils import ac_ldp_group_removal +from app.iris_engine.access_control.utils import ac_ldp_group_removal, ac_permission_to_list from app.iris_engine.access_control.utils import ac_ldp_group_update from app.iris_engine.access_control.utils import ac_recompute_effective_ac_from_users_list from app.models.authorization import Permissions, ac_flag_match_mask @@ -59,7 +60,12 @@ @manage_groups_rest_blueprint.route('/manage/groups/list', methods=['GET']) @ac_api_requires(Permissions.server_administrator) def manage_groups_index(): - groups = get_groups_list_hr_perms() + groups = get_groups_list() + groups = AuthorizationGroupSchema().dump(groups, many=True) + group_to_users = get_users_by_group_identifiers() + for group in groups: + group['group_permissions_list'] = ac_permission_to_list(group['group_permissions']) + group['group_members'] = group_to_users.get(group['group_id'], []) return response_success('', data=groups) diff --git a/source/app/datamgmt/manage/manage_groups_db.py b/source/app/datamgmt/manage/manage_groups_db.py index e4013a9c8..9ab524033 100644 --- a/source/app/datamgmt/manage/manage_groups_db.py +++ b/source/app/datamgmt/manage/manage_groups_db.py @@ -25,11 +25,11 @@ 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, ac_access_level_mask_from_val_list +from app.models.authorization import Group +from app.models.authorization import 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 -from app.schema.marshables import AuthorizationGroupSchema def create_group(group: Group): @@ -47,9 +47,7 @@ def update_group(): db.session.commit() -def get_groups_list_hr_perms(): - groups = get_groups_list() - +def get_users_by_group_identifiers(): get_membership_list = UserGroup.query.with_entities( UserGroup.group_id, User.user, @@ -57,28 +55,21 @@ def get_groups_list_hr_perms(): User.name ).join(UserGroup.user).all() - membership_list = {} + result = {} for member in get_membership_list: - if member.group_id not in membership_list: - membership_list[member.group_id] = [{ + if member.group_id not in result: + result[member.group_id] = [{ 'user': member.user, 'name': member.name, 'id': member.id }] else: - membership_list[member.group_id].append({ + result[member.group_id].append({ 'user': member.user, 'name': member.name, 'id': member.id }) - - groups = AuthorizationGroupSchema().dump(groups, many=True) - for group in groups: - perms = ac_permission_to_list(group['group_permissions']) - group['group_permissions_list'] = perms - group['group_members'] = membership_list.get(group['group_id'], []) - - return groups + return result def get_group(group_id): From 197de4f774c6094154b7ee7f4b1c77a0644e8d5f Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:20:41 +0100 Subject: [PATCH 113/286] Fixed ruff warning --- source/app/datamgmt/manage/manage_case_templates_db.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/app/datamgmt/manage/manage_case_templates_db.py b/source/app/datamgmt/manage/manage_case_templates_db.py index f022240b8..2608707ac 100644 --- a/source/app/datamgmt/manage/manage_case_templates_db.py +++ b/source/app/datamgmt/manage/manage_case_templates_db.py @@ -30,7 +30,6 @@ from app.models.models import Tags from app.models.models import NoteDirectory from app.models.authorization import User -from app.schema.marshables import CaseSchema from app.schema.marshables import CaseTaskSchema from app.schema.marshables import CaseNoteDirectorySchema from app.schema.marshables import CaseNoteSchema From 5b308a084b4fed1caf522df507a974431bbd820f Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:22:08 +0100 Subject: [PATCH 114/286] Deprecated POST /manage/customers/delete/ --- 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 89b6618fc..e410f198a 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -285,6 +285,7 @@ def add_customers(): @manage_customers_rest_blueprint.route('/manage/customers/delete/', methods=['POST']) +@endpoint_deprecated('DELETE', '/api/v2/manage/customers/{identifier}') @ac_api_requires(Permissions.customers_write) @ac_api_requires_client_access() def delete_customers(client_id): From 901098bc1f1abea8b68d23c6307064c3ed03cc60 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 08:55:19 +0100 Subject: [PATCH 115/286] Removed unnecessary file --- source/tests/unit/datamgmt/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 source/tests/unit/datamgmt/__init__.py diff --git a/source/tests/unit/datamgmt/__init__.py b/source/tests/unit/datamgmt/__init__.py deleted file mode 100644 index e69de29bb..000000000 From 0b2b15f5b9f2ec3b21ed7e4fd7a4c1c3fdb903ed Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 09:08:27 +0100 Subject: [PATCH 116/286] Removed all unit tests which have not been run in a long time. Added a very simple tests, to show it is hard to execute them (because of the app.__init__) --- source/tests/{unit => app}/__init__.py | 0 .../{unit => app}/blueprints/__init__.py | 0 .../case => app/blueprints/rest}/__init__.py | 0 .../blueprints/rest/test_parsing.py} | 22 +++----- source/tests/test_helper.py | 50 ------------------- .../case/test_case_rfiles_routes.py | 36 ------------- .../tests/unit/blueprints/manage/__init__.py | 0 .../manage/test_manage_modules_routes.py | 36 ------------- 8 files changed, 7 insertions(+), 137 deletions(-) rename source/tests/{unit => app}/__init__.py (100%) rename source/tests/{unit => app}/blueprints/__init__.py (100%) rename source/tests/{unit/blueprints/case => app/blueprints/rest}/__init__.py (100%) rename source/tests/{unit/blueprints/case/test_case_tasks_routes.py => app/blueprints/rest/test_parsing.py} (57%) delete mode 100644 source/tests/test_helper.py delete mode 100644 source/tests/unit/blueprints/case/test_case_rfiles_routes.py delete mode 100644 source/tests/unit/blueprints/manage/__init__.py delete mode 100644 source/tests/unit/blueprints/manage/test_manage_modules_routes.py diff --git a/source/tests/unit/__init__.py b/source/tests/app/__init__.py similarity index 100% rename from source/tests/unit/__init__.py rename to source/tests/app/__init__.py diff --git a/source/tests/unit/blueprints/__init__.py b/source/tests/app/blueprints/__init__.py similarity index 100% rename from source/tests/unit/blueprints/__init__.py rename to source/tests/app/blueprints/__init__.py diff --git a/source/tests/unit/blueprints/case/__init__.py b/source/tests/app/blueprints/rest/__init__.py similarity index 100% rename from source/tests/unit/blueprints/case/__init__.py rename to source/tests/app/blueprints/rest/__init__.py diff --git a/source/tests/unit/blueprints/case/test_case_tasks_routes.py b/source/tests/app/blueprints/rest/test_parsing.py similarity index 57% rename from source/tests/unit/blueprints/case/test_case_tasks_routes.py rename to source/tests/app/blueprints/rest/test_parsing.py index cdc3d7240..f7cc253de 100644 --- a/source/tests/unit/blueprints/case/test_case_tasks_routes.py +++ b/source/tests/app/blueprints/rest/test_parsing.py @@ -1,6 +1,6 @@ # IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net +# Copyright (C) 2025 - 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 @@ -16,21 +16,13 @@ # 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 app import app -from tests.test_helper import TestHelper - -app.testing = True +from app.blueprints.rest.parsing import parse_comma_separated_identifiers -class TestCaseTasksRoutes(TestCase): - def setUp(self) -> None: - self._test_helper = TestHelper() +class TestCaseParsing(TestCase): - def test_case_get_tasks_should_redirect_to_cid_1_if_no_cid_is_provided(self): - self._test_helper.verify_path_without_cid_redirects_correctly( - 'case_tasks.case_tasks', - 'You should be redirected automatically to target URL: /case/tasks?cid=1' - ) + def test_parse_comma_separated_identifiers_should_return_identifiers(self): + result = parse_comma_separated_identifiers('a,b') + self.assertEqual(['a', 'c'], result) diff --git a/source/tests/test_helper.py b/source/tests/test_helper.py deleted file mode 100644 index 9809a7170..000000000 --- a/source/tests/test_helper.py +++ /dev/null @@ -1,50 +0,0 @@ -# IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net -# -# 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 os import environ -from unittest import TestCase - -import re -from flask import url_for -from flask.testing import FlaskClient - -from app import app - - -class TestHelper(TestCase): - @staticmethod - def log_in(test_app: FlaskClient) -> None: - login_page = test_app.get('/login') - - csrf_token = re.search(r'id="csrf_token" name="csrf_token" type="hidden" value="(.*?)"', str(login_page.data)).group(1) - - test_app.post('/login', data=dict(username='administrator', password=environ.get("IRIS_ADM_PASSWORD", ""), csrf_token=csrf_token), follow_redirects=True) - - def verify_path_without_cid_redirects_correctly(self, path: str, assert_string: str): - with app.test_client() as test_app: - self.log_in(test_app) - - result = test_app.get(url_for(path)) - - self.assertEqual(302, result.status_code) - self.assertIn(assert_string, str(result.data)) - - result2 = test_app.get(url_for(path), follow_redirects=True) - - self.assertEqual(200, result2.status_code) diff --git a/source/tests/unit/blueprints/case/test_case_rfiles_routes.py b/source/tests/unit/blueprints/case/test_case_rfiles_routes.py deleted file mode 100644 index 6395e7333..000000000 --- a/source/tests/unit/blueprints/case/test_case_rfiles_routes.py +++ /dev/null @@ -1,36 +0,0 @@ -# IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net -# -# 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 app import app -from tests.test_helper import TestHelper - -app.testing = True - - -class TestCaseRfilesRoutes(TestCase): - def setUp(self) -> None: - self._test_helper = TestHelper() - - def test_case_get_case_rfiles_should_redirect_to_cid_1_if_no_cid_is_provided(self): - self._test_helper.verify_path_without_cid_redirects_correctly( - 'case_rfiles.case_rfile', - 'You should be redirected automatically to target URL: /case/evidences?cid=1' - ) diff --git a/source/tests/unit/blueprints/manage/__init__.py b/source/tests/unit/blueprints/manage/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/source/tests/unit/blueprints/manage/test_manage_modules_routes.py b/source/tests/unit/blueprints/manage/test_manage_modules_routes.py deleted file mode 100644 index e636d3722..000000000 --- a/source/tests/unit/blueprints/manage/test_manage_modules_routes.py +++ /dev/null @@ -1,36 +0,0 @@ -# IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net -# -# 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 app import app -from tests.test_helper import TestHelper - -app.testing = True - - -class TestManageModulesRoutes(TestCase): - def setUp(self) -> None: - self._test_helper = TestHelper() - - def test_case_get_manage_modules_index_should_redirect_to_cid_1_if_no_cid_is_provided(self): - self._test_helper.verify_path_without_cid_redirects_correctly( - 'manage_module.manage_modules_index', - 'You should be redirected automatically to target URL: /manage/modules?cid=1' - ) From c099feff72bfc6194fb7a02a6dc670861caad38b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 10:56:38 +0100 Subject: [PATCH 117/286] Started implementation of GET /api/v2/manage/customers --- .../pages/manage/manage_cases_routes.py | 9 ++---- .../blueprints/pages/manage/manage_users.py | 3 +- .../rest/manage/manage_customers_routes.py | 3 +- .../rest/v2/case_routes/evidences.py | 2 +- .../rest/v2/manage_routes/customers.py | 29 +++++++++++++++---- source/app/business/customers.py | 8 +++++ source/app/datamgmt/client/client_db.py | 18 ++++++++++-- tests/tests_rest_customers.py | 4 +++ 8 files changed, 57 insertions(+), 19 deletions(-) diff --git a/source/app/blueprints/pages/manage/manage_cases_routes.py b/source/app/blueprints/pages/manage/manage_cases_routes.py index 8a2a96725..5e4c486ad 100644 --- a/source/app/blueprints/pages/manage/manage_cases_routes.py +++ b/source/app/blueprints/pages/manage/manage_cases_routes.py @@ -85,15 +85,14 @@ def _details_case(cur_id: int, caseid: int, url_redir: bool) -> Union[str, Respo 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, - is_server_administrator=user_is_server_administrator) + customers = get_client_list(iris_current_user.id, user_is_server_administrator) severities = get_severities_list() protagonists = [r._asdict() for r in get_case_protagonists(cur_id)] form = FlaskForm() - return render_template("modal_case_info_from_case.html", data=res, form=form, protagonists=protagonists, + return render_template('modal_case_info_from_case.html', data=res, form=form, protagonists=protagonists, case_classifications=case_classifications, case_states=dumped_case_states, customers=customers, severities=severities) @@ -119,9 +118,7 @@ def add_case_modal(caseid: int, url_redir: bool): form = AddCaseForm() # Show only clients that the user has access to - client_list = get_client_list(current_user_id=iris_current_user.id, - is_server_administrator=ac_current_user_has_permission( - Permissions.server_administrator)) + client_list = get_client_list(iris_current_user.id, ac_current_user_has_permission(Permissions.server_administrator)) form.case_customer_id.choices = [(c['customer_id'], c['customer_name']) for c in client_list] diff --git a/source/app/blueprints/pages/manage/manage_users.py b/source/app/blueprints/pages/manage/manage_users.py index f73103bf3..49a21a4f7 100644 --- a/source/app/blueprints/pages/manage/manage_users.py +++ b/source/app/blueprints/pages/manage/manage_users.py @@ -103,8 +103,7 @@ def manage_user_customers_modal(cur_id, caseid, url_redir): return response_error("Invalid user ID") user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) - groups = get_client_list(current_user_id=iris_current_user.id, - is_server_administrator=user_is_server_administrator) + groups = get_client_list(iris_current_user.id, user_is_server_administrator) return render_template("modal_manage_user_customers.html", groups=groups, user=user) diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index e410f198a..11bc0c236 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -58,8 +58,7 @@ @ac_api_requires(Permissions.customers_read) def list_customers(): user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) - client_list = get_client_list(current_user_id=iris_current_user.id, - is_server_administrator=user_is_server_administrator) + client_list = get_client_list(iris_current_user.id, user_is_server_administrator) return response_success("", data=client_list) diff --git a/source/app/blueprints/rest/v2/case_routes/evidences.py b/source/app/blueprints/rest/v2/case_routes/evidences.py index 8d40c306c..2bd350f18 100644 --- a/source/app/blueprints/rest/v2/case_routes/evidences.py +++ b/source/app/blueprints/rest/v2/case_routes/evidences.py @@ -159,7 +159,7 @@ def delete(self, case_identifier, identifier): @case_evidences_blueprint.get('') @ac_api_requires() -def get_evidences(case_identifier): +def search_evidences(case_identifier): return evidences_operations.search(case_identifier) diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index 1e430d44a..6ddb146f9 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -24,24 +24,35 @@ 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_paginated from app.blueprints.rest.endpoints import response_api_deleted from app.blueprints.access_controls import ac_api_requires +from app.iris_engine.access_control.utils import ac_current_user_has_permission from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema from app.models.errors import ObjectNotFoundError from app.models.errors import ElementInUseError from app.business.customers import customers_create_with_user +from app.business.customers import customers_filter from app.business.customers import customers_get from app.business.customers import customers_update from app.business.customers import customers_delete from app.blueprints.iris_user import iris_current_user +from app.blueprints.rest.parsing import parse_pagination_parameters -class Customers: +class CustomersOperations: def __init__(self): self._schema = CustomerSchema() + def search(self): + pagination_parameters = parse_pagination_parameters(request) + user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) + customers = customers_filter(iris_current_user, pagination_parameters, user_is_server_administrator) + return response_api_paginated(self._schema, customers) + + def create(self): try: request_data = request.get_json() @@ -88,28 +99,34 @@ def delete(self, identifier): customers_blueprint = Blueprint('customers_rest_v2', __name__, url_prefix='/customers') -customers = Customers() +customers_operations = CustomersOperations() + + +@customers_blueprint.get('') +@ac_api_requires(Permissions.customers_read) +def search_evidences(): + return customers_operations.search() @customers_blueprint.post('') @ac_api_requires(Permissions.customers_write) def create_customer(): - return customers.create() + return customers_operations.create() @customers_blueprint.get('/') @ac_api_requires(Permissions.customers_read) def get_customer(identifier): - return customers.read(identifier) + return customers_operations.read(identifier) @customers_blueprint.put('/') @ac_api_requires(Permissions.customers_write) def put_customer(identifier): - return customers.update(identifier) + return customers_operations.update(identifier) @customers_blueprint.delete('/') @ac_api_requires(Permissions.customers_write) def delete_user(identifier): - return customers.delete(identifier) + return customers_operations.delete(identifier) diff --git a/source/app/business/customers.py b/source/app/business/customers.py index 92c750f83..e3af66667 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -16,15 +16,23 @@ # 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_sqlalchemy.pagination import Pagination + 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 create_customer +from app.datamgmt.client.client_db import get_paginated_customers from app.datamgmt.client.client_db import get_customer from app.datamgmt.client.client_db import get_customer_by_name from app.datamgmt.client.client_db import update_customer from app.datamgmt.client.client_db import delete_client from app.models.errors import ObjectNotFoundError +from app.models.pagination_parameters import PaginationParameters + + +def customers_filter(user, pagination_parameters: PaginationParameters, is_server_administrator) -> Pagination: + return get_paginated_customers(pagination_parameters, user.id, is_server_administrator) # TODO maybe this method should be removed and always create a customer with at least a user diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index 388f920be..acb22adaf 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -20,6 +20,7 @@ from sqlalchemy import and_ from typing import List from typing import Optional +from flask_sqlalchemy.pagination import Pagination from app import db from app.models.errors import ElementInUseError @@ -28,6 +29,8 @@ from app.models.models import Contact from app.models.authorization import User from app.models.authorization import UserClient +from app.models.pagination_parameters import PaginationParameters +from app.datamgmt.filtering import paginate # TODO most probably rename into add (or save?) and make more generic @@ -46,8 +49,7 @@ def update_customer(): db.session.commit() -def get_client_list(current_user_id: int = None, - is_server_administrator: bool = False) -> List[dict]: +def get_client_list(current_user_id: int, is_server_administrator: bool) -> List[dict]: if not is_server_administrator: filter = and_( Client.client_id == UserClient.client_id, @@ -72,6 +74,18 @@ def get_client_list(current_user_id: int = None, return output +def get_paginated_customers(pagination_parameters: PaginationParameters, current_user_identifier: int, is_server_administrator: bool) -> Pagination: + query = Client.query + + if not is_server_administrator: + query = query.filter( + Client.client_id == UserClient.client_id, + UserClient.user_id == current_user_identifier + ) + + return paginate(Client, pagination_parameters, query) + + def get_customer(client_id: int) -> Optional[Client]: return Client.query.filter(Client.client_id == client_id).first() diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index af283ddbc..b66544585 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -155,3 +155,7 @@ def test_delete_customer_should_return_400_when_referenced_in_a_case(self): response = self._subject.delete(f'/api/v2/manage/customers/{identifier}') self.assertEqual(400, response.status_code) + + def test_search_customers_should_return_200(self): + response = self._subject.get(f'/api/v2/manage/customers') + self.assertEqual(200, response.status_code) From 4ae9af9abc3dd5acee6bda728b14ec2473e3ae97 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:12:04 +0100 Subject: [PATCH 118/286] Fixed ruff warning --- source/app/blueprints/rest/v2/manage_routes/customers.py | 1 - 1 file changed, 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 6ddb146f9..e8b53c933 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -52,7 +52,6 @@ def search(self): customers = customers_filter(iris_current_user, pagination_parameters, user_is_server_administrator) return response_api_paginated(self._schema, customers) - def create(self): try: request_data = request.get_json() From 7eb8971a1c6f3a003fa2c7372fda58a2480bd7c3 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:22:06 +0100 Subject: [PATCH 119/286] Moved method into API layer --- source/app/blueprints/access_controls.py | 8 ++++++++ source/app/blueprints/pages/manage/manage_cases_routes.py | 5 +++-- source/app/blueprints/pages/manage/manage_users.py | 3 +-- source/app/blueprints/rest/profile_routes.py | 2 +- source/app/blueprints/rest/v2/manage_routes/customers.py | 2 +- source/app/datamgmt/alerts/alerts_db.py | 5 +++-- source/app/iris_engine/access_control/utils.py | 8 -------- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index f8495f564..3c97e62a8 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -52,6 +52,7 @@ from app.iris_engine.access_control.utils import ac_get_effective_permissions_of_user from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions +from app.models.authorization import ac_flag_match_mask from app.models.authorization import CaseAccessLevel @@ -582,3 +583,10 @@ def is_authentication_ldap(): def ac_fast_check_current_user_has_case_access(cid, access_level): return ac_fast_check_user_has_case_access(iris_current_user.id, cid, access_level) + + +def ac_current_user_has_permission(permission): + """ + Return True if current user has permission + """ + return ac_flag_match_mask(session['permissions'], permission.value) diff --git a/source/app/blueprints/pages/manage/manage_cases_routes.py b/source/app/blueprints/pages/manage/manage_cases_routes.py index 5e4c486ad..45de533a8 100644 --- a/source/app/blueprints/pages/manage/manage_cases_routes.py +++ b/source/app/blueprints/pages/manage/manage_cases_routes.py @@ -34,11 +34,12 @@ from app.datamgmt.manage.manage_cases_db import get_case_protagonists from app.datamgmt.manage.manage_common import get_severities_list from app.forms import AddCaseForm -from app.iris_engine.access_control.utils import ac_current_user_has_permission from app.models.authorization import CaseAccessLevel from app.models.authorization import Permissions from app.schema.marshables import CaseDetailsSchema -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_api_return_access_denied +from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access +from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.access_controls import ac_requires from app.blueprints.responses import response_error from app.schema.marshables import CaseStateSchema diff --git a/source/app/blueprints/pages/manage/manage_users.py b/source/app/blueprints/pages/manage/manage_users.py index 49a21a4f7..4d18141f1 100644 --- a/source/app/blueprints/pages/manage/manage_users.py +++ b/source/app/blueprints/pages/manage/manage_users.py @@ -30,9 +30,8 @@ from app.datamgmt.manage.manage_users_db import get_user_effective_permissions from app.forms import AddUserForm from app.iris_engine.access_control.utils import ac_get_all_access_level -from app.iris_engine.access_control.utils import ac_current_user_has_permission from app.models.authorization import Permissions -from app.blueprints.access_controls import ac_requires +from app.blueprints.access_controls import ac_requires, ac_current_user_has_permission from app.blueprints.responses import response_error manage_users_blueprint = Blueprint('manage_users', __name__, template_folder='templates') diff --git a/source/app/blueprints/rest/profile_routes.py b/source/app/blueprints/rest/profile_routes.py index d85f9f927..a0375663e 100644 --- a/source/app/blueprints/rest/profile_routes.py +++ b/source/app/blueprints/rest/profile_routes.py @@ -27,7 +27,6 @@ from app.datamgmt.manage.manage_users_db import get_user from app.datamgmt.manage.manage_users_db import get_user_primary_org from app.datamgmt.manage.manage_users_db import update_user -from app.iris_engine.access_control.utils import ac_current_user_has_permission from app.iris_engine.access_control.utils import ac_get_effective_permissions_of_user from app.iris_engine.access_control.utils import ac_recompute_effective_ac from app.iris_engine.utils.tracker import track_activity @@ -36,6 +35,7 @@ from app.schema.marshables import UserSchema from app.schema.marshables import BasicUserSchema from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.responses import response_error from app.blueprints.responses import response_success from app.blueprints.rest.endpoints import endpoint_removed diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index e8b53c933..f921e38b8 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -27,7 +27,7 @@ from app.blueprints.rest.endpoints import response_api_paginated from app.blueprints.rest.endpoints import response_api_deleted from app.blueprints.access_controls import ac_api_requires -from app.iris_engine.access_control.utils import ac_current_user_has_permission +from app.blueprints.access_controls import ac_current_user_has_permission from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema from app.models.errors import ObjectNotFoundError diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 67b701109..4f25f8f67 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -20,7 +20,8 @@ import json from datetime import datetime from datetime import timedelta -from typing import List, Optional +from typing import List +from typing import Optional from typing import Tuple from sqlalchemy import desc @@ -51,7 +52,7 @@ from app.datamgmt.manage.manage_case_templates_db import case_template_post_modifier from app.datamgmt.states import update_timeline_state from app.blueprints.iris_user import iris_current_user -from app.iris_engine.access_control.utils import ac_current_user_has_permission +from app.blueprints.access_controls import ac_current_user_has_permission from app.iris_engine.utils.common import parse_bf_date_format from app.models.cases import Cases from app.models.models import EventCategory diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index 6e7f132f4..88d4842c2 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -1,4 +1,3 @@ -from flask import session from sqlalchemy import and_ from app import db @@ -842,10 +841,3 @@ def ac_user_has_permission(user, permission): Return True if user has permission """ return ac_flag_match_mask(ac_get_effective_permissions_of_user(user), permission.value) - - -def ac_current_user_has_permission(permission): - """ - Return True if current user has permission - """ - return ac_flag_match_mask(session['permissions'], permission.value) From 704bb0749d0c8d254b95521e0327e921753db57e Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:51:37 +0100 Subject: [PATCH 120/286] app.datamgmt.alerts.alerts_db should not import app.blueprints.access_controls --- pyproject.toml | 9 +- source/app/blueprints/rest/alerts_routes.py | 53 ++++++------ source/app/blueprints/rest/v2/alerts.py | 7 +- source/app/business/alerts.py | 55 ++++++------ source/app/datamgmt/alerts/alerts_db.py | 93 +++++++-------------- 5 files changed, 99 insertions(+), 118 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d44173337..099b1840d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,14 @@ allow_indirect_imports = true name = "Do not import API layer from the persistence layer" type = "forbidden" source_modules = "app.datamgmt.dashboard" -forbidden_modules = "app.blueprints.iris_user" +forbidden_modules = "app.blueprints" +allow_indirect_imports = true + +[[tool.importlinter.contracts]] +name = "Do not import API layer from the persistence layer (access_controls)" +type = "forbidden" +source_modules = "app.datamgmt.alerts.alerts_db" +forbidden_modules = "app.blueprints.access_controls" allow_indirect_imports = true [[tool.importlinter.contracts]] diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 0f802205a..4b7b92985 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -57,6 +57,7 @@ from app.schema.marshables import IocSchema from app.schema.marshables import CommentSchema from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.responses import response_error from app.util import add_obj_history_entry from app.blueprints.responses import response_success @@ -137,31 +138,35 @@ def alerts_list_route() -> Response: fields = None try: + user_identifier_filter = iris_current_user.id + if ac_current_user_has_permission(Permissions.server_administrator): + user_identifier_filter = None + filtered_alerts = get_filtered_alerts( - start_date=request.args.get('creation_start_date'), - end_date=request.args.get('creation_end_date'), - source_start_date=request.args.get('source_start_date'), - source_end_date=request.args.get('source_end_date'), - source_reference=request.args.get('source_reference'), - title=request.args.get('alert_title'), - description=request.args.get('alert_description'), - status=request.args.get('alert_status_id', type=int), - severity=request.args.get('alert_severity_id', type=int), - owner=request.args.get('alert_owner_id', type=int), - source=request.args.get('alert_source'), - tags=request.args.get('alert_tags'), - classification=request.args.get('alert_classification_id', type=int), - client=request.args.get('alert_customer_id'), - case_id=request.args.get('case_id', type=int), - alert_ids=alert_ids, - page=page, - per_page=per_page, - sort=request.args.get('sort', 'desc', type=str), - custom_conditions=request.args.get('custom_conditions'), - assets=alert_assets, - iocs=alert_iocs, - resolution_status=request.args.get('alert_resolution_id', type=int), - current_user_id=iris_current_user.id + request.args.get('creation_start_date'), + request.args.get('creation_end_date'), + request.args.get('source_start_date'), + request.args.get('source_end_date'), + request.args.get('alert_title'), + request.args.get('alert_description'), + request.args.get('alert_status_id', type=int), + request.args.get('alert_severity_id', type=int), + request.args.get('alert_owner_id', type=int), + request.args.get('alert_source'), + request.args.get('alert_tags'), + request.args.get('case_id', type=int), + request.args.get('alert_customer_id', type=int), + request.args.get('alert_classification_id', type=int), + alert_ids, + alert_assets, + alert_iocs, + request.args.get('alert_resolution_id', type=int), + page, + per_page, + request.args.get('sort', 'desc', type=str), + user_identifier_filter, + request.args.get('source_reference'), + request.args.get('custom_conditions') ) except Exception as e: diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index 9c3ab98ac..b9a724680 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -22,6 +22,7 @@ from marshmallow.exceptions import ValidationError from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.rest.endpoints import response_api_success from app.blueprints.rest.endpoints import response_api_paginated from app.blueprints.rest.endpoints import response_api_error @@ -92,8 +93,11 @@ def search(self): else: fields = None + user_identifier_filter = iris_current_user.id + if ac_current_user_has_permission(Permissions.server_administrator): + user_identifier_filter = None + filtered_alerts = alerts_search( - iris_current_user.id, request.args.get('creation_start_date'), request.args.get('creation_end_date'), request.args.get('source_start_date'), @@ -114,6 +118,7 @@ def search(self): request.args.get('alert_resolution_id', type=int), request.args.get('source_reference'), request.args.get('custom_conditions'), + user_identifier_filter, page, per_page, request.args.get('sort') diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index e23ef8d07..b80e84739 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -39,34 +39,35 @@ from app.datamgmt.manage.manage_access_control_db import user_has_client_access -def alerts_search(user_identifier, start_date, end_date, source_start_date, source_end_date, title, description, - status, severity, owner, source, tags, case_identifier, customer, classification, alert_identifiers, - assets, iocs, resolution_status, source_reference, custom_conditions, page, per_page, sort): +def alerts_search(start_date, end_date, source_start_date, source_end_date, title, description, + status, severity, owner, source, tags, case_identifier, customer_identifier, classification, alert_identifiers, + assets, iocs, resolution_status, source_reference, custom_conditions, user_identifier_filter, page, per_page, sort): + return get_filtered_alerts( - start_date=start_date, - end_date=end_date, - source_start_date=source_start_date, - source_end_date=source_end_date, - title=title, - description=description, - status=status, - severity=severity, - owner=owner, - source=source, - tags=tags, - case_id=case_identifier, - client=customer, - classification=classification, - alert_ids=alert_identifiers, - assets=assets, - iocs=iocs, - resolution_status=resolution_status, - page=page, - per_page=per_page, - sort=sort, - current_user_id=user_identifier, - source_reference=source_reference, - custom_conditions=custom_conditions + start_date, + end_date, + source_start_date, + source_end_date, + title, + description, + status, + severity, + owner, + source, + tags, + case_identifier, + customer_identifier, + classification, + alert_identifiers, + assets, + iocs, + resolution_status, + page, + per_page, + sort, + user_identifier_filter, + source_reference, + custom_conditions ) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 4f25f8f67..5cd2ad2b5 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -52,7 +52,6 @@ from app.datamgmt.manage.manage_case_templates_db import case_template_post_modifier from app.datamgmt.states import update_timeline_state from app.blueprints.iris_user import iris_current_user -from app.blueprints.access_controls import ac_current_user_has_permission from app.iris_engine.utils.common import parse_bf_date_format from app.models.cases import Cases from app.models.models import EventCategory @@ -71,7 +70,6 @@ from app.models.alerts import AlertResolutionStatus from app.models.alerts import AlertSimilarity from app.models.alerts import Severity -from app.models.authorization import Permissions from app.models.authorization import User from app.schema.marshables import EventSchema from app.util import add_obj_history_entry @@ -98,67 +96,32 @@ def db_list_all_alerts(): def get_filtered_alerts( - start_date: str = None, - end_date: str = None, - source_start_date: str = None, - source_end_date: str = None, - title: str = None, - description: str = None, - status: int = None, - severity: int = None, - owner: int = None, - source: str = None, - tags: str = None, - case_id: int = None, - client: int = None, - classification: int = None, - alert_ids: List[int] = None, - assets: List[str] = None, - iocs: List[str] = None, - resolution_status: List[int] = None, - logical_operator: str = 'and', # Logical operator: 'and', 'or', 'not' - page: int = 1, - per_page: int = 10, - sort: str = 'desc', - current_user_id: int = None, - source_reference=None, - custom_conditions: List[dict] = None) -> Pagination: - """ - Get a list of alerts that match the given filter conditions - - args: - start_date (datetime): The start date of the alert creation time - end_date (datetime): The end date of the alert creation time - title (str): The title of the alert - description (str): The description of the alert - status (str): The status of the alert - severity (str): The severity of the alert - owner (str): The owner of the alert - source (str): The source of the alert - tags (str): The tags of the alert - case_id (int): The case id of the alert - client (int): The client id of the alert - classification (int): The classification id of the alert - alert_ids (int): The alert ids - assets (list): The assets of the alert - iocs (list): The iocs of the alert - resolution_status (list): The resolution status of the alert - logical_operator (str): Logical operator to combine conditions ('and', 'or', 'not') - page (int): The page number - per_page (int): The number of alerts per page - sort (str): The sort order - current_user_id (int): The ID of the current user - source_reference (str): Alert source reference - custom_conditions (list): Custom conditions to be applied (e.g., NOT client AND owner_id in [1,2,3]) - - returns: - list: A list of alerts that match the given filter conditions - ... - fields (List[str]): The list of fields to include in the output - - returns: - dict: Dictionary with pagination info and list of serialized alerts - """ + start_date: str, + end_date: str, + source_start_date: str, + source_end_date: str, + title: str, + description: str, + status: int, + severity: int, + owner: int, + source: str, + tags: str, + case_id: int, + client: int, + classification: int, + alert_ids: List[int], + assets: List[str], + iocs: List[str], + resolution_status: List[int], + page: int, + per_page: int, + sort: str, + user_identifier_filter: int | None, + source_reference, + custom_conditions: List[dict], + logical_operator: str = 'and' # Logical operator: 'and', 'or', 'not' + ) -> Pagination: conditions = [] if start_date is not None and end_date is not None: @@ -227,8 +190,8 @@ def get_filtered_alerts( if isinstance(iocs, list): conditions.append(Alert.iocs.any(Ioc.ioc_value.in_(iocs))) - if current_user_id is not None and not ac_current_user_has_permission(Permissions.server_administrator): - clients_filters = get_user_clients_id(current_user_id) + if user_identifier_filter is not None: + clients_filters = get_user_clients_id(user_identifier) if clients_filters is not None: conditions.append(Alert.alert_customer_id.in_(clients_filters)) From cc14de1a0e104ae7406fbd28af90c57cf62191f3 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:55:48 +0100 Subject: [PATCH 121/286] Fixed ruff warning --- source/app/datamgmt/alerts/alerts_db.py | 11 +++++------ tests/tests_rest_customers.py | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 5cd2ad2b5..1a59f2506 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -117,10 +117,9 @@ def get_filtered_alerts( page: int, per_page: int, sort: str, - user_identifier_filter: int | None, + user_identifier: int | None, source_reference, - custom_conditions: List[dict], - logical_operator: str = 'and' # Logical operator: 'and', 'or', 'not' + custom_conditions: List[dict] ) -> Pagination: conditions = [] @@ -190,7 +189,7 @@ def get_filtered_alerts( if isinstance(iocs, list): conditions.append(Alert.iocs.any(Ioc.ioc_value.in_(iocs))) - if user_identifier_filter is not None: + if user_identifier is not None: clients_filters = get_user_clients_id(user_identifier) if clients_filters is not None: conditions.append(Alert.alert_customer_id.in_(clients_filters)) @@ -218,8 +217,8 @@ def get_filtered_alerts( query, conditions_tmp = apply_custom_conditions(query, Alert, custom_conditions, relationship_model_map) conditions.extend(conditions_tmp) - # Combine conditions - combined_conditions = combine_conditions(conditions, logical_operator) + # Combine conditions + combined_conditions = combine_conditions(conditions, 'and') order_func = desc if sort == "desc" else asc diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index b66544585..cebf410d4 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -157,5 +157,5 @@ def test_delete_customer_should_return_400_when_referenced_in_a_case(self): self.assertEqual(400, response.status_code) def test_search_customers_should_return_200(self): - response = self._subject.get(f'/api/v2/manage/customers') + response = self._subject.get('/api/v2/manage/customers') self.assertEqual(200, response.status_code) From 221ea9dd7c3fe93856eb94409fdf2c75902c0448 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:16:46 +0100 Subject: [PATCH 122/286] Removed seemingly dead method --- .vulture.ignore | 1 - source/app/iris_engine/access_control/utils.py | 7 ------- 2 files changed, 8 deletions(-) diff --git a/.vulture.ignore b/.vulture.ignore index cf594ad7c..053d1f142 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -318,7 +318,6 @@ org_type # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/for case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:148) CaseNoteForm # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:223) ac_get_effective_permissions_from_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/access_control/utils.py:138) -ac_user_has_permission # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/access_control/utils.py:840) check_module_compatibility # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:46) get_mod_config_by_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:234) register_hook # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:323) diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index 88d4842c2..ed2237f4d 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -834,10 +834,3 @@ def ac_access_level_to_list(access_level): }) return access_levels - - -def ac_user_has_permission(user, permission): - """ - Return True if user has permission - """ - return ac_flag_match_mask(ac_get_effective_permissions_of_user(user), permission.value) From dcc483061e86a398364d87ba8773b0b571495014 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:25:58 +0100 Subject: [PATCH 123/286] Factored code somewhat --- source/app/__init__.py | 8 ++++++-- source/app/models/authorization.py | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/source/app/__init__.py b/source/app/__init__.py index eec3917fd..9a46bf0db 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -39,6 +39,7 @@ from app.iris_engine.tasker.celery import set_celery_flask_context from app.iris_engine.access_control.oidc_handler import get_oidc_client from app.jinja_filters import register_jinja_filters +from app.models.authorization import ac_flag_match_mask class ReverseProxied(object): @@ -75,10 +76,13 @@ def ac_current_user_has_permission(*permissions): """ Return True if current user has permission """ + if not 'permissions' in session: + return False + + current_user_permissions = session['permissions'] for permission in permissions: - if ('permissions' in session and - session['permissions'] & permission.value == permission.value): + if ac_flag_match_mask(current_user_permissions, permission.value): return True return False diff --git a/source/app/models/authorization.py b/source/app/models/authorization.py index 51a831e1f..165516236 100644 --- a/source/app/models/authorization.py +++ b/source/app/models/authorization.py @@ -20,7 +20,8 @@ import secrets import uuid from flask_login import UserMixin -from sqlalchemy import BigInteger, JSON +from sqlalchemy import BigInteger +from sqlalchemy import JSON from sqlalchemy import Boolean from sqlalchemy import Column from sqlalchemy import ForeignKey From 76acb33ec7df16bde58fa49c26748851931faec1 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:07:04 +0100 Subject: [PATCH 124/286] Removed cycle --- source/app/__init__.py | 19 ++++++------------ source/app/db.py | 31 ++++++++++++++++++++++++++++++ source/app/models/authorization.py | 2 +- 3 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 source/app/db.py diff --git a/source/app/__init__.py b/source/app/__init__.py index 9a46bf0db..42c86a0ae 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -16,10 +16,9 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import collections -import json import os -from flask import Flask, g +from flask import Flask +from flask import g from flask import session from flask_bcrypt import Bcrypt from flask_caching import Cache @@ -27,9 +26,8 @@ from flask_login import LoginManager from flask_marshmallow import Marshmallow -from flask_socketio import SocketIO, Namespace -from flask_sqlalchemy import SQLAlchemy -from functools import partial +from flask_socketio import SocketIO +from flask_socketio import Namespace from werkzeug.middleware.proxy_fix import ProxyFix @@ -40,6 +38,7 @@ from app.iris_engine.access_control.oidc_handler import get_oidc_client from app.jinja_filters import register_jinja_filters from app.models.authorization import ac_flag_match_mask +from app.db import db class ReverseProxied(object): @@ -60,12 +59,6 @@ class AlertsNamespace(Namespace): APP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TEMPLATE_PATH = os.path.join(APP_PATH, 'templates/') -SQLALCHEMY_ENGINE_OPTIONS = { - "json_deserializer": partial(json.loads, object_pairs_hook=collections.OrderedDict), - "pool_pre_ping": True -} - -db = SQLAlchemy(engine_options=SQLALCHEMY_ENGINE_OPTIONS) # flask-sqlalchemy bc = Bcrypt() # flask-bcrypt ma = Marshmallow() celery = make_celery(__name__) @@ -76,7 +69,7 @@ def ac_current_user_has_permission(*permissions): """ Return True if current user has permission """ - if not 'permissions' in session: + if 'permissions' not in session: return False current_user_permissions = session['permissions'] diff --git a/source/app/db.py b/source/app/db.py new file mode 100644 index 000000000..d7171a351 --- /dev/null +++ b/source/app/db.py @@ -0,0 +1,31 @@ +# IRIS Source Code +# Copyright (C) 2025 - Airbus CyberSecurity (SAS) +# ir@cyberactionlab.net +# +# 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 functools import partial +import json +import collections + +from flask_sqlalchemy import SQLAlchemy + + +SQLALCHEMY_ENGINE_OPTIONS = { + "json_deserializer": partial(json.loads, object_pairs_hook=collections.OrderedDict), + "pool_pre_ping": True +} + +db = SQLAlchemy(engine_options=SQLALCHEMY_ENGINE_OPTIONS) # flask-sqlalchemy diff --git a/source/app/models/authorization.py b/source/app/models/authorization.py index 165516236..62556b936 100644 --- a/source/app/models/authorization.py +++ b/source/app/models/authorization.py @@ -33,7 +33,7 @@ from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship -from app import db +from app.db import db class CaseAccessLevel(enum.Enum): From 524427901856d8e574d4161c76bbf9be83b26041 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:07:33 +0100 Subject: [PATCH 125/286] Use method from the business layer --- source/app/blueprints/access_controls.py | 6 ++-- source/app/blueprints/graphql/cases.py | 5 ++- .../blueprints/pages/alerts/alerts_routes.py | 4 +-- source/app/blueprints/rest/alerts_routes.py | 36 +++++++++---------- .../rest/manage/manage_cases_routes.py | 4 +-- source/app/business/alerts.py | 4 +-- 6 files changed, 29 insertions(+), 30 deletions(-) diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index 3c97e62a8..e171fee11 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -45,7 +45,7 @@ from app.business.auth import validate_auth_token from app.business.auth import update_session_current_case from app.datamgmt.case.case_db import get_case -from app.datamgmt.manage.manage_access_control_db import user_has_client_access +from app.business.access_controls import access_controls_user_has_customer_access from app.datamgmt.manage.manage_users_db import get_user from app.blueprints.iris_user import iris_current_user from app.business.access_controls import ac_fast_check_user_has_case_access @@ -389,7 +389,7 @@ def inner_wrap(f): @wraps(f) def wrap(*args, **kwargs): client_id = kwargs.get('client_id') - if not user_has_client_access(iris_current_user.id, client_id): + if not access_controls_user_has_customer_access(iris_current_user.id, client_id): return _ac_return_access_denied() return f(*args, **kwargs) @@ -439,7 +439,7 @@ def inner_wrap(f): @wraps(f) def wrap(*args, **kwargs): client_id = kwargs.get('client_id') - if not user_has_client_access(iris_current_user.id, client_id): + if not access_controls_user_has_customer_access(iris_current_user.id, client_id): return response_error("Permission denied", status=403) return f(*args, **kwargs) diff --git a/source/app/blueprints/graphql/cases.py b/source/app/blueprints/graphql/cases.py index a82015819..33339959e 100644 --- a/source/app/blueprints/graphql/cases.py +++ b/source/app/blueprints/graphql/cases.py @@ -42,8 +42,7 @@ from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook from app.schema.marshables import CaseSchema from app.blueprints.iris_user import iris_current_user -from app.datamgmt.manage.manage_access_control_db import user_has_client_access - +from app.business.access_controls import access_controls_user_has_customer_access from app.blueprints.graphql.iocs import IOCConnection @@ -185,7 +184,7 @@ def mutate(root, info, case_id, name=None, soc_id=None, classification_id=None, # If user tries to update the customer, check if the user has access to the new customer if request.get('case_customer') and request.get('case_customer') != case.client_id: - if not user_has_client_access(iris_current_user.id, request.get('case_customer')): + if not access_controls_user_has_customer_access(iris_current_user.id, request.get('case_customer')): raise BusinessProcessingError('Invalid customer ID. Permission denied.') if 'case_name' in request: diff --git a/source/app/blueprints/pages/alerts/alerts_routes.py b/source/app/blueprints/pages/alerts/alerts_routes.py index 1689eeb06..297aec97e 100644 --- a/source/app/blueprints/pages/alerts/alerts_routes.py +++ b/source/app/blueprints/pages/alerts/alerts_routes.py @@ -25,7 +25,7 @@ from werkzeug import Response from app.datamgmt.alerts.alerts_db import get_alert_by_id -from app.datamgmt.manage.manage_access_control_db import user_has_client_access +from app.business.access_controls import access_controls_user_has_customer_access from app.models.authorization import Permissions from app.blueprints.responses import response_error from app.blueprints.access_controls import ac_requires @@ -78,7 +78,7 @@ def alert_comment_modal(cur_id, caseid, url_redir): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) return render_template("modal_conversation.html", element_id=cur_id, element_type='alerts', diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 4b7b92985..49cbacb39 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -45,7 +45,7 @@ from app.datamgmt.alerts.alerts_db import create_case_from_alerts from app.datamgmt.case.case_db import get_case 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.business.access_controls import access_controls_user_has_customer_access from app.iris_engine.access_control.utils import ac_set_new_case_access from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity @@ -215,7 +215,7 @@ def alerts_add_route() -> Response: alert = _load(request_data) result = alerts_create(alert, iocs, assets) - if not user_has_client_access(iris_current_user.id, result.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, result.alert_customer_id): return response_error('User not entitled to create alerts for the client') alert_schema = AlertSchema() return response_success('Alert added', data=alert_schema.dump(result)) @@ -249,7 +249,7 @@ def alerts_get_route(alert_id) -> Response: # Return the alert as JSON if alert is None: return response_error('Alert not found') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('Alert not found') alert_dump = alert_schema.dump(alert) @@ -281,7 +281,7 @@ def alerts_similarities_route(alert_id) -> Response: # Return the alert as JSON if alert is None: return response_error('Alert not found') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('Alert not found') open_alerts = request.args.get('open-alerts', 'false').lower() == 'true' @@ -325,7 +325,7 @@ def alerts_update_route(alert_id) -> Response: alert = get_alert_by_id(alert_id) if not alert: return response_error('Alert not found') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) alert_schema = AlertSchema() @@ -441,7 +441,7 @@ def alerts_batch_update_route() -> Response: activity_data.append(f"\"{key}\"") # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) if alert.alert_owner_id is None: @@ -501,7 +501,7 @@ def alerts_batch_delete_route() -> Response: if not alert: return response_error(f'Alert with ID {alert_id} not found') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to delete alerts for the client', status=403) success, logs = delete_alerts(alert_ids) @@ -538,7 +538,7 @@ def alerts_delete_route(alert_id) -> Response: try: # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to delete alerts for the client', status=403) # Delete the case association @@ -596,7 +596,7 @@ def alerts_escalate_route(alert_id) -> Response: try: # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to escalate alerts for the client', status=403) # Escalate the alert to a case @@ -671,7 +671,7 @@ def alerts_merge_route(alert_id) -> Response: case_tags = data.get('case_tags') try: # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to merge alerts for the client', status=403) # Check if the user has access to the case @@ -731,7 +731,7 @@ def alerts_unmerge_route(alert_id) -> Response: try: # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to unmerge alerts for the client', status=403) # Check if the user has access to the case @@ -804,7 +804,7 @@ def alerts_batch_merge_route() -> Response: continue # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to merge alerts for the client', status=403) alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id @@ -872,7 +872,7 @@ def alerts_batch_escalate_route() -> Response: continue # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to escalate alerts for the client', status=403) alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id @@ -928,7 +928,7 @@ def alert_comments_get(alert_id): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) alert_comments = get_alert_comments(alert_id) @@ -957,7 +957,7 @@ def alert_comment_delete(alert_id, com_id): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) success, msg = delete_alert_comment(comment_id=com_id, alert_id=alert_id) @@ -991,7 +991,7 @@ def alert_comment_get(alert_id, com_id): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) comment = get_alert_comment(alert_id, com_id) @@ -1019,7 +1019,7 @@ def alert_comment_edit(alert_id, com_id): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) return case_comment_update(com_id, 'events', None) @@ -1045,7 +1045,7 @@ def case_comment_add(alert_id): if not alert: return response_error('Invalid alert ID') # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) comment_schema = CommentSchema() diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index 4c7a52d1c..a2675d770 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -62,7 +62,7 @@ from app.business.cases import cases_get_by_identifier from app.models.errors import BusinessProcessingError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook -from app.datamgmt.manage.manage_access_control_db import user_has_client_access +from app.business.access_controls import access_controls_user_has_customer_access manage_cases_rest_blueprint = Blueprint('manage_case_rest', __name__) @@ -281,7 +281,7 @@ def update_case_info(identifier): request_data = request.get_json() # If user tries to update the customer, check if the user has access to the new customer if request_data.get('case_customer') and request_data.get('case_customer') != case.client_id: - if not user_has_client_access(iris_current_user.id, request_data.get('case_customer')): + if not access_controls_user_has_customer_access(iris_current_user.id, request_data.get('case_customer')): raise BusinessProcessingError('Invalid customer ID. Permission denied.') if 'case_name' in request_data: diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index b80e84739..19c268887 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -36,7 +36,7 @@ from app.iris_engine.utils.tracker import track_activity from app.util import add_obj_history_entry from app.models.errors import ObjectNotFoundError -from app.datamgmt.manage.manage_access_control_db import user_has_client_access +from app.business.access_controls import access_controls_user_has_customer_access def alerts_search(start_date, end_date, source_start_date, source_end_date, title, description, @@ -100,7 +100,7 @@ def _get(user, identifier) -> Optional[Alert]: alert = get_alert_by_id(identifier) if not alert: return None - if not user_has_client_access(user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(user.id, alert.alert_customer_id): return None return alert From 0f4319e8d5cb596305a413365896c71a2aab6cb2 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:21:16 +0100 Subject: [PATCH 126/286] Use method from the API layer --- source/app/blueprints/access_controls.py | 8 +++-- source/app/blueprints/graphql/cases.py | 4 +-- .../blueprints/pages/alerts/alerts_routes.py | 6 ++-- source/app/blueprints/rest/alerts_routes.py | 36 +++++++++---------- .../rest/manage/manage_cases_routes.py | 7 ++-- source/app/blueprints/rest/v2/alerts.py | 4 +-- source/app/blueprints/rest/v2/cases.py | 4 +-- source/app/business/access_controls.py | 5 +++ source/app/business/alerts.py | 4 +-- .../manage/manage_access_control_db.py | 3 -- 10 files changed, 43 insertions(+), 38 deletions(-) diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index e171fee11..0cd4ef760 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -389,7 +389,7 @@ def inner_wrap(f): @wraps(f) def wrap(*args, **kwargs): client_id = kwargs.get('client_id') - if not access_controls_user_has_customer_access(iris_current_user.id, client_id): + if not ac_current_user_has_customer_access(client_id): return _ac_return_access_denied() return f(*args, **kwargs) @@ -439,7 +439,7 @@ def inner_wrap(f): @wraps(f) def wrap(*args, **kwargs): client_id = kwargs.get('client_id') - if not access_controls_user_has_customer_access(iris_current_user.id, client_id): + if not ac_current_user_has_customer_access(client_id): return response_error("Permission denied", status=403) return f(*args, **kwargs) @@ -590,3 +590,7 @@ def ac_current_user_has_permission(permission): Return True if current user has permission """ return ac_flag_match_mask(session['permissions'], permission.value) + + +def ac_current_user_has_customer_access(customer_identifier): + return access_controls_user_has_customer_access(iris_current_user.id, customer_identifier) diff --git a/source/app/blueprints/graphql/cases.py b/source/app/blueprints/graphql/cases.py index 33339959e..20c6c39dd 100644 --- a/source/app/blueprints/graphql/cases.py +++ b/source/app/blueprints/graphql/cases.py @@ -27,6 +27,7 @@ from graphene import Float from graphene import String +from app.blueprints.access_controls import ac_current_user_has_customer_access from app.models.cases import Cases from app.models.authorization import Permissions from app.models.authorization import CaseAccessLevel @@ -42,7 +43,6 @@ from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook from app.schema.marshables import CaseSchema from app.blueprints.iris_user import iris_current_user -from app.business.access_controls import access_controls_user_has_customer_access from app.blueprints.graphql.iocs import IOCConnection @@ -184,7 +184,7 @@ def mutate(root, info, case_id, name=None, soc_id=None, classification_id=None, # If user tries to update the customer, check if the user has access to the new customer if request.get('case_customer') and request.get('case_customer') != case.client_id: - if not access_controls_user_has_customer_access(iris_current_user.id, request.get('case_customer')): + if not ac_current_user_has_customer_access(request.get('case_customer')): raise BusinessProcessingError('Invalid customer ID. Permission denied.') if 'case_name' in request: diff --git a/source/app/blueprints/pages/alerts/alerts_routes.py b/source/app/blueprints/pages/alerts/alerts_routes.py index 297aec97e..315df4a8f 100644 --- a/source/app/blueprints/pages/alerts/alerts_routes.py +++ b/source/app/blueprints/pages/alerts/alerts_routes.py @@ -25,11 +25,9 @@ from werkzeug import Response from app.datamgmt.alerts.alerts_db import get_alert_by_id -from app.business.access_controls import access_controls_user_has_customer_access from app.models.authorization import Permissions from app.blueprints.responses import response_error -from app.blueprints.access_controls import ac_requires -from app.blueprints.iris_user import iris_current_user +from app.blueprints.access_controls import ac_requires, ac_current_user_has_customer_access alerts_blueprint = Blueprint( 'alerts', @@ -78,7 +76,7 @@ def alert_comment_modal(cur_id, caseid, url_redir): if not alert: return response_error('Invalid alert ID') - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) return render_template("modal_conversation.html", element_id=cur_id, element_type='alerts', diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 49cbacb39..8a3fe0737 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -45,7 +45,6 @@ from app.datamgmt.alerts.alerts_db import create_case_from_alerts from app.datamgmt.case.case_db import get_case from app.datamgmt.manage.manage_access_control_db import check_ua_case_client -from app.business.access_controls import access_controls_user_has_customer_access from app.iris_engine.access_control.utils import ac_set_new_case_access from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity @@ -57,6 +56,7 @@ from app.schema.marshables import IocSchema from app.schema.marshables import CommentSchema from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_customer_access from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.responses import response_error from app.util import add_obj_history_entry @@ -215,7 +215,7 @@ def alerts_add_route() -> Response: alert = _load(request_data) result = alerts_create(alert, iocs, assets) - if not access_controls_user_has_customer_access(iris_current_user.id, result.alert_customer_id): + if not ac_current_user_has_customer_access(result.alert_customer_id): return response_error('User not entitled to create alerts for the client') alert_schema = AlertSchema() return response_success('Alert added', data=alert_schema.dump(result)) @@ -249,7 +249,7 @@ def alerts_get_route(alert_id) -> Response: # Return the alert as JSON if alert is None: return response_error('Alert not found') - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('Alert not found') alert_dump = alert_schema.dump(alert) @@ -281,7 +281,7 @@ def alerts_similarities_route(alert_id) -> Response: # Return the alert as JSON if alert is None: return response_error('Alert not found') - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('Alert not found') open_alerts = request.args.get('open-alerts', 'false').lower() == 'true' @@ -325,7 +325,7 @@ def alerts_update_route(alert_id) -> Response: alert = get_alert_by_id(alert_id) if not alert: return response_error('Alert not found') - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) alert_schema = AlertSchema() @@ -441,7 +441,7 @@ def alerts_batch_update_route() -> Response: activity_data.append(f"\"{key}\"") # Check if the user has access to the client - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) if alert.alert_owner_id is None: @@ -501,7 +501,7 @@ def alerts_batch_delete_route() -> Response: if not alert: return response_error(f'Alert with ID {alert_id} not found') - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to delete alerts for the client', status=403) success, logs = delete_alerts(alert_ids) @@ -538,7 +538,7 @@ def alerts_delete_route(alert_id) -> Response: try: # Check if the user has access to the client - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to delete alerts for the client', status=403) # Delete the case association @@ -596,7 +596,7 @@ def alerts_escalate_route(alert_id) -> Response: try: # Check if the user has access to the client - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to escalate alerts for the client', status=403) # Escalate the alert to a case @@ -671,7 +671,7 @@ def alerts_merge_route(alert_id) -> Response: case_tags = data.get('case_tags') try: # Check if the user has access to the client - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to merge alerts for the client', status=403) # Check if the user has access to the case @@ -731,7 +731,7 @@ def alerts_unmerge_route(alert_id) -> Response: try: # Check if the user has access to the client - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to unmerge alerts for the client', status=403) # Check if the user has access to the case @@ -804,7 +804,7 @@ def alerts_batch_merge_route() -> Response: continue # Check if the user has access to the client - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to merge alerts for the client', status=403) alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id @@ -872,7 +872,7 @@ def alerts_batch_escalate_route() -> Response: continue # Check if the user has access to the client - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to escalate alerts for the client', status=403) alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id @@ -928,7 +928,7 @@ def alert_comments_get(alert_id): if not alert: return response_error('Invalid alert ID') - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) alert_comments = get_alert_comments(alert_id) @@ -957,7 +957,7 @@ def alert_comment_delete(alert_id, com_id): if not alert: return response_error('Invalid alert ID') - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) success, msg = delete_alert_comment(comment_id=com_id, alert_id=alert_id) @@ -991,7 +991,7 @@ def alert_comment_get(alert_id, com_id): if not alert: return response_error('Invalid alert ID') - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) comment = get_alert_comment(alert_id, com_id) @@ -1019,7 +1019,7 @@ def alert_comment_edit(alert_id, com_id): if not alert: return response_error('Invalid alert ID') - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) return case_comment_update(com_id, 'events', None) @@ -1045,7 +1045,7 @@ def case_comment_add(alert_id): if not alert: return response_error('Invalid alert ID') # Check if the user has access to the client - if not access_controls_user_has_customer_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) comment_schema = CommentSchema() diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index a2675d770..100a453a7 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -50,7 +50,9 @@ from app.schema.marshables import CaseSchema from app.schema.marshables import CaseDetailsSchema from app.util import add_obj_history_entry -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_current_user_has_customer_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 @@ -62,7 +64,6 @@ from app.business.cases import cases_get_by_identifier from app.models.errors import BusinessProcessingError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook -from app.business.access_controls import access_controls_user_has_customer_access manage_cases_rest_blueprint = Blueprint('manage_case_rest', __name__) @@ -281,7 +282,7 @@ def update_case_info(identifier): request_data = request.get_json() # If user tries to update the customer, check if the user has access to the new customer if request_data.get('case_customer') and request_data.get('case_customer') != case.client_id: - if not access_controls_user_has_customer_access(iris_current_user.id, request_data.get('case_customer')): + if not ac_current_user_has_customer_access(request_data.get('case_customer')): raise BusinessProcessingError('Invalid customer ID. Permission denied.') if 'case_name' in request_data: diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index b9a724680..f6dd52b8c 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -22,6 +22,7 @@ from marshmallow.exceptions import ValidationError from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_customer_access from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.rest.endpoints import response_api_success from app.blueprints.rest.endpoints import response_api_paginated @@ -44,7 +45,6 @@ from app.business.alerts import related_alerts_get from app.models.errors import BusinessProcessingError from app.models.errors import ObjectNotFoundError -from app.business.access_controls import access_controls_user_has_customer_access class AlertsOperations: @@ -152,7 +152,7 @@ def create(self): alert = self._schema.load(request_data) result = alerts_create(alert, iocs, assets) - if not access_controls_user_has_customer_access(iris_current_user, result.alert_customer_id): + if not ac_current_user_has_customer_access(result.alert_customer_id): return response_api_error('User not entitled to create alerts for the client') return response_api_created(self._schema.dump(result)) diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index cfa7b42f4..3387d9ceb 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -46,12 +46,12 @@ from app.business.cases import cases_filter from app.schema.marshables import CaseSchemaForAPIV2 from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_customer_access from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access from app.blueprints.access_controls import ac_api_return_access_denied from app.models.authorization import Permissions from app.models.authorization import CaseAccessLevel from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook -from app.business.access_controls import access_controls_user_has_customer_access class CasesOperations: @@ -135,7 +135,7 @@ def update(self, identifier): customer_identifier = request_data.get('case_customer_id') # If user tries to update the customer, check if the user has access to the new customer if customer_identifier and customer_identifier != case.client_id: - if not access_controls_user_has_customer_access(iris_current_user, customer_identifier): + if not ac_current_user_has_customer_access(customer_identifier): raise BusinessProcessingError('Invalid customer ID. Permission denied.') if 'case_name' in request_data: diff --git a/source/app/business/access_controls.py b/source/app/business/access_controls.py index ef91d8ba2..d2dbe2b8f 100644 --- a/source/app/business/access_controls.py +++ b/source/app/business/access_controls.py @@ -17,6 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from app import db +from app import ac_current_user_has_permission 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 @@ -27,6 +28,7 @@ from app.models.authorization import UserCaseAccess from app.models.authorization import CaseAccessLevel from app.models.authorization import ac_flag_match_mask +from app.models.authorization import Permissions def set_user_case_access(user_id, case_id, access_level): @@ -95,4 +97,7 @@ def ac_fast_check_user_has_case_access(user_id, cid, expected_access_levels: lis def access_controls_user_has_customer_access(user, customer_identifier): + if ac_current_user_has_permission(Permissions.server_administrator): + return True + return user_has_client_access(user.id, customer_identifier) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 19c268887..0f99bc384 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -22,6 +22,7 @@ from app import db from app import socket_io +from app.blueprints.access_controls import ac_current_user_has_customer_access from app.models.alerts import Alert from app.models.iocs import Ioc from app.models.models import CaseAssets @@ -36,7 +37,6 @@ from app.iris_engine.utils.tracker import track_activity from app.util import add_obj_history_entry from app.models.errors import ObjectNotFoundError -from app.business.access_controls import access_controls_user_has_customer_access def alerts_search(start_date, end_date, source_start_date, source_end_date, title, description, @@ -100,7 +100,7 @@ def _get(user, identifier) -> Optional[Alert]: alert = get_alert_by_id(identifier) if not alert: return None - if not access_controls_user_has_customer_access(user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return None return alert diff --git a/source/app/datamgmt/manage/manage_access_control_db.py b/source/app/datamgmt/manage/manage_access_control_db.py index bdddeea8d..da508e175 100644 --- a/source/app/datamgmt/manage/manage_access_control_db.py +++ b/source/app/datamgmt/manage/manage_access_control_db.py @@ -167,9 +167,6 @@ def user_has_client_access(user_id: int, client_id: int) -> bool: Returns: bool: True if the user has access to the client """ - if ac_current_user_has_permission(Permissions.server_administrator): - return True - result = UserClient.query.filter( UserClient.user_id == user_id, UserClient.client_id == client_id From 8f3bf8ea3b526c34fc330f52b25a97e5e02e6230 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:33:56 +0100 Subject: [PATCH 127/286] Fixed incorrect retrieval of identifier --- source/app/blueprints/access_controls.py | 5 ++++- source/app/business/access_controls.py | 5 ----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index 0cd4ef760..8748676b8 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -593,4 +593,7 @@ def ac_current_user_has_permission(permission): def ac_current_user_has_customer_access(customer_identifier): - return access_controls_user_has_customer_access(iris_current_user.id, customer_identifier) + if ac_current_user_has_permission(Permissions.server_administrator): + return True + + return access_controls_user_has_customer_access(iris_current_user, customer_identifier) diff --git a/source/app/business/access_controls.py b/source/app/business/access_controls.py index d2dbe2b8f..ef91d8ba2 100644 --- a/source/app/business/access_controls.py +++ b/source/app/business/access_controls.py @@ -17,7 +17,6 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from app import db -from app import ac_current_user_has_permission 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 @@ -28,7 +27,6 @@ from app.models.authorization import UserCaseAccess from app.models.authorization import CaseAccessLevel from app.models.authorization import ac_flag_match_mask -from app.models.authorization import Permissions def set_user_case_access(user_id, case_id, access_level): @@ -97,7 +95,4 @@ def ac_fast_check_user_has_case_access(user_id, cid, expected_access_levels: lis def access_controls_user_has_customer_access(user, customer_identifier): - if ac_current_user_has_permission(Permissions.server_administrator): - return True - return user_has_client_access(user.id, customer_identifier) From 88d09a39285ea9be8a05b2b9d4cf284189ee39d7 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:49:09 +0100 Subject: [PATCH 128/286] Import db directly from app.db rather than from app --- source/app/blueprints/pages/login/login_routes.py | 2 +- source/app/blueprints/rest/alerts_routes.py | 2 +- source/app/blueprints/rest/case/case_assets_routes.py | 2 +- source/app/blueprints/rest/case/case_evidences_routes.py | 2 +- source/app/blueprints/rest/case/case_ioc_routes.py | 2 +- source/app/blueprints/rest/case/case_notes_routes.py | 2 +- source/app/blueprints/rest/case/case_routes.py | 2 +- source/app/blueprints/rest/case/case_tasks_routes.py | 2 +- source/app/blueprints/rest/case/case_timeline_routes.py | 2 +- source/app/blueprints/rest/context_routes.py | 2 +- source/app/blueprints/rest/dashboard_routes.py | 2 +- source/app/blueprints/rest/datastore_routes.py | 2 +- source/app/blueprints/rest/filters_routes.py | 2 +- source/app/blueprints/rest/manage/manage_assets_type_routes.py | 2 +- source/app/blueprints/rest/manage/manage_attributes_routes.py | 2 +- .../rest/manage/manage_case_classifications_routes.py | 2 +- source/app/blueprints/rest/manage/manage_case_state.py | 2 +- .../app/blueprints/rest/manage/manage_case_templates_routes.py | 2 +- source/app/blueprints/rest/manage/manage_cases_routes.py | 2 +- .../app/blueprints/rest/manage/manage_evidence_types_routes.py | 2 +- source/app/blueprints/rest/manage/manage_groups.py | 2 +- source/app/blueprints/rest/manage/manage_ioc_types_routes.py | 2 +- .../blueprints/rest/manage/manage_server_settings_routes.py | 2 +- source/app/blueprints/rest/manage/manage_templates_routes.py | 2 +- source/app/blueprints/rest/manage/manage_users.py | 2 +- source/app/blueprints/rest/profile_routes.py | 2 +- source/app/blueprints/rest/v2/auth.py | 2 +- source/app/business/access_controls.py | 2 +- source/app/business/alerts.py | 2 +- source/app/business/alerts_filters.py | 2 +- source/app/business/assets.py | 2 +- source/app/business/auth.py | 2 +- source/app/business/cases.py | 2 +- source/app/business/comments.py | 2 +- source/app/business/events.py | 2 +- source/app/business/iocs.py | 2 +- source/app/business/notes.py | 2 +- source/app/business/notes_directories.py | 2 +- source/app/business/tasks.py | 2 +- source/app/business/users.py | 2 +- source/app/datamgmt/alerts/alerts_db.py | 2 +- source/app/datamgmt/case/case_assets_db.py | 2 +- source/app/datamgmt/case/case_db.py | 2 +- source/app/datamgmt/case/case_events_db.py | 2 +- source/app/datamgmt/case/case_iocs_db.py | 2 +- source/app/datamgmt/case/case_notes_db.py | 2 +- source/app/datamgmt/case/case_rfiles_db.py | 2 +- source/app/datamgmt/case/case_tasks_db.py | 2 +- source/app/datamgmt/client/client_db.py | 2 +- source/app/datamgmt/comments.py | 2 +- source/app/datamgmt/dashboard/dashboard_db.py | 2 +- source/app/datamgmt/datastore/datastore_db.py | 2 +- source/app/datamgmt/iris_engine/modules_db.py | 3 ++- source/app/datamgmt/manage/manage_access_control_db.py | 2 +- source/app/datamgmt/manage/manage_attribute_db.py | 2 +- source/app/datamgmt/manage/manage_case_templates_db.py | 2 +- source/app/datamgmt/manage/manage_cases_db.py | 2 +- source/app/datamgmt/manage/manage_common.py | 2 +- source/app/datamgmt/manage/manage_groups_db.py | 2 +- source/app/datamgmt/manage/manage_srv_settings_db.py | 2 +- source/app/datamgmt/manage/manage_tags_db.py | 2 +- source/app/datamgmt/manage/manage_users_db.py | 2 +- source/app/datamgmt/states.py | 2 +- source/app/iris_engine/access_control/utils.py | 2 +- source/app/iris_engine/demo_builder.py | 2 +- source/app/iris_engine/module_handler/module_handler.py | 2 +- source/app/iris_engine/tasker/tasks.py | 2 +- source/app/iris_engine/updater/updater.py | 2 +- source/app/iris_engine/utils/tracker.py | 2 +- source/app/models/alerts.py | 2 +- source/app/models/cases.py | 2 +- source/app/models/comments.py | 2 +- source/app/models/iocs.py | 2 +- source/app/models/models.py | 2 +- source/app/post_init.py | 2 +- source/app/schema/marshables.py | 2 +- source/app/util.py | 2 +- 77 files changed, 78 insertions(+), 77 deletions(-) diff --git a/source/app/blueprints/pages/login/login_routes.py b/source/app/blueprints/pages/login/login_routes.py index e7ab3fb83..45991a093 100644 --- a/source/app/blueprints/pages/login/login_routes.py +++ b/source/app/blueprints/pages/login/login_routes.py @@ -34,7 +34,7 @@ from app import app from app import bc -from app import db +from app.db import db from app import oidc_client from app.blueprints.access_controls import is_authentication_oidc from app.blueprints.access_controls import is_authentication_ldap diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 8a3fe0737..054863daa 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -24,7 +24,7 @@ from typing import List from werkzeug import Response -from app import db +from app.db import db from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.rest.parsing import parse_comma_separated_identifiers from app.blueprints.rest.case_comments import case_comment_update diff --git a/source/app/blueprints/rest/case/case_assets_routes.py b/source/app/blueprints/rest/case/case_assets_routes.py index e0200a73f..f5275cc96 100644 --- a/source/app/blueprints/rest/case/case_assets_routes.py +++ b/source/app/blueprints/rest/case/case_assets_routes.py @@ -22,7 +22,7 @@ from flask import request from marshmallow import ValidationError -from app import db +from app.db import db from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.business.assets import assets_delete diff --git a/source/app/blueprints/rest/case/case_evidences_routes.py b/source/app/blueprints/rest/case/case_evidences_routes.py index 93fdb740b..583aa221b 100644 --- a/source/app/blueprints/rest/case/case_evidences_routes.py +++ b/source/app/blueprints/rest/case/case_evidences_routes.py @@ -22,7 +22,7 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user diff --git a/source/app/blueprints/rest/case/case_ioc_routes.py b/source/app/blueprints/rest/case/case_ioc_routes.py index feda2c938..8383b4116 100644 --- a/source/app/blueprints/rest/case/case_ioc_routes.py +++ b/source/app/blueprints/rest/case/case_ioc_routes.py @@ -25,7 +25,7 @@ from flask import request from marshmallow import ValidationError -from app import db +from app.db import db from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user diff --git a/source/app/blueprints/rest/case/case_notes_routes.py b/source/app/blueprints/rest/case/case_notes_routes.py index 6a0fc5e2a..f1432c9dc 100644 --- a/source/app/blueprints/rest/case/case_notes_routes.py +++ b/source/app/blueprints/rest/case/case_notes_routes.py @@ -21,7 +21,7 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app import app from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated diff --git a/source/app/blueprints/rest/case/case_routes.py b/source/app/blueprints/rest/case/case_routes.py index 32267fdd5..9fcef1783 100644 --- a/source/app/blueprints/rest/case/case_routes.py +++ b/source/app/blueprints/rest/case/case_routes.py @@ -24,7 +24,7 @@ from flask import request from app import app -from app import db +from app.db import db from app import socket_io from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user diff --git a/source/app/blueprints/rest/case/case_tasks_routes.py b/source/app/blueprints/rest/case/case_tasks_routes.py index 6078da2cf..01d5ce2fc 100644 --- a/source/app/blueprints/rest/case/case_tasks_routes.py +++ b/source/app/blueprints/rest/case/case_tasks_routes.py @@ -22,7 +22,7 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index 6eea9cd60..abd1c5512 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -26,7 +26,7 @@ from flask import request from sqlalchemy import and_ -from app import db +from app.db import db from app import app from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated diff --git a/source/app/blueprints/rest/context_routes.py b/source/app/blueprints/rest/context_routes.py index beb454560..8076220a6 100644 --- a/source/app/blueprints/rest/context_routes.py +++ b/source/app/blueprints/rest/context_routes.py @@ -22,7 +22,7 @@ from app import app from app import cache -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.context.context_db import ctx_search_user_cases from app.models.authorization import Permissions diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index 0e4355bbc..6ef1e12b7 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -27,7 +27,7 @@ from flask import redirect from flask_login import logout_user -from app import db +from app.db import db from app import app from app import oidc_client diff --git a/source/app/blueprints/rest/datastore_routes.py b/source/app/blueprints/rest/datastore_routes.py index f2609d87f..31cdb80ef 100644 --- a/source/app/blueprints/rest/datastore_routes.py +++ b/source/app/blueprints/rest/datastore_routes.py @@ -27,7 +27,7 @@ from flask import send_file from pathlib import Path -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.datastore.datastore_db import datastore_add_child_node from app.datamgmt.datastore.datastore_db import datastore_add_file_as_evidence diff --git a/source/app/blueprints/rest/filters_routes.py b/source/app/blueprints/rest/filters_routes.py index 3b67847a2..51d09b7cc 100644 --- a/source/app/blueprints/rest/filters_routes.py +++ b/source/app/blueprints/rest/filters_routes.py @@ -19,7 +19,7 @@ from flask import Blueprint, request from werkzeug import Response -from app import db +from app.db import db 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 diff --git a/source/app/blueprints/rest/manage/manage_assets_type_routes.py b/source/app/blueprints/rest/manage/manage_assets_type_routes.py index 3fa82461e..da6476869 100644 --- a/source/app/blueprints/rest/manage/manage_assets_type_routes.py +++ b/source/app/blueprints/rest/manage/manage_assets_type_routes.py @@ -24,7 +24,7 @@ from flask import request from app import app -from app import db +from app.db import db from app.datamgmt.manage.manage_case_objs import search_asset_type_by_name from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions diff --git a/source/app/blueprints/rest/manage/manage_attributes_routes.py b/source/app/blueprints/rest/manage/manage_attributes_routes.py index 5dcd1b8ae..8f7e755b4 100644 --- a/source/app/blueprints/rest/manage/manage_attributes_routes.py +++ b/source/app/blueprints/rest/manage/manage_attributes_routes.py @@ -19,7 +19,7 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app.datamgmt.manage.manage_attribute_db import update_all_attributes from app.datamgmt.manage.manage_attribute_db import validate_attribute from app.models.authorization import Permissions diff --git a/source/app/blueprints/rest/manage/manage_case_classifications_routes.py b/source/app/blueprints/rest/manage/manage_case_classifications_routes.py index a63f3c26f..d7b61070b 100644 --- a/source/app/blueprints/rest/manage/manage_case_classifications_routes.py +++ b/source/app/blueprints/rest/manage/manage_case_classifications_routes.py @@ -22,7 +22,7 @@ from flask import Response from flask import request -from app import db +from app.db import db from app.datamgmt.manage.manage_case_classifications_db import get_case_classifications_list from app.datamgmt.manage.manage_case_classifications_db import get_case_classification_by_id from app.datamgmt.manage.manage_case_classifications_db import search_classification_by_name diff --git a/source/app/blueprints/rest/manage/manage_case_state.py b/source/app/blueprints/rest/manage/manage_case_state.py index cc0618dfc..50570fc4a 100644 --- a/source/app/blueprints/rest/manage/manage_case_state.py +++ b/source/app/blueprints/rest/manage/manage_case_state.py @@ -22,7 +22,7 @@ from flask import Response from flask import request -from app import db +from app.db import db from app.datamgmt.manage.manage_case_state_db import get_case_states_list from app.datamgmt.manage.manage_case_state_db import get_case_state_by_id from app.datamgmt.manage.manage_case_state_db import get_cases_using_state diff --git a/source/app/blueprints/rest/manage/manage_case_templates_routes.py b/source/app/blueprints/rest/manage/manage_case_templates_routes.py index 0c27de5a8..4567b9bd8 100644 --- a/source/app/blueprints/rest/manage/manage_case_templates_routes.py +++ b/source/app/blueprints/rest/manage/manage_case_templates_routes.py @@ -21,7 +21,7 @@ from flask import request from marshmallow import ValidationError -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_case_templates_db import get_case_templates_list from app.datamgmt.manage.manage_case_templates_db import get_case_template_by_id diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index 100a453a7..d009e6c09 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -26,7 +26,7 @@ from werkzeug.utils import secure_filename from marshmallow import ValidationError -from app import db +from app.db import db from app.blueprints.rest.parsing import parse_comma_separated_identifiers from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user diff --git a/source/app/blueprints/rest/manage/manage_evidence_types_routes.py b/source/app/blueprints/rest/manage/manage_evidence_types_routes.py index 2bf31c48a..a64fa4099 100644 --- a/source/app/blueprints/rest/manage/manage_evidence_types_routes.py +++ b/source/app/blueprints/rest/manage/manage_evidence_types_routes.py @@ -22,7 +22,7 @@ from flask import Response from flask import request -from app import db +from app.db import db from app.datamgmt.manage.manage_evidence_types_db import get_evidence_types_list from app.datamgmt.manage.manage_evidence_types_db import get_evidence_type_by_id from app.datamgmt.manage.manage_evidence_types_db import verify_evidence_type_in_use diff --git a/source/app/blueprints/rest/manage/manage_groups.py b/source/app/blueprints/rest/manage/manage_groups.py index ea02453b3..0e005ba2d 100644 --- a/source/app/blueprints/rest/manage/manage_groups.py +++ b/source/app/blueprints/rest/manage/manage_groups.py @@ -22,7 +22,7 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app import app from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_groups_db import add_all_cases_access_to_group diff --git a/source/app/blueprints/rest/manage/manage_ioc_types_routes.py b/source/app/blueprints/rest/manage/manage_ioc_types_routes.py index 9e46e46bc..3f275fd89 100644 --- a/source/app/blueprints/rest/manage/manage_ioc_types_routes.py +++ b/source/app/blueprints/rest/manage/manage_ioc_types_routes.py @@ -20,7 +20,7 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app.datamgmt.case.case_iocs_db import get_ioc_types_list from app.datamgmt.manage.manage_case_objs import search_ioc_type_by_name from app.iris_engine.utils.tracker import track_activity diff --git a/source/app/blueprints/rest/manage/manage_server_settings_routes.py b/source/app/blueprints/rest/manage/manage_server_settings_routes.py index 3bd5f6e3f..adcb7d1f5 100644 --- a/source/app/blueprints/rest/manage/manage_server_settings_routes.py +++ b/source/app/blueprints/rest/manage/manage_server_settings_routes.py @@ -22,7 +22,7 @@ from app import app from app import celery -from app import db +from app.db import db from app.datamgmt.manage.manage_srv_settings_db import get_srv_settings from app.iris_engine.backup.backup import backup_iris_db from app.iris_engine.updater.updater import remove_periodic_update_checks diff --git a/source/app/blueprints/rest/manage/manage_templates_routes.py b/source/app/blueprints/rest/manage/manage_templates_routes.py index 9471fc69e..467ce34a8 100644 --- a/source/app/blueprints/rest/manage/manage_templates_routes.py +++ b/source/app/blueprints/rest/manage/manage_templates_routes.py @@ -28,7 +28,7 @@ from werkzeug.utils import secure_filename from app import app -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions diff --git a/source/app/blueprints/rest/manage/manage_users.py b/source/app/blueprints/rest/manage/manage_users.py index a32be8e20..240f36769 100644 --- a/source/app/blueprints/rest/manage/manage_users.py +++ b/source/app/blueprints/rest/manage/manage_users.py @@ -23,7 +23,7 @@ from flask import request from app import app -from app import db +from app.db import db from app.blueprints.rest.parsing import parse_comma_separated_identifiers from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user diff --git a/source/app/blueprints/rest/profile_routes.py b/source/app/blueprints/rest/profile_routes.py index a0375663e..ac33637c5 100644 --- a/source/app/blueprints/rest/profile_routes.py +++ b/source/app/blueprints/rest/profile_routes.py @@ -22,7 +22,7 @@ from flask import request from flask import session -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_users_db import get_user from app.datamgmt.manage.manage_users_db import get_user_primary_org diff --git a/source/app/blueprints/rest/v2/auth.py b/source/app/blueprints/rest/v2/auth.py index 8a5f4ed01..43f86270e 100644 --- a/source/app/blueprints/rest/v2/auth.py +++ b/source/app/blueprints/rest/v2/auth.py @@ -26,7 +26,7 @@ from oic.oauth2.exception import GrantError from app import app -from app import db +from app.db import db from app import oidc_client from app.blueprints.iris_user import iris_current_user from app.models.errors import ObjectNotFoundError diff --git a/source/app/business/access_controls.py b/source/app/business/access_controls.py index ef91d8ba2..1a8db5792 100644 --- a/source/app/business/access_controls.py +++ b/source/app/business/access_controls.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 import db +from app.db import db 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 diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 0f99bc384..daa4e7789 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -20,7 +20,7 @@ from datetime import datetime from typing import Optional -from app import db +from app.db import db from app import socket_io from app.blueprints.access_controls import ac_current_user_has_customer_access from app.models.alerts import Alert diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index ef1ddaa60..09ee796a8 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.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 import db +from app.db import db from app.datamgmt.filters.filters_db import get_filter_by_id from app.models.errors import ObjectNotFoundError diff --git a/source/app/business/assets.py b/source/app/business/assets.py index 11ac26f72..67ec4d748 100644 --- a/source/app/business/assets.py +++ b/source/app/business/assets.py @@ -18,7 +18,7 @@ from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.models.errors import BusinessProcessingError from app.models.errors import ObjectNotFoundError from app.business.cases import cases_exists diff --git a/source/app/business/auth.py b/source/app/business/auth.py index 3b2b0382d..89e85ec60 100644 --- a/source/app/business/auth.py +++ b/source/app/business/auth.py @@ -26,7 +26,7 @@ from app import bc from app import app -from app import db +from app.db import db from app.business.cases import cases_get_by_identifier from app.business.cases import cases_get_first from app.logger import logger diff --git a/source/app/business/cases.py b/source/app/business/cases.py index d12d608f2..44504947d 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -19,7 +19,7 @@ import datetime import traceback -from app import db +from app.db import db from app.logger import logger from app.util import add_obj_history_entry from app.models.models import ReviewStatusList diff --git a/source/app/business/comments.py b/source/app/business/comments.py index 5ade5b99e..18543bfc2 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -20,7 +20,7 @@ from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.business.alerts import alerts_exists from app.business.alerts import alerts_get from app.models.errors import ObjectNotFoundError diff --git a/source/app/business/events.py b/source/app/business/events.py index d485493ff..8f6702ae8 100644 --- a/source/app/business/events.py +++ b/source/app/business/events.py @@ -18,7 +18,7 @@ from datetime import datetime -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.cases import CasesEvent from app.models.errors import ObjectNotFoundError diff --git a/source/app/business/iocs.py b/source/app/business/iocs.py index d84bf1dd2..6582235dd 100644 --- a/source/app/business/iocs.py +++ b/source/app/business/iocs.py @@ -17,7 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.iocs import Ioc from app.datamgmt.case.case_iocs_db import add_ioc diff --git a/source/app/business/notes.py b/source/app/business/notes.py index 9d17f647a..147ea9657 100644 --- a/source/app/business/notes.py +++ b/source/app/business/notes.py @@ -18,7 +18,7 @@ from datetime import datetime -from app import db +from app.db import db from app.datamgmt.persistence_error import PersistenceError from app.blueprints.iris_user import iris_current_user from app.logger import logger diff --git a/source/app/business/notes_directories.py b/source/app/business/notes_directories.py index e8dd63248..c5b5eef41 100644 --- a/source/app/business/notes_directories.py +++ b/source/app/business/notes_directories.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 import db +from app.db import db from app.iris_engine.utils.tracker import track_activity from app.models.models import NoteDirectory from app.models.errors import ObjectNotFoundError diff --git a/source/app/business/tasks.py b/source/app/business/tasks.py index 227fb7245..a6090f858 100644 --- a/source/app/business/tasks.py +++ b/source/app/business/tasks.py @@ -20,7 +20,7 @@ from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.case.case_tasks_db import delete_task from app.datamgmt.case.case_tasks_db import add_task diff --git a/source/app/business/users.py b/source/app/business/users.py index 0b7b6189a..9ebff7a2f 100644 --- a/source/app/business/users.py +++ b/source/app/business/users.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 import db +from app.db import db from app.models.authorization import User from app.models.errors import BusinessProcessingError from app.models.errors import ObjectNotFoundError diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 1a59f2506..8a3e6bf63 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -37,7 +37,7 @@ from flask_sqlalchemy.pagination import Pagination import app -from app import db +from app.db import db from app.datamgmt.filtering import combine_conditions from app.datamgmt.filtering import apply_custom_conditions from app.datamgmt.case.case_assets_db import create_asset diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index 2fb112602..dcde97194 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -23,7 +23,7 @@ from sqlalchemy import func from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.logger import logger from app.blueprints.iris_user import iris_current_user from app.datamgmt.filtering import get_filtered_data diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index 766ab7363..dd3ea59e0 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -24,7 +24,7 @@ from sqlalchemy import exists from sqlalchemy import select -from app import db +from app.db import db from app.datamgmt.manage.manage_tags_db import add_db_tag from app.models.authorization import User from app.models.cases import CaseProtagonist diff --git a/source/app/datamgmt/case/case_events_db.py b/source/app/datamgmt/case/case_events_db.py index de41e8586..6f652d849 100644 --- a/source/app/datamgmt/case/case_events_db.py +++ b/source/app/datamgmt/case/case_events_db.py @@ -18,7 +18,7 @@ from sqlalchemy import and_ -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.states import update_timeline_state from app.models.models import AssetsType diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index f3a6be745..2c4b930ac 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -18,7 +18,7 @@ from sqlalchemy import and_ -from app import db +from app.db import db 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 diff --git a/source/app/datamgmt/case/case_notes_db.py b/source/app/datamgmt/case/case_notes_db.py index b74902d1d..eeda49a42 100644 --- a/source/app/datamgmt/case/case_notes_db.py +++ b/source/app/datamgmt/case/case_notes_db.py @@ -22,7 +22,7 @@ from datetime import datetime from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.datamgmt.persistence_error import PersistenceError from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes diff --git a/source/app/datamgmt/case/case_rfiles_db.py b/source/app/datamgmt/case/case_rfiles_db.py index 1cc73b8ff..d05ac2dc7 100644 --- a/source/app/datamgmt/case/case_rfiles_db.py +++ b/source/app/datamgmt/case/case_rfiles_db.py @@ -20,7 +20,7 @@ from sqlalchemy import desc from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes from app.datamgmt.states import update_evidences_state diff --git a/source/app/datamgmt/case/case_tasks_db.py b/source/app/datamgmt/case/case_tasks_db.py index 27ee76296..44896fcb1 100644 --- a/source/app/datamgmt/case/case_tasks_db.py +++ b/source/app/datamgmt/case/case_tasks_db.py @@ -22,7 +22,7 @@ from sqlalchemy import desc from sqlalchemy import and_ -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.conversions import convert_sort_direction from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index acb22adaf..c3b00c671 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -22,7 +22,7 @@ from typing import Optional from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.models.errors import ElementInUseError from app.models.cases import Cases from app.models.models import Client diff --git a/source/app/datamgmt/comments.py b/source/app/datamgmt/comments.py index 267beb358..589ac3099 100644 --- a/source/app/datamgmt/comments.py +++ b/source/app/datamgmt/comments.py @@ -19,7 +19,7 @@ from flask_sqlalchemy.pagination import Pagination from sqlalchemy import and_ -from app import db +from app.db import db from app.models.cases import Cases from app.models.comments import Comments from app.models.comments import EventComments diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index 9ae0e2fcd..46750c9cc 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -18,7 +18,7 @@ from sqlalchemy import and_ from sqlalchemy import desc -from app import db +from app.db import db from app.models.models import CaseTasks from app.models.models import TaskAssignee from app.models.models import ReviewStatus diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 87e5d3b94..387a3b048 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -25,7 +25,7 @@ from sqlalchemy import func from app import app -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.models import CaseReceivedFile from app.models.models import DataStoreFile diff --git a/source/app/datamgmt/iris_engine/modules_db.py b/source/app/datamgmt/iris_engine/modules_db.py index 381618355..97a86c425 100644 --- a/source/app/datamgmt/iris_engine/modules_db.py +++ b/source/app/datamgmt/iris_engine/modules_db.py @@ -18,7 +18,8 @@ import base64 import datetime -from app import db, app +from app.db import db +from app import app from app.blueprints.iris_user import iris_current_user from app.models.models import IrisHook from app.models.models import IrisModule diff --git a/source/app/datamgmt/manage/manage_access_control_db.py b/source/app/datamgmt/manage/manage_access_control_db.py index da508e175..9ccd50916 100644 --- a/source/app/datamgmt/manage/manage_access_control_db.py +++ b/source/app/datamgmt/manage/manage_access_control_db.py @@ -17,7 +17,7 @@ from sqlalchemy import and_ from app import ac_current_user_has_permission -from app import db +from app.db import db from app.models.cases import Cases from app.models.authorization import Group from app.models.authorization import UserClient diff --git a/source/app/datamgmt/manage/manage_attribute_db.py b/source/app/datamgmt/manage/manage_attribute_db.py index 12728799b..fc63c203e 100644 --- a/source/app/datamgmt/manage/manage_attribute_db.py +++ b/source/app/datamgmt/manage/manage_attribute_db.py @@ -20,7 +20,7 @@ import logging as logger from sqlalchemy.orm.attributes import flag_modified -from app import db +from app.db import db from app import app from app.models.models import CaseAssets from app.models.models import CaseReceivedFile diff --git a/source/app/datamgmt/manage/manage_case_templates_db.py b/source/app/datamgmt/manage/manage_case_templates_db.py index 2608707ac..151577590 100644 --- a/source/app/datamgmt/manage/manage_case_templates_db.py +++ b/source/app/datamgmt/manage/manage_case_templates_db.py @@ -21,7 +21,7 @@ from typing import Optional from typing import Union -from app import db +from app.db import db from app.datamgmt.case.case_tasks_db import add_task from app.datamgmt.manage.manage_case_classifications_db import get_case_classification_by_name from app.iris_engine.module_handler.module_handler import call_modules_hook diff --git a/source/app/datamgmt/manage/manage_cases_db.py b/source/app/datamgmt/manage/manage_cases_db.py index b28efe9f7..062854f94 100644 --- a/source/app/datamgmt/manage/manage_cases_db.py +++ b/source/app/datamgmt/manage/manage_cases_db.py @@ -25,7 +25,7 @@ from sqlalchemy.orm import aliased from functools import reduce -from app import db +from app.db import db from app.datamgmt.alerts.alerts_db import search_alert_resolution_by_name from app.datamgmt.case.case_db import get_case_tags from app.datamgmt.manage.manage_case_state_db import get_case_state_by_name diff --git a/source/app/datamgmt/manage/manage_common.py b/source/app/datamgmt/manage/manage_common.py index 52b4894f6..d1106675f 100644 --- a/source/app/datamgmt/manage/manage_common.py +++ b/source/app/datamgmt/manage/manage_common.py @@ -17,7 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import func -from app import db +from app.db import db from app.models.alerts import Severity diff --git a/source/app/datamgmt/manage/manage_groups_db.py b/source/app/datamgmt/manage/manage_groups_db.py index 9ab524033..458dacbd0 100644 --- a/source/app/datamgmt/manage/manage_groups_db.py +++ b/source/app/datamgmt/manage/manage_groups_db.py @@ -16,7 +16,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import and_ -from app import db +from app.db import db 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 diff --git a/source/app/datamgmt/manage/manage_srv_settings_db.py b/source/app/datamgmt/manage/manage_srv_settings_db.py index a7e77b282..f76626e34 100644 --- a/source/app/datamgmt/manage/manage_srv_settings_db.py +++ b/source/app/datamgmt/manage/manage_srv_settings_db.py @@ -1,6 +1,6 @@ from sqlalchemy import text -from app import db +from app.db import db from app.models.models import ServerSettings from app.schema.marshables import ServerSettingsSchema diff --git a/source/app/datamgmt/manage/manage_tags_db.py b/source/app/datamgmt/manage/manage_tags_db.py index 26f77bde8..085963af7 100644 --- a/source/app/datamgmt/manage/manage_tags_db.py +++ b/source/app/datamgmt/manage/manage_tags_db.py @@ -21,7 +21,7 @@ from sqlalchemy import and_ from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.logger import logger from app.models.models import Tags from app.datamgmt.conversions import convert_sort_direction diff --git a/source/app/datamgmt/manage/manage_users_db.py b/source/app/datamgmt/manage/manage_users_db.py index e266a17ee..b403f75fc 100644 --- a/source/app/datamgmt/manage/manage_users_db.py +++ b/source/app/datamgmt/manage/manage_users_db.py @@ -26,7 +26,7 @@ from app.blueprints.iris_user import iris_current_user from app.logger import logger from app import bc -from app import db +from app.db 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_ldp_group_removal diff --git a/source/app/datamgmt/states.py b/source/app/datamgmt/states.py index 48463720f..c22a3e030 100644 --- a/source/app/datamgmt/states.py +++ b/source/app/datamgmt/states.py @@ -19,7 +19,7 @@ from datetime import datetime from sqlalchemy import and_ -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.models import ObjectState diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index ed2237f4d..be38a78a9 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -1,6 +1,6 @@ from sqlalchemy import and_ -from app import db +from app.db 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 diff --git a/source/app/iris_engine/demo_builder.py b/source/app/iris_engine/demo_builder.py index 6059012cb..5e7c258bd 100644 --- a/source/app/iris_engine/demo_builder.py +++ b/source/app/iris_engine/demo_builder.py @@ -20,7 +20,7 @@ from app import app from app import bc -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user 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 diff --git a/source/app/iris_engine/module_handler/module_handler.py b/source/app/iris_engine/module_handler/module_handler.py index 2e66840c4..ecadeecff 100644 --- a/source/app/iris_engine/module_handler/module_handler.py +++ b/source/app/iris_engine/module_handler/module_handler.py @@ -30,7 +30,7 @@ from app.blueprints.iris_user import iris_current_user from app.logger import logger from app import celery -from app import db +from app.db import db from app.datamgmt.iris_engine.modules_db import get_module_config_from_hname from app.datamgmt.iris_engine.modules_db import iris_module_add from app.datamgmt.iris_engine.modules_db import iris_module_exists diff --git a/source/app/iris_engine/tasker/tasks.py b/source/app/iris_engine/tasker/tasks.py index b437c54c2..ddde1506f 100644 --- a/source/app/iris_engine/tasker/tasks.py +++ b/source/app/iris_engine/tasker/tasks.py @@ -20,7 +20,7 @@ import urllib.parse from celery.signals import task_prerun -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.case.case_db import get_case from app.iris_engine.module_handler.module_handler import pipeline_dispatcher diff --git a/source/app/iris_engine/updater/updater.py b/source/app/iris_engine/updater/updater.py index a331d92f9..59e8d5cd8 100644 --- a/source/app/iris_engine/updater/updater.py +++ b/source/app/iris_engine/updater/updater.py @@ -32,7 +32,7 @@ from app import app from app import cache from app import celery -from app import db +from app.db import db from app import socket_io from app.datamgmt.manage.manage_srv_settings_db import get_server_settings_as_dict from app.iris_engine.backup.backup import backup_iris_db diff --git a/source/app/iris_engine/utils/tracker.py b/source/app/iris_engine/utils/tracker.py index 0fb99cb0a..999d09a7d 100644 --- a/source/app/iris_engine/utils/tracker.py +++ b/source/app/iris_engine/utils/tracker.py @@ -20,7 +20,7 @@ from flask import request import app -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.models import UserActivity diff --git a/source/app/models/alerts.py b/source/app/models/alerts.py index b8c0b68cd..8dc623c06 100644 --- a/source/app/models/alerts.py +++ b/source/app/models/alerts.py @@ -31,7 +31,7 @@ from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship -from app import db +from app.db import db from app.models.models import alert_assets_association from app.models.iocs import alert_iocs_association diff --git a/source/app/models/cases.py b/source/app/models/cases.py index 622ab1627..f3a5b2dbe 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -36,7 +36,7 @@ from sqlalchemy.orm import relationship from sqlalchemy.orm import backref -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.states import update_assets_state from app.datamgmt.states import update_evidences_state diff --git a/source/app/models/comments.py b/source/app/models/comments.py index 459051ed5..7d5f48fd0 100644 --- a/source/app/models/comments.py +++ b/source/app/models/comments.py @@ -27,7 +27,7 @@ from sqlalchemy import ForeignKey from sqlalchemy.orm import relationship -from app import db +from app.db import db class Comments(db.Model): diff --git a/source/app/models/iocs.py b/source/app/models/iocs.py index 94dfefa49..8ea967435 100644 --- a/source/app/models/iocs.py +++ b/source/app/models/iocs.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 import db +from app.db import db from sqlalchemy import Table from sqlalchemy import Column diff --git a/source/app/models/models.py b/source/app/models/models.py index fca339676..1fa3171ff 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -44,7 +44,7 @@ from sqlalchemy.sql import func from app import app -from app import db +from app.db import db Base = declarative_base() metadata = Base.metadata diff --git a/source/app/post_init.py b/source/app/post_init.py index 068ada0b4..668a17a70 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -36,7 +36,7 @@ from app import bc from app import celery -from app import db +from app.db import db 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 diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 00cc68810..5e601f5b6 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -43,7 +43,7 @@ from typing import Union from werkzeug.datastructures import FileStorage from app.business.customers import customers_exists_another_with_same_name -from app import db +from app.db import db from app import ma from app.blueprints.iris_user import iris_current_user from app.logger import logger diff --git a/source/app/util.py b/source/app/util.py index 111db8247..d2fac7180 100644 --- a/source/app/util.py +++ b/source/app/util.py @@ -29,7 +29,7 @@ from sqlalchemy.orm.attributes import flag_modified from flask import current_app -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user From ebe8870cbf131827e06da8cafbbe7ced348c2be7 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:55:39 +0100 Subject: [PATCH 129/286] Deprecated GET /manage/customers/list --- 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 11bc0c236..b90d409bb 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -55,6 +55,7 @@ @manage_customers_rest_blueprint.route('/manage/customers/list', methods=['GET']) +@endpoint_deprecated('GET', '/api/v2/manage/customers') @ac_api_requires(Permissions.customers_read) def list_customers(): user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) From 08db0cd230ce21123b82e632c01df93fd06a2259 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:18:29 +0100 Subject: [PATCH 130/286] Fixed deepsource warning --- pyproject.toml | 4 ++-- source/app/blueprints/access_controls.py | 5 +---- source/app/blueprints/rest/v2/alerts.py | 9 +++++---- .../blueprints/rest/v2/alerts_routes/comments.py | 7 ++++--- source/app/business/access_controls.py | 6 +++++- source/app/business/alerts.py | 14 +++++++------- source/app/business/comments.py | 4 ++-- source/app/models/authorization.py | 4 ++++ 8 files changed, 30 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 099b1840d..906cb75b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,8 +28,8 @@ 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", "app.business.cases"] -forbidden_modules = "app.blueprints.iris_user" +source_modules = ["app.business.access_controls", "app.business.assets", "app.business.cases", "app.business.alerts"] +forbidden_modules = "app.blueprints" allow_indirect_imports = true [[tool.importlinter.contracts]] diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index 8748676b8..98a565e1b 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -593,7 +593,4 @@ def ac_current_user_has_permission(permission): def ac_current_user_has_customer_access(customer_identifier): - if ac_current_user_has_permission(Permissions.server_administrator): - return True - - return access_controls_user_has_customer_access(iris_current_user, customer_identifier) + return access_controls_user_has_customer_access(iris_current_user, session['permissions'], customer_identifier) diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index f6dd52b8c..3c8223cc6 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -17,6 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from flask import Blueprint +from flask import session from flask import request from flask import Response from marshmallow.exceptions import ValidationError @@ -165,7 +166,7 @@ def create(self): def read(self, identifier): try: - alert = alerts_get(iris_current_user, identifier) + alert = alerts_get(iris_current_user, session['permissions'], identifier) return response_api_success(self._schema.dump(alert)) except ObjectNotFoundError: @@ -174,7 +175,7 @@ def read(self, identifier): def get_related_alerts(self, identifier): try: - alert = alerts_get(iris_current_user, identifier) + alert = alerts_get(iris_current_user, session['permissions'], identifier) open_alerts = request.args.get('open-alerts', 'false').lower() == 'true' open_cases = request.args.get('open-cases', 'false').lower() == 'true' @@ -197,7 +198,7 @@ def get_related_alerts(self, identifier): def update(self, identifier): try: - alert = alerts_get(iris_current_user, identifier) + alert = alerts_get(iris_current_user, session['permissions'], identifier) request_data = request.get_json() updated_alert = self._schema.load(request_data, instance=alert, partial=True) activity_data = [] @@ -231,7 +232,7 @@ def update(self, identifier): def delete(self, identifier): try: - alert = alerts_get(iris_current_user, identifier) + alert = alerts_get(iris_current_user, session['permissions'], identifier) alerts_delete(alert) return response_api_deleted() diff --git a/source/app/blueprints/rest/v2/alerts_routes/comments.py b/source/app/blueprints/rest/v2/alerts_routes/comments.py index 521d91d8d..86d0d3f0d 100644 --- a/source/app/blueprints/rest/v2/alerts_routes/comments.py +++ b/source/app/blueprints/rest/v2/alerts_routes/comments.py @@ -17,6 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from flask import Blueprint +from flask import session from flask import request from marshmallow.exceptions import ValidationError @@ -58,7 +59,7 @@ def search(self, alert_identifier): def create(self, alert_identifier): try: comment = self._schema.load(request.get_json()) - comments_create_for_alert(iris_current_user, comment, alert_identifier) + comments_create_for_alert(iris_current_user, session['permissions'], comment, alert_identifier) result = self._schema.dump(comment) return response_api_created(result) except ValidationError as e: @@ -68,7 +69,7 @@ def create(self, alert_identifier): def read(self, alert_identifier, identifier): try: - alert = alerts_get(iris_current_user, alert_identifier) + alert = alerts_get(iris_current_user, session['permissions'], alert_identifier) comment = comments_get_for_alert(alert, identifier) result = self._schema.dump(comment) return response_api_success(result) @@ -84,7 +85,7 @@ def update(self, alert_identifier, identifier): def delete(self, alert_identifier, identifier): try: - alert = alerts_get(iris_current_user, alert_identifier) + alert = alerts_get(iris_current_user, session['permissions'], alert_identifier) comment = comments_get_for_alert(alert, identifier) if comment.comment_user_id != iris_current_user.id: return ac_api_return_access_denied() diff --git a/source/app/business/access_controls.py b/source/app/business/access_controls.py index 1a8db5792..3797f62cb 100644 --- a/source/app/business/access_controls.py +++ b/source/app/business/access_controls.py @@ -25,6 +25,7 @@ from app.datamgmt.manage.manage_access_control_db import user_has_client_access from app.logger import logger from app.models.authorization import UserCaseAccess +from app.models.authorization import ac_has_permission_server_administrator from app.models.authorization import CaseAccessLevel from app.models.authorization import ac_flag_match_mask @@ -94,5 +95,8 @@ def ac_fast_check_user_has_case_access(user_id, cid, expected_access_levels: lis return None -def access_controls_user_has_customer_access(user, customer_identifier): +def access_controls_user_has_customer_access(user, permissions, customer_identifier): + if ac_has_permission_server_administrator(permissions): + return True + return user_has_client_access(user.id, customer_identifier) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index daa4e7789..88466f327 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -20,9 +20,9 @@ from datetime import datetime from typing import Optional +from app.business.access_controls import access_controls_user_has_customer_access from app.db import db from app import socket_io -from app.blueprints.access_controls import ac_current_user_has_customer_access from app.models.alerts import Alert from app.models.iocs import Ioc from app.models.models import CaseAssets @@ -96,17 +96,17 @@ def alerts_create(alert: Alert, iocs: list[Ioc], assets: list[CaseAssets]) -> Al return alert -def _get(user, identifier) -> Optional[Alert]: +def _get(user, permissions, identifier) -> Optional[Alert]: alert = get_alert_by_id(identifier) if not alert: return None - if not ac_current_user_has_customer_access(alert.alert_customer_id): + if not access_controls_user_has_customer_access(user, permissions, alert.alert_customer_id): return None return alert -def alerts_get(user, identifier) -> Alert: - alert = _get(user, identifier) +def alerts_get(user, permissions, identifier) -> Alert: + alert = _get(user, permissions, identifier) if not alert: raise ObjectNotFoundError() return alert @@ -119,8 +119,8 @@ def related_alerts_get(alert, open_alerts, closed_alerts, open_cases, closed_cas days_back, number_of_results) -def alerts_exists(user, identifier) -> bool: - alert = _get(user, identifier) +def alerts_exists(identifier) -> bool: + alert = _get(identifier, permissions) return alert is not None diff --git a/source/app/business/comments.py b/source/app/business/comments.py index 18543bfc2..33d3e5c3b 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -121,8 +121,8 @@ def comments_update_for_case(current_user, comment_text, comment_id, object_type return comment -def comments_create_for_alert(current_user, comment: Comments, alert_identifier: int): - alert = alerts_get(current_user, alert_identifier) +def comments_create_for_alert(current_user, permissions, comment: Comments, alert_identifier: int): + alert = alerts_get(current_user, permissions, alert_identifier) comment.comment_alert_id = alert_identifier comment.comment_user_id = current_user.id comment.comment_date = datetime.now() diff --git a/source/app/models/authorization.py b/source/app/models/authorization.py index 62556b936..7cdcc70da 100644 --- a/source/app/models/authorization.py +++ b/source/app/models/authorization.py @@ -260,6 +260,10 @@ def ac_flag_match_mask(flag, mask): return (flag & mask) == mask +def ac_has_permission_server_administrator(permissions): + return ac_flag_match_mask(permissions, Permissions.server_administrator) + + def ac_access_level_mask_from_val_list(access_levels) -> int: """ Return an access level mask from a list of access levels From be022b8ba1f727cc6a9e087a0828b6f524cead7b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:23:24 +0100 Subject: [PATCH 131/286] Fixed missing parameters --- source/app/blueprints/rest/v2/alerts_routes/comments.py | 4 ++-- source/app/business/alerts.py | 4 ++-- source/app/business/comments.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_routes/comments.py b/source/app/blueprints/rest/v2/alerts_routes/comments.py index 86d0d3f0d..e62c52778 100644 --- a/source/app/blueprints/rest/v2/alerts_routes/comments.py +++ b/source/app/blueprints/rest/v2/alerts_routes/comments.py @@ -51,7 +51,7 @@ def __init__(self): def search(self, alert_identifier): pagination_parameters = parse_pagination_parameters(request) try: - comments = comments_get_filtered_by_alert(iris_current_user, alert_identifier, pagination_parameters) + comments = comments_get_filtered_by_alert(iris_current_user, session['permissions'], alert_identifier, pagination_parameters) return response_api_paginated(self._schema, comments) except ObjectNotFoundError: return response_api_not_found() @@ -79,7 +79,7 @@ def read(self, alert_identifier, identifier): return response_api_not_found() def update(self, alert_identifier, identifier): - if not alerts_exists(iris_current_user, alert_identifier): + if not alerts_exists(iris_current_user, session['permissions'], alert_identifier): return response_api_not_found() return case_comment_update(identifier, 'events', None) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 88466f327..e37fbcb95 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -119,8 +119,8 @@ def related_alerts_get(alert, open_alerts, closed_alerts, open_cases, closed_cas days_back, number_of_results) -def alerts_exists(identifier) -> bool: - alert = _get(identifier, permissions) +def alerts_exists(user, permissions, identifier) -> bool: + alert = _get(user, permissions, identifier) return alert is not None diff --git a/source/app/business/comments.py b/source/app/business/comments.py index 33d3e5c3b..35903d5ce 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -67,8 +67,8 @@ from app.models.alerts import Alert -def comments_get_filtered_by_alert(current_user, alert_identifier: int, pagination_parameters: PaginationParameters) -> Pagination: - if not alerts_exists(current_user, alert_identifier): +def comments_get_filtered_by_alert(current_user, permissions, alert_identifier: int, pagination_parameters: PaginationParameters) -> Pagination: + if not alerts_exists(current_user, permissions, alert_identifier): raise ObjectNotFoundError() return get_filtered_alert_comments(alert_identifier, pagination_parameters) From 6fe4364daf11d934c379c5d2e6afb0565ae62634 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:32:56 +0100 Subject: [PATCH 132/286] Fixed bug when checking for permission --- source/app/models/authorization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/models/authorization.py b/source/app/models/authorization.py index 7cdcc70da..04030bd42 100644 --- a/source/app/models/authorization.py +++ b/source/app/models/authorization.py @@ -261,7 +261,7 @@ def ac_flag_match_mask(flag, mask): def ac_has_permission_server_administrator(permissions): - return ac_flag_match_mask(permissions, Permissions.server_administrator) + return ac_flag_match_mask(permissions, Permissions.server_administrator.value) def ac_access_level_mask_from_val_list(access_levels) -> int: From 216b57cfb90db2d28ea59d59592e04e8f5fabca9 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:52:22 +0100 Subject: [PATCH 133/286] Started implementation of POST /api/v2/global-tasks --- source/app/blueprints/graphql/cases.py | 2 +- source/app/blueprints/rest/api_v2_routes.py | 2 + .../rest/manage/manage_cases_routes.py | 2 +- source/app/blueprints/rest/v2/cases.py | 2 +- source/app/blueprints/rest/v2/global_tasks.py | 56 +++++++++++++++++++ source/app/business/global_tasks.py | 37 ++++++++++++ source/app/datamgmt/dashboard/dashboard_db.py | 6 ++ .../module_handler/module_handler.py | 2 +- tests/iris.py | 4 ++ tests/tests_rest_global_tasks.py | 36 ++++++++++++ 10 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 source/app/blueprints/rest/v2/global_tasks.py create mode 100644 source/app/business/global_tasks.py create mode 100644 tests/tests_rest_global_tasks.py diff --git a/source/app/blueprints/graphql/cases.py b/source/app/blueprints/graphql/cases.py index 20c6c39dd..255b7e718 100644 --- a/source/app/blueprints/graphql/cases.py +++ b/source/app/blueprints/graphql/cases.py @@ -110,7 +110,7 @@ def mutate(root, info, name, description, client_id, soc_id=None, classification if classification_id: request['classification_id'] = classification_id - request_data = call_deprecated_on_preload_modules_hook('case_create', request, None) + request_data = call_deprecated_on_preload_modules_hook('case_create', request) schema = CaseSchema() case = schema.load(request_data) case_template_id = request_data.pop('case_template_id', None) diff --git a/source/app/blueprints/rest/api_v2_routes.py b/source/app/blueprints/rest/api_v2_routes.py index 782dac1b7..467658d61 100644 --- a/source/app/blueprints/rest/api_v2_routes.py +++ b/source/app/blueprints/rest/api_v2_routes.py @@ -26,6 +26,7 @@ from app.blueprints.rest.v2.auth import auth_blueprint from app.blueprints.rest.v2.cases import cases_blueprint from app.blueprints.rest.v2.dashboard import dashboard_blueprint +from app.blueprints.rest.v2.global_tasks import global_tasks_blueprint from app.blueprints.rest.v2.iocs import iocs_blueprint from app.blueprints.rest.v2.manage import manage_v2_blueprint from app.blueprints.rest.v2.tags import tags_blueprint @@ -41,6 +42,7 @@ rest_v2_blueprint.register_blueprint(cases_blueprint) rest_v2_blueprint.register_blueprint(auth_blueprint) rest_v2_blueprint.register_blueprint(tasks_blueprint) +rest_v2_blueprint.register_blueprint(global_tasks_blueprint) rest_v2_blueprint.register_blueprint(iocs_blueprint) rest_v2_blueprint.register_blueprint(assets_blueprint) rest_v2_blueprint.register_blueprint(events_blueprint) diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index d009e6c09..51a7bf1eb 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -249,7 +249,7 @@ def api_add_case(): case_schema = CaseSchema() try: - request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json(), None) + request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json()) case = case_schema.load(request_data) case_template_id = request_data.pop('case_template_id', None) result = cases_create(iris_current_user, case, case_template_id) diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index 3387d9ceb..9d9b7c5ba 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -101,7 +101,7 @@ def search(self): def create(self): try: - request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json(), None) + request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json()) case = self._schema.load(request_data) case_template_id = request_data.pop('case_template_id', None) case = cases_create(iris_current_user, case, case_template_id) diff --git a/source/app/blueprints/rest/v2/global_tasks.py b/source/app/blueprints/rest/v2/global_tasks.py new file mode 100644 index 000000000..f0f72e949 --- /dev/null +++ b/source/app/blueprints/rest/v2/global_tasks.py @@ -0,0 +1,56 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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.iris_user import iris_current_user +from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook +from app.schema.marshables import GlobalTasksSchema +from app.blueprints.access_controls import ac_api_requires +from app.blueprints.rest.endpoints import response_api_error +from app.blueprints.rest.endpoints import response_api_created +from app.business.global_tasks import global_tasks_create + + +class GlobalTasksOperations: + + def __init__(self): + self._schema = GlobalTasksSchema() + + def create(self): + request_data = call_deprecated_on_preload_modules_hook('global_task_create', request.get_json()) + try: + global_task = self._schema.load(request_data) + global_task = global_tasks_create(iris_current_user, global_task) + result = self._schema.dump(global_task) + return response_api_created(result) + except ValidationError as e: + return response_api_error('Data error', data=e.messages) + + +global_tasks_blueprint = Blueprint('global_tasks_rest_v2', __name__, url_prefix='/global-tasks') + +global_tasks_operations = GlobalTasksOperations() + + +@global_tasks_blueprint.post('') +@ac_api_requires() +def create_customer(): + return global_tasks_operations.create() diff --git a/source/app/business/global_tasks.py b/source/app/business/global_tasks.py new file mode 100644 index 000000000..695c10153 --- /dev/null +++ b/source/app/business/global_tasks.py @@ -0,0 +1,37 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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.models.models import GlobalTasks +from datetime import datetime +from app.iris_engine.module_handler.module_handler import call_modules_hook +from app.iris_engine.utils.tracker import track_activity +from app.datamgmt.dashboard.dashboard_db import create_global_task + + +def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: + global_task.task_userid_update = user.id + global_task.task_open_date = datetime.utcnow() + global_task.task_last_update = datetime.utcnow() + global_task.task_last_update = datetime.utcnow() + + create_global_task(global_task) + + global_task = call_modules_hook('on_postload_global_task_create', data=global_task) + track_activity(f'created new global task "{global_task.task_title}"') + + return global_task diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index 46750c9cc..6a6043d5c 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -15,6 +15,7 @@ # 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 sqlalchemy import and_ from sqlalchemy import desc @@ -28,6 +29,11 @@ from app.models.authorization import User +def create_global_task(global_task: GlobalTasks): + db.session.add(global_task) + db.session.commit() + + def list_global_tasks(): ct = GlobalTasks.query.with_entities( GlobalTasks.id.label("task_id"), diff --git a/source/app/iris_engine/module_handler/module_handler.py b/source/app/iris_engine/module_handler/module_handler.py index ecadeecff..45281aa82 100644 --- a/source/app/iris_engine/module_handler/module_handler.py +++ b/source/app/iris_engine/module_handler/module_handler.py @@ -581,7 +581,7 @@ def call_modules_hook(hook_name: str, data: any, caseid: int = None, hook_ui_nam return data -def call_deprecated_on_preload_modules_hook(hook_name: str, data: any, case_identifier) -> any: +def call_deprecated_on_preload_modules_hook(hook_name: str, data: any, case_identifier=None) -> any: hook_name = f'on_preload_{hook_name}' logger.warning(f'DEPRECATION WARNING: Hook {hook_name} has been deprecated and will be removed in a future version') return call_modules_hook(hook_name, data, caseid=case_identifier) diff --git a/tests/iris.py b/tests/iris.py index 464aed85e..37e593bee 100644 --- a/tests/iris.py +++ b/tests/iris.py @@ -134,6 +134,10 @@ def clear_database(self): for alert in response['data']: identifier = alert['alert_id'] self.delete(f'/api/v2/alerts/{identifier}') + response = self.get('/global/tasks/list').json() + for global_task in response['data']['tasks']: + identifier = global_task['task_id'] + self.create(f'/global/tasks/delete/{identifier}', {}) users = self.get('/manage/users/list').json() for user in users['data']: identifier = user['user_id'] diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py new file mode 100644 index 000000000..c1ea3ed95 --- /dev/null +++ b/tests/tests_rest_global_tasks.py @@ -0,0 +1,36 @@ +# 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 +from iris import ADMINISTRATOR_USER_IDENTIFIER + + +class TestsRestGlobalTasks(TestCase): + + def setUp(self) -> None: + self._subject = Iris() + + def tearDown(self): + self._subject.clear_database() + + def test_create_global_task_should_return_201(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create(f'/api/v2/global-tasks', body) + self.assertEqual(201, response.status_code) + From 87b094997e3b14670e88fe90781297635a6fcfc1 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:00:43 +0100 Subject: [PATCH 134/286] Fixed ruff warnings --- tests/tests_rest_global_tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py index c1ea3ed95..a4d027e5c 100644 --- a/tests/tests_rest_global_tasks.py +++ b/tests/tests_rest_global_tasks.py @@ -31,6 +31,5 @@ def tearDown(self): def test_create_global_task_should_return_201(self): body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} - response = self._subject.create(f'/api/v2/global-tasks', body) + response = self._subject.create('/api/v2/global-tasks', body) self.assertEqual(201, response.status_code) - From 70f262ced08e263b48f36ec64e6ff42225fb5ea4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:04:30 +0100 Subject: [PATCH 135/286] Factored code to create an object in database --- .../rest/manage/manage_customers_routes.py | 7 +++--- source/app/business/customers.py | 6 ++--- source/app/business/global_tasks.py | 4 ++-- source/app/business/groups.py | 5 ++-- source/app/datamgmt/alerts/alerts_db.py | 10 ++++---- source/app/datamgmt/case/case_assets_db.py | 4 ++-- source/app/datamgmt/case/case_events_db.py | 7 +++--- source/app/datamgmt/case/case_iocs_db.py | 7 +++--- source/app/datamgmt/case/case_notes_db.py | 7 +++--- source/app/datamgmt/case/case_rfiles_db.py | 4 ++-- source/app/datamgmt/case/case_tasks_db.py | 4 ++-- source/app/datamgmt/client/client_db.py | 11 --------- source/app/datamgmt/dashboard/dashboard_db.py | 5 ---- source/app/datamgmt/datastore/datastore_db.py | 16 +++++-------- source/app/datamgmt/db_operations.py | 24 +++++++++++++++++++ source/app/datamgmt/iris_engine/modules_db.py | 4 ++-- .../manage/manage_case_templates_db.py | 4 ++-- .../app/datamgmt/manage/manage_groups_db.py | 5 ---- source/app/datamgmt/manage/manage_users_db.py | 10 ++++---- 19 files changed, 67 insertions(+), 77 deletions(-) create mode 100644 source/app/datamgmt/db_operations.py diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index b90d409bb..1ef924d4b 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -27,10 +27,9 @@ from app.blueprints.iris_user import iris_current_user from app.business.customers import customers_get, customers_update from app.business.customers_contacts import customers_contacts_get +from app.datamgmt.db_operations import db_create from app.models.errors import ObjectNotFoundError from app.models.errors import ElementInUseError -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 from app.datamgmt.client.client_db import get_customer @@ -128,7 +127,7 @@ def customer_add_contact(client_id): data['client_id'] = client_id contact = contact_schema.load(data) - create_contact(contact) + db_create(contact) except ValidationError as e: return response_error(msg='Error adding contact', data=e.messages) @@ -268,7 +267,7 @@ def add_customers(): try: customer = customer_schema.load(request.json) - create_customer(customer) + db_create(customer) except ValidationError as e: return response_error(msg='Error adding customer', data=e.messages) except Exception as e: diff --git a/source/app/business/customers.py b/source/app/business/customers.py index e3af66667..28772c071 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -18,10 +18,10 @@ from flask_sqlalchemy.pagination import Pagination +from app.datamgmt.db_operations import db_create 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 create_customer from app.datamgmt.client.client_db import get_paginated_customers from app.datamgmt.client.client_db import get_customer from app.datamgmt.client.client_db import get_customer_by_name @@ -37,11 +37,11 @@ def customers_filter(user, pagination_parameters: PaginationParameters, is_serve # 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) + db_create(customer) def customers_create_with_user(user, customer: Client): - create_customer(customer) + customers_create(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/business/global_tasks.py b/source/app/business/global_tasks.py index 695c10153..8e67f5300 100644 --- a/source/app/business/global_tasks.py +++ b/source/app/business/global_tasks.py @@ -20,7 +20,7 @@ from datetime import datetime from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity -from app.datamgmt.dashboard.dashboard_db import create_global_task +from app.datamgmt.db_operations import db_create def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: @@ -29,7 +29,7 @@ def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: global_task.task_last_update = datetime.utcnow() global_task.task_last_update = datetime.utcnow() - create_global_task(global_task) + db_create(global_task) global_task = call_modules_hook('on_postload_global_task_create', data=global_task) track_activity(f'created new global task "{global_task.task_title}"') diff --git a/source/app/business/groups.py b/source/app/business/groups.py index 8d5e3eabf..86d08999a 100644 --- a/source/app/business/groups.py +++ b/source/app/business/groups.py @@ -15,10 +15,9 @@ # 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.datamgmt.db_operations import db_create from app.models.authorization import Group from app.iris_engine.utils.tracker import track_activity -from app.datamgmt.manage.manage_groups_db import create_group from app.datamgmt.manage.manage_groups_db import get_group_details from app.datamgmt.manage.manage_groups_db import update_group from app.datamgmt.manage.manage_groups_db import delete_group @@ -28,7 +27,7 @@ def groups_create(group: Group) -> Group: - create_group(group) + db_create(group) track_activity(f'added group {group.group_name}', ctx_less=True) return group diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 8a3e6bf63..2686186cd 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -73,6 +73,7 @@ from app.models.authorization import User from app.schema.marshables import EventSchema from app.util import add_obj_history_entry +from app.datamgmt.db_operations import db_create relationship_model_map = { @@ -271,8 +272,7 @@ def add_alert( alert.alert_owner_id = owner # Add the alert to the database - db.session.add(alert) - db.session.commit() + db_create(alert) return alert @@ -518,8 +518,7 @@ def create_case_from_alert(alert: Alert, iocs_list: List[str], assets_list: List new_alert_ioc.user_id = iris_current_user.id new_alert_ioc.case_id = case.case_id - db.session.add(new_alert_ioc) - db.session.commit() + db_create(new_alert_ioc) alert_ioc = new_alert_ioc @@ -541,8 +540,7 @@ def create_case_from_alert(alert: Alert, iocs_list: List[str], assets_list: List new_alert_asset.asset_id = None new_alert_asset.asset_uuid = asset_uuid - db.session.add(new_alert_asset) - db.session.commit() + db_create(new_alert_asset) alert_asset = new_alert_asset diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index dcde97194..97dd5f77b 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -23,6 +23,7 @@ from sqlalchemy import func from flask_sqlalchemy.pagination import Pagination +from app.datamgmt.db_operations import db_create from app.db import db from app.logger import logger from app.blueprints.iris_user import iris_current_user @@ -336,8 +337,7 @@ def add_comment_to_asset(asset_id, comment_id): ec.comment_asset_id = asset_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_assets_comments_count(asset_id): diff --git a/source/app/datamgmt/case/case_events_db.py b/source/app/datamgmt/case/case_events_db.py index 6f652d849..9d8f3305b 100644 --- a/source/app/datamgmt/case/case_events_db.py +++ b/source/app/datamgmt/case/case_events_db.py @@ -18,6 +18,7 @@ from sqlalchemy import and_ +from app.datamgmt.db_operations import db_create from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.states import update_timeline_state @@ -195,8 +196,7 @@ def add_comment_to_event(event_id, comment_id): ec.comment_event_id = event_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def delete_event_category(event_id): @@ -221,8 +221,7 @@ def save_event_category(event_id, category_id): cec.event_id = event_id cec.category_id = category_id - db.session.add(cec) - db.session.commit() + db_create(cec) def get_event_assets_ids(event_id, caseid): diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index 2c4b930ac..6a3831699 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -18,6 +18,7 @@ from sqlalchemy import and_ +from app.datamgmt.db_operations import db_create from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.filtering import get_filtered_data @@ -179,8 +180,7 @@ def add_ioc_type(name: str, description: str, taxonomy: str): type_taxonomy=taxonomy ) - db.session.add(ioct) - db.session.commit() + db_create(ioct) return ioct @@ -229,8 +229,7 @@ def add_comment_to_ioc(ioc_id, comment_id): ec.comment_ioc_id = ioc_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_iocs_comments_count(iocs_list): diff --git a/source/app/datamgmt/case/case_notes_db.py b/source/app/datamgmt/case/case_notes_db.py index eeda49a42..380575601 100644 --- a/source/app/datamgmt/case/case_notes_db.py +++ b/source/app/datamgmt/case/case_notes_db.py @@ -22,6 +22,7 @@ from datetime import datetime from flask_sqlalchemy.pagination import Pagination +from app.datamgmt.db_operations import db_create from app.db import db from app.datamgmt.persistence_error import PersistenceError from app.blueprints.iris_user import iris_current_user @@ -160,8 +161,7 @@ def update_note_revision(note: Notes) -> bool: note_user=iris_current_user.id, revision_timestamp=datetime.utcnow() ) - db.session.add(note_version) - db.session.commit() + db_create(note_version) return True except IntegrityError as e: @@ -379,8 +379,7 @@ def add_comment_to_note(note_id, comment_id): ec.comment_note_id = note_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_notes_comments_count(notes_list): diff --git a/source/app/datamgmt/case/case_rfiles_db.py b/source/app/datamgmt/case/case_rfiles_db.py index d05ac2dc7..ba5539306 100644 --- a/source/app/datamgmt/case/case_rfiles_db.py +++ b/source/app/datamgmt/case/case_rfiles_db.py @@ -20,6 +20,7 @@ from sqlalchemy import desc from flask_sqlalchemy.pagination import Pagination +from app.datamgmt.db_operations import db_create from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes @@ -129,8 +130,7 @@ def add_comment_to_evidence(evidence_id, comment_id): ec.comment_evidence_id = evidence_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_evidence_comments_count(evidences_list): diff --git a/source/app/datamgmt/case/case_tasks_db.py b/source/app/datamgmt/case/case_tasks_db.py index 44896fcb1..9dc368201 100644 --- a/source/app/datamgmt/case/case_tasks_db.py +++ b/source/app/datamgmt/case/case_tasks_db.py @@ -22,6 +22,7 @@ from sqlalchemy import desc from sqlalchemy import and_ +from app.datamgmt.db_operations import db_create from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.conversions import convert_sort_direction @@ -241,8 +242,7 @@ def add_comment_to_task(task_id, comment_id): ec.comment_task_id = task_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_tasks_comments_count(tasks_list): diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index c3b00c671..e42d9410f 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -33,12 +33,6 @@ from app.datamgmt.filtering import paginate -# TODO most probably rename into add (or save?) and make more generic -def create_contact(contact): - db.session.add(contact) - db.session.commit() - - # TODO most probably rename into update (or save?) and make more generic, maybe just use the preceding method? def update_contact(): db.session.commit() @@ -126,11 +120,6 @@ def get_client_cases(client_id: int): return cases_list -def create_customer(customer: Client): - db.session.add(customer) - db.session.commit() - - def get_client_contacts(client_id: int) -> List[Contact]: contacts = Contact.query.filter( Contact.client_id == client_id diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index 6a6043d5c..7d7e2ee4d 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -29,11 +29,6 @@ from app.models.authorization import User -def create_global_task(global_task: GlobalTasks): - db.session.add(global_task) - db.session.commit() - - def list_global_tasks(): ct = GlobalTasks.query.with_entities( GlobalTasks.id.label("task_id"), diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 387a3b048..7632e6640 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -25,6 +25,7 @@ from sqlalchemy import func from app import app +from app.datamgmt.db_operations import db_create from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.models import CaseReceivedFile @@ -146,8 +147,7 @@ def init_ds_tree(cid): dsp_root.path_case_id = cid dsp_root.path_parent_id = 0 - db.session.add(dsp_root) - db.session.commit() + db_create(dsp_root) for path in ['Evidences', 'IOCs', 'Images']: dsp_init = DataStorePath() @@ -193,8 +193,7 @@ def datastore_add_child_node(parent_node, folder_name, cid): dsp.path_parent_id = parent_node dsp.path_is_root = False - db.session.add(dsp) - db.session.commit() + db_create(dsp) return False, 'Folder added', dsp @@ -296,8 +295,7 @@ def datastore_get_interactive_path_node(cid): dsp.path_name = 'Notes Upload' dsp.path_is_root = False - db.session.add(dsp) - db.session.commit() + db_create(dsp) return dsp @@ -370,8 +368,7 @@ def datastore_add_file_as_ioc(dsf, caseid): ioc.ioc_tags = "datastore" ioc.user_id = iris_current_user.id - db.session.add(ioc) - db.session.commit() + db_create(ioc) def datastore_add_file_as_evidence(dsf, caseid): @@ -389,8 +386,7 @@ def datastore_add_file_as_evidence(dsf, caseid): crf.file_size = dsf.file_size crf.user_id = iris_current_user.id - db.session.add(crf) - db.session.commit() + db_create(crf) return diff --git a/source/app/datamgmt/db_operations.py b/source/app/datamgmt/db_operations.py new file mode 100644 index 000000000..3269a46a8 --- /dev/null +++ b/source/app/datamgmt/db_operations.py @@ -0,0 +1,24 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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.db import db + + +def db_create(element): + db.session.add(element) + db.session.commit() diff --git a/source/app/datamgmt/iris_engine/modules_db.py b/source/app/datamgmt/iris_engine/modules_db.py index 97a86c425..f5e4f316a 100644 --- a/source/app/datamgmt/iris_engine/modules_db.py +++ b/source/app/datamgmt/iris_engine/modules_db.py @@ -18,6 +18,7 @@ import base64 import datetime +from app.datamgmt.db_operations import db_create from app.db import db from app import app from app.blueprints.iris_user import iris_current_user @@ -57,8 +58,7 @@ def iris_module_add(module_name, module_human_name, module_description, im.module_type = module_type try: - db.session.add(im) - db.session.commit() + db_create(im) except Exception: return None diff --git a/source/app/datamgmt/manage/manage_case_templates_db.py b/source/app/datamgmt/manage/manage_case_templates_db.py index 151577590..9075b964c 100644 --- a/source/app/datamgmt/manage/manage_case_templates_db.py +++ b/source/app/datamgmt/manage/manage_case_templates_db.py @@ -21,6 +21,7 @@ from typing import Optional from typing import Union +from app.datamgmt.db_operations import db_create from app.db import db from app.datamgmt.case.case_tasks_db import add_task from app.datamgmt.manage.manage_case_classifications_db import get_case_classification_by_name @@ -262,8 +263,7 @@ def case_template_populate_note_groups(case: Cases, case_template: CaseTemplate) } note_dir = note_dir_schema.load(mapped_note_dir_template) - db.session.add(note_dir) - db.session.commit() + db_create(note_dir) if not note_dir: logs.append("Unable to add note group for internal reasons") diff --git a/source/app/datamgmt/manage/manage_groups_db.py b/source/app/datamgmt/manage/manage_groups_db.py index 458dacbd0..e5d28feba 100644 --- a/source/app/datamgmt/manage/manage_groups_db.py +++ b/source/app/datamgmt/manage/manage_groups_db.py @@ -32,11 +32,6 @@ from app.models.authorization import UserGroup -def create_group(group: Group): - db.session.add(group) - db.session.commit() - - def get_groups_list(): groups = Group.query.all() diff --git a/source/app/datamgmt/manage/manage_users_db.py b/source/app/datamgmt/manage/manage_users_db.py index b403f75fc..db327b632 100644 --- a/source/app/datamgmt/manage/manage_users_db.py +++ b/source/app/datamgmt/manage/manage_users_db.py @@ -24,6 +24,7 @@ from sqlalchemy import and_ from app.blueprints.iris_user import iris_current_user +from app.datamgmt.db_operations import db_create from app.logger import logger from app import bc from app.db import db @@ -149,8 +150,7 @@ def add_user_to_customer(user_id, customer_id): user_client.client_id = customer_id user_client.access_level = CaseAccessLevel.full_access.value user_client.allow_alerts = True - db.session.add(user_client) - db.session.commit() + db_create(user_client) ac_auto_update_user_effective_access(user_id) @@ -285,8 +285,7 @@ def add_user_to_organisation(user_id, org_id, make_primary=False): uo.user_id = user_id uo.org_id = org_id uo.is_primary_org = prim_org is None - db.session.add(uo) - db.session.commit() + db_create(uo) return True @@ -328,8 +327,7 @@ def add_user_to_group(user_id, group_id): ug = UserGroup() ug.user_id = user_id ug.group_id = group_id - db.session.add(ug) - db.session.commit() + db_create(ug) return True From 005ab91daa394f61a8b5a6e659cd6f1001712ce5 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:06:04 +0100 Subject: [PATCH 136/286] Removed unnecessary return values --- source/app/datamgmt/manage/manage_users_db.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/source/app/datamgmt/manage/manage_users_db.py b/source/app/datamgmt/manage/manage_users_db.py index db327b632..15397ee76 100644 --- a/source/app/datamgmt/manage/manage_users_db.py +++ b/source/app/datamgmt/manage/manage_users_db.py @@ -257,7 +257,6 @@ def change_user_primary_org(user_id, old_org_id, new_org_id): uo_new.is_primary_org = True db.session.commit() - return def add_user_to_organisation(user_id, org_id, make_primary=False): @@ -271,8 +270,7 @@ def add_user_to_organisation(user_id, org_id, make_primary=False): if uo_exists: uo_exists.is_primary_org = make_primary db.session.commit() - - return True + return # Check if user has a primary org already prim_org = get_user_primary_org(user_id=user_id) @@ -286,7 +284,6 @@ def add_user_to_organisation(user_id, org_id, make_primary=False): uo.org_id = org_id uo.is_primary_org = prim_org is None db_create(uo) - return True def get_user_primary_org(user_id): @@ -322,13 +319,12 @@ def add_user_to_group(user_id, group_id): ).scalar() if exists: - return True + return ug = UserGroup() ug.user_id = user_id ug.group_id = group_id db_create(ug) - return True def get_user_organisations(user_id): From d7393b906c1fa233274ded7853ea87d96bfd55a4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:09:39 +0100 Subject: [PATCH 137/286] Generalized import-linter constraint somewhat --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 906cb75b8..a51db598a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the persistence layer (access_controls)" type = "forbidden" -source_modules = "app.datamgmt.alerts.alerts_db" +source_modules = "app.datamgmt" forbidden_modules = "app.blueprints.access_controls" allow_indirect_imports = true From 1862bb7dbd64c992b71abfcd721a02bf1eff61ec Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:12:40 +0100 Subject: [PATCH 138/286] Calling conventions --- source/app/blueprints/rest/alerts_routes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 054863daa..ebb158555 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -882,9 +882,8 @@ def alerts_batch_escalate_route() -> Response: alerts_list.append(alert) # Merge alerts in the case - case = create_case_from_alerts(alerts_list, iocs_list=iocs_import_list, assets_list=assets_import_list, - note=note, import_as_event=import_as_event, case_tags=case_tags, - case_title=case_title, template_id=case_template_id) + case = create_case_from_alerts(alerts_list, iocs_import_list, assets_import_list, case_title, + note, import_as_event, case_tags, case_template_id) if not case: return response_error('Failed to create case from alert') From 554482905e90cd7b3ec6791796739129c4411390 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:21:16 +0100 Subject: [PATCH 139/286] Added a FIXME --- source/app/datamgmt/alerts/alerts_db.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 2686186cd..5d550c445 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -335,6 +335,7 @@ def create_case_from_alerts(alerts: List[Alert], iocs_list: List[str], assets_li case_template_title_prefix = case_template.title_prefix # Create the case + # FIXME I think there is a bug, if no template_id is provided case = Cases( name=f"[ALERT]{case_template_title_prefix} " f"Merge of alerts {', '.join([str(alert.alert_id) for alert in alerts])}" if not case_title else From 8fa3305b19859e36a0311b2d64f812735ac787c8 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:28:51 +0100 Subject: [PATCH 140/286] Removed unnecessary return --- source/app/models/cases.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/source/app/models/cases.py b/source/app/models/cases.py index f3a5b2dbe..62f415bdc 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -37,6 +37,7 @@ from sqlalchemy.orm import backref from app.db import db +from app.datamgmt.db_operations import db_create from app.blueprints.iris_user import iris_current_user from app.datamgmt.states import update_assets_state from app.datamgmt.states import update_evidences_state @@ -122,11 +123,7 @@ def save(self): Save the current case in database :return: """ - # Inject self into db - db.session.add(self) - - # Commit the changes - db.session.commit() + db_create(self) # Rename case with the ID self.name = f'#{self.case_id} - {self.name}' @@ -141,8 +138,6 @@ def save(self): db.session.commit() - return self - def validate_on_build(self): """ Execute an autocheck of the case metadata and validate it From c69dcf54ded0b286549267b68466a2cbfb7a7aad Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:34:53 +0100 Subject: [PATCH 141/286] Added import constraint: Do not import the persistence layer from the models --- pyproject.toml | 7 +++++++ source/app/business/cases.py | 4 ++-- source/app/datamgmt/alerts/alerts_db.py | 5 +++-- source/app/datamgmt/case/case_db.py | 26 +++++++++++++++++++++++- source/app/iris_engine/demo_builder.py | 5 +++-- source/app/models/cases.py | 27 ------------------------- source/app/post_init.py | 3 ++- 7 files changed, 42 insertions(+), 35 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a51db598a..149781051 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,3 +74,10 @@ source_modules = "app.datamgmt.case" forbidden_modules = "app.iris_engine" allow_indirect_imports = true +[[tool.importlinter.contracts]] +name = "Do not import the persistence layer from the models" +type = "forbidden" +source_modules = "app.models.cases" +forbidden_modules = "app.datamgmt" +allow_indirect_imports = true + diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 44504947d..ba0ffb810 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -29,7 +29,7 @@ from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.iris_engine.access_control.utils import ac_set_new_case_access -from app.datamgmt.case.case_db import case_db_exists +from app.datamgmt.case.case_db import case_db_exists, case_db_save from app.datamgmt.case.case_db import save_case_tags from app.datamgmt.case.case_db import register_case_protagonists from app.datamgmt.case.case_db import get_review_id_from_name @@ -118,7 +118,7 @@ def cases_create(user, case: Cases, case_template_id) -> Cases: case.state_id = get_case_state_by_name('Open').state_id - case.save() + case_db_save(case) if case_template_id and len(case_template_id) > 0: try: diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 5d550c445..a46c3b614 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -37,6 +37,7 @@ from flask_sqlalchemy.pagination import Pagination import app +from app.datamgmt.case.case_db import case_db_save from app.db import db from app.datamgmt.filtering import combine_conditions from app.datamgmt.filtering import apply_custom_conditions @@ -349,7 +350,7 @@ def create_case_from_alerts(alerts: List[Alert], iocs_list: List[str], assets_li state_id=get_case_state_by_name('Open').state_id ) - case.save() + case_db_save(case) for tag in case_tags.split(','): tag = Tags(tag_title=tag) @@ -474,7 +475,7 @@ def create_case_from_alert(alert: Alert, iocs_list: List[str], assets_list: List state_id=get_case_state_by_name('Open').state_id ) - case.save() + case_db_save(case) for tag in case_tags.split(','): tag = Tags(tag_title=tag) diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index dd3ea59e0..125b7e65d 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -25,7 +25,6 @@ from sqlalchemy import select from app.db import db -from app.datamgmt.manage.manage_tags_db import add_db_tag from app.models.authorization import User from app.models.cases import CaseProtagonist from app.models.cases import Cases @@ -34,6 +33,14 @@ from app.models.models import Client from app.models.models import Languages from app.models.models import ReportType +from app.datamgmt.manage.manage_tags_db import add_db_tag +from app.datamgmt.db_operations import db_create +from app.datamgmt.states import update_assets_state +from app.datamgmt.states import update_evidences_state +from app.datamgmt.states import update_ioc_state +from app.datamgmt.states import update_notes_state +from app.datamgmt.states import update_tasks_state +from app.datamgmt.states import update_timeline_state def get_first_case() -> Optional[Cases]: @@ -223,3 +230,20 @@ def get_review_id_from_name(review_name): return status.id return None + + +def case_db_save(case: Cases): + db_create(case) + + # Rename case with the ID + case.name = f'#{case.case_id} - {case.name}' + + # Create the states + update_timeline_state(caseid=case.case_id, userid=case.user_id) + update_tasks_state(caseid=case.case_id, userid=case.user_id) + update_evidences_state(caseid=case.case_id, userid=case.user_id) + update_ioc_state(caseid=case.case_id, userid=case.user_id) + update_assets_state(caseid=case.case_id, userid=case.user_id) + update_notes_state(caseid=case.case_id, userid=case.user_id) + + db.session.commit() diff --git a/source/app/iris_engine/demo_builder.py b/source/app/iris_engine/demo_builder.py index 5e7c258bd..20e908f30 100644 --- a/source/app/iris_engine/demo_builder.py +++ b/source/app/iris_engine/demo_builder.py @@ -20,6 +20,7 @@ from app import app from app import bc +from app.datamgmt.case.case_db import case_db_save from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_groups_db import add_case_access_to_group @@ -160,7 +161,7 @@ def create_demo_cases(users_data: dict = None, cases_count: int = 0, clients_cou ) case.validate_on_build() - case.save() + case_db_save(case) db.session.commit() cases_list.append(case.case_id) @@ -197,7 +198,7 @@ def create_demo_cases(users_data: dict = None, cases_count: int = 0, clients_cou client_id=random.choice(clients) ) case.validate_on_build() - case.save() + case_db_save(case) db.session.commit() cases_list.append(case.case_id) diff --git a/source/app/models/cases.py b/source/app/models/cases.py index 62f415bdc..2e6bf509b 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -37,14 +37,7 @@ from sqlalchemy.orm import backref from app.db import db -from app.datamgmt.db_operations import db_create from app.blueprints.iris_user import iris_current_user -from app.datamgmt.states import update_assets_state -from app.datamgmt.states import update_evidences_state -from app.datamgmt.states import update_ioc_state -from app.datamgmt.states import update_notes_state -from app.datamgmt.states import update_tasks_state -from app.datamgmt.states import update_timeline_state from app.models.models import Client @@ -118,26 +111,6 @@ def __init__(self, self.state_id = state_id, self.severity_id = severity_id - def save(self): - """ - Save the current case in database - :return: - """ - db_create(self) - - # Rename case with the ID - self.name = f'#{self.case_id} - {self.name}' - - # Create the states - update_timeline_state(caseid=self.case_id, userid=self.user_id) - update_tasks_state(caseid=self.case_id, userid=self.user_id) - update_evidences_state(caseid=self.case_id, userid=self.user_id) - update_ioc_state(caseid=self.case_id, userid=self.user_id) - update_assets_state(caseid=self.case_id, userid=self.user_id) - update_notes_state(caseid=self.case_id, userid=self.user_id) - - db.session.commit() - def validate_on_build(self): """ Execute an autocheck of the case metadata and validate it diff --git a/source/app/post_init.py b/source/app/post_init.py index 668a17a70..3fbcc14f4 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -85,6 +85,7 @@ from app.datamgmt.manage.manage_users_db import add_user_to_group from app.datamgmt.manage.manage_users_db import add_user_to_organisation from app.datamgmt.manage.manage_groups_db import get_group_by_name +from app.datamgmt.case.case_db import case_db_save _INITIAL_CLIENT_NAME = 'IrisInitialClient' @@ -741,7 +742,7 @@ def create_safe_case(user, client, groups): # Validate the case and save it to the database case.validate_on_build() - case.save() + case_db_save(case) db.session.commit() From 1daa99c75a31e13f3c221dfc130b6478ed9a193d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:41:35 +0100 Subject: [PATCH 142/286] Deprecated POST /global/tasks/add --- source/app/blueprints/rest/dashboard_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index 6ef1e12b7..09b2790a2 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -200,6 +200,7 @@ def utask_statusupdate(caseid): @dashboard_rest_blueprint.route('/global/tasks/add', methods=['POST']) +@endpoint_deprecated('POST', '/api/v2/global-tasks') @ac_api_requires() @ac_requires_case_identifier() def add_gtask(caseid): From 7a1b747d75b182063cf1e5e1f605a34f4398ee14 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:15:55 +0100 Subject: [PATCH 143/286] Removed an import app --- source/app/iris_engine/utils/tracker.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/source/app/iris_engine/utils/tracker.py b/source/app/iris_engine/utils/tracker.py index 999d09a7d..d17cdbda8 100644 --- a/source/app/iris_engine/utils/tracker.py +++ b/source/app/iris_engine/utils/tracker.py @@ -19,13 +19,11 @@ from datetime import datetime from flask import request -import app +from app.logger import logger from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.models import UserActivity -log = app.app.logger - def track_activity(message, caseid=None, ctx_less=False, user_input=False, display_in_ui=True): """ @@ -51,9 +49,9 @@ def track_activity(message, caseid=None, ctx_less=False, user_input=False, displ ua.activity_desc = message.capitalize() if iris_current_user.is_authenticated: - log.info(f"{iris_current_user.user} [#{iris_current_user.id}] :: Case {caseid} :: {ua.activity_desc}") + logger.info(f"{iris_current_user.user} [#{iris_current_user.id}] :: Case {caseid} :: {ua.activity_desc}") else: - log.info(f"Anonymous :: Case {caseid} :: {ua.activity_desc}") + logger.info(f"Anonymous :: Case {caseid} :: {ua.activity_desc}") ua.user_input = user_input ua.display_in_ui = display_in_ui From c345e41748a3b1c95525e8fe575b46a3340b2639 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:26:40 +0100 Subject: [PATCH 144/286] Removed an import app --- source/app/datamgmt/alerts/alerts_db.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index a46c3b614..33a3a4e32 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -36,7 +36,7 @@ from sqlalchemy.orm import selectinload from flask_sqlalchemy.pagination import Pagination -import app +from app.logger import logger from app.datamgmt.case.case_db import case_db_save from app.db import db from app.datamgmt.filtering import combine_conditions @@ -213,7 +213,7 @@ def get_filtered_alerts( try: custom_conditions = json.loads(custom_conditions) except: - app.app.logger.exception(f"Error parsing custom_conditions: {custom_conditions}") + logger.exception(f"Error parsing custom_conditions: {custom_conditions}") return query, conditions_tmp = apply_custom_conditions(query, Alert, custom_conditions, relationship_model_map) @@ -237,7 +237,7 @@ def get_filtered_alerts( return filtered_alerts except Exception as e: - app.app.logger.exception(f"Error getting alerts: {str(e)}") + logger.exception(f"Error getting alerts: {str(e)}") return None @@ -1405,7 +1405,7 @@ def delete_alerts(alert_ids: List[int]) -> tuple[bool, str]: except Exception as e: db.session.rollback() - app.logger.exception(str(e)) + logger.exception(str(e)) return False, "Server side error" return True, "" From 5c474c104a56a9f8716d28563368afe033246ae1 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:27:44 +0100 Subject: [PATCH 145/286] Removed an import app --- source/app/datamgmt/manage/manage_assets_db.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/datamgmt/manage/manage_assets_db.py b/source/app/datamgmt/manage/manage_assets_db.py index f236f167f..73a01ae09 100644 --- a/source/app/datamgmt/manage/manage_assets_db.py +++ b/source/app/datamgmt/manage/manage_assets_db.py @@ -1,7 +1,7 @@ from sqlalchemy import and_ from functools import reduce -import app +from app.logger import logger from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_cases_db import user_list_cases_view from app.datamgmt.conversions import convert_sort_direction @@ -82,7 +82,7 @@ def get_filtered_assets(case_id=None, filtered_assets = data.paginate(page=page, per_page=per_page) except Exception as e: - app.logger.exception(f"Failed to get filtered assets: {e}") + logger.exception(f"Failed to get filtered assets: {e}") raise e return filtered_assets From d4346fdbd433bf9ae24745cfed3ff3ac1ca11607 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:24:33 +0100 Subject: [PATCH 146/286] Started GET /api/v2/global-tasks/{identifier} --- source/app/blueprints/rest/dashboard_routes.py | 4 ++-- source/app/blueprints/rest/v2/global_tasks.py | 18 ++++++++++++++++++ source/app/business/global_tasks.py | 9 +++++++++ source/app/datamgmt/dashboard/dashboard_db.py | 6 ++++++ tests/tests_rest_global_tasks.py | 14 ++++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index 09b2790a2..430016023 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -155,11 +155,11 @@ def get_gtasks(): @dashboard_rest_blueprint.route('/global/tasks/', methods=['GET']) @ac_api_requires() def view_gtask(cur_id): - task = get_global_task(task_id=cur_id) + task = get_global_task(cur_id) if not task: return response_error(f'Global task ID {cur_id} not found') - return response_success("", data=task._asdict()) + return response_success('', data=task._asdict()) @dashboard_rest_blueprint.route('/user/tasks/status/update', methods=['POST']) diff --git a/source/app/blueprints/rest/v2/global_tasks.py b/source/app/blueprints/rest/v2/global_tasks.py index f0f72e949..a914336d1 100644 --- a/source/app/blueprints/rest/v2/global_tasks.py +++ b/source/app/blueprints/rest/v2/global_tasks.py @@ -26,7 +26,11 @@ from app.blueprints.access_controls import ac_api_requires from app.blueprints.rest.endpoints import response_api_error 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_not_found from app.business.global_tasks import global_tasks_create +from app.business.global_tasks import global_tasks_get +from app.models.errors import ObjectNotFoundError class GlobalTasksOperations: @@ -44,6 +48,14 @@ def create(self): except ValidationError as e: return response_api_error('Data error', data=e.messages) + def read(self, identifier): + try: + customer = global_tasks_get(identifier) + result = self._schema.dump(customer) + return response_api_success(result) + except ObjectNotFoundError: + return response_api_not_found() + global_tasks_blueprint = Blueprint('global_tasks_rest_v2', __name__, url_prefix='/global-tasks') @@ -54,3 +66,9 @@ def create(self): @ac_api_requires() def create_customer(): return global_tasks_operations.create() + + +@global_tasks_blueprint.get('/') +@ac_api_requires() +def get_customer(identifier): + return global_tasks_operations.read(identifier) diff --git a/source/app/business/global_tasks.py b/source/app/business/global_tasks.py index 8e67f5300..7c796493c 100644 --- a/source/app/business/global_tasks.py +++ b/source/app/business/global_tasks.py @@ -16,11 +16,13 @@ # 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.models.errors import ObjectNotFoundError from app.models.models import GlobalTasks from datetime import datetime from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.datamgmt.db_operations import db_create +from app.datamgmt.dashboard.dashboard_db import get_global_task_by_identifier def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: @@ -35,3 +37,10 @@ def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: track_activity(f'created new global task "{global_task.task_title}"') return global_task + + +def global_tasks_get(identifier) -> GlobalTasks: + task = get_global_task_by_identifier(identifier) + if not task: + raise ObjectNotFoundError() + return task diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index 7d7e2ee4d..383002be6 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -79,6 +79,12 @@ def get_global_task(task_id): return ct +def get_global_task_by_identifier(identifier): + return GlobalTasks.query.filter( + GlobalTasks.id == identifier + ).first() + + def get_tasks_status(): return TaskStatus.query.all() diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py index a4d027e5c..eaef193f9 100644 --- a/tests/tests_rest_global_tasks.py +++ b/tests/tests_rest_global_tasks.py @@ -33,3 +33,17 @@ def test_create_global_task_should_return_201(self): body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} response = self._subject.create('/api/v2/global-tasks', body) self.assertEqual(201, response.status_code) + + def test_get_global_task_should_return_200(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + response = self._subject.get(f'/api/v2/global-tasks/{identifier}') + self.assertEqual(200, response.status_code) + + def test_get_global_task_should_return_field_task_id(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + response = self._subject.get(f'/api/v2/global-tasks/{identifier}').json() + self.assertEqual(identifier, response['task_id']) From b314c45839e858ad40b4ba06ef4fa590f161d70c Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:48:56 +0100 Subject: [PATCH 147/286] Removed unnecessary return --- source/app/models/cases.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/app/models/cases.py b/source/app/models/cases.py index 2e6bf509b..bd00a0dee 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -125,8 +125,6 @@ def validate_on_build(self): self.client_id = res[0] - return True, [] - class CaseTags(db.Model): __tablename__ = 'case_tags' From ed4d0d53b22d97463f225e1c79e1b7abcfe03fe5 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:57:23 +0100 Subject: [PATCH 148/286] Removed seemingly unnecessary method --- source/app/iris_engine/demo_builder.py | 3 +-- source/app/models/cases.py | 15 +-------------- source/app/post_init.py | 2 -- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/source/app/iris_engine/demo_builder.py b/source/app/iris_engine/demo_builder.py index 20e908f30..4f6ea6941 100644 --- a/source/app/iris_engine/demo_builder.py +++ b/source/app/iris_engine/demo_builder.py @@ -15,6 +15,7 @@ # 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. + import random import string @@ -160,7 +161,6 @@ def create_demo_cases(users_data: dict = None, cases_count: int = 0, clients_cou client_id=random.choice(clients) ) - case.validate_on_build() case_db_save(case) db.session.commit() @@ -197,7 +197,6 @@ def create_demo_cases(users_data: dict = None, cases_count: int = 0, clients_cou user=random.choice(users_data['admins']), client_id=random.choice(clients) ) - case.validate_on_build() case_db_save(case) db.session.commit() diff --git a/source/app/models/cases.py b/source/app/models/cases.py index bd00a0dee..1f3930a1f 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -15,6 +15,7 @@ # 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. + import uuid from datetime import datetime @@ -111,20 +112,6 @@ def __init__(self, self.state_id = state_id, self.severity_id = severity_id - def validate_on_build(self): - """ - Execute an autocheck of the case metadata and validate it - :return: True if valid else false; Tuple with errors - """ - # TODO : Check if case name already exists - - res = Client.query\ - .with_entities(Client.client_id)\ - .filter(Client.client_id == self.client_id)\ - .first() - - self.client_id = res[0] - class CaseTags(db.Model): __tablename__ = 'case_tags' diff --git a/source/app/post_init.py b/source/app/post_init.py index 3fbcc14f4..1a0041920 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -740,8 +740,6 @@ def create_safe_case(user, client, groups): client_id=client.client_id ) - # Validate the case and save it to the database - case.validate_on_build() case_db_save(case) db.session.commit() From 149d3c937ccf4c056bd105ab35f71b09f5c24063 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 09:09:34 +0100 Subject: [PATCH 149/286] Fixed ruff warning --- source/app/models/cases.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/app/models/cases.py b/source/app/models/cases.py index 1f3930a1f..36f2fa4db 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -39,7 +39,6 @@ from app.db import db from app.blueprints.iris_user import iris_current_user -from app.models.models import Client class Cases(db.Model): From 3b57d7fd7f9106a24633109b38f30964819360e7 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 09:25:42 +0100 Subject: [PATCH 150/286] Fixed import to Client --- source/app/post_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/post_init.py b/source/app/post_init.py index 1a0041920..2657180e9 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -56,7 +56,7 @@ from app.models.authorization import User from app.models.cases import Cases from app.models.cases import CaseState -from app.models.cases import Client +from app.models.models import Client from app.models.models import AnalysisStatus from app.models.models import CaseClassification from app.models.models import ReviewStatus From aa735c4bc510fada62e90bc91ba260353522661c Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 09:38:43 +0100 Subject: [PATCH 151/286] Removed one use of too generic method get_or_create in models --- source/app/business/organisations.py | 35 +++++++++++++++++++ .../datamgmt/manage/manage_organisations.py | 24 +++++++++++++ source/app/post_init.py | 13 +++++-- 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 source/app/business/organisations.py create mode 100644 source/app/datamgmt/manage/manage_organisations.py diff --git a/source/app/business/organisations.py b/source/app/business/organisations.py new file mode 100644 index 000000000..bb6c8bdcd --- /dev/null +++ b/source/app/business/organisations.py @@ -0,0 +1,35 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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.datamgmt.db_operations import db_create +from app.datamgmt.manage.manage_organisations import get_organisation_by_name +from app.models.authorization import Organisation +from app.models.errors import ObjectNotFoundError + + +def organisations_get(name) -> Organisation: + organisation = get_organisation_by_name(name) + if not organisation: + raise ObjectNotFoundError() + return organisation + + +def organisations_create(name, description) -> Organisation: + organisation = Organisation(org_name=name, org_description=description) + db_create(organisation) + return organisation diff --git a/source/app/datamgmt/manage/manage_organisations.py b/source/app/datamgmt/manage/manage_organisations.py new file mode 100644 index 000000000..4bcd21307 --- /dev/null +++ b/source/app/datamgmt/manage/manage_organisations.py @@ -0,0 +1,24 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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.db import db +from app.models.authorization import Organisation + + +def get_organisation_by_name(name) -> Organisation: + return db.session.query(Organisation).filter_by(org_name=name).first() diff --git a/source/app/post_init.py b/source/app/post_init.py index 2657180e9..b2a8be31b 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -36,6 +36,7 @@ from app import bc from app import celery +from app.business.organisations import organisations_get, organisations_create from app.db import db from app.datamgmt.manage.manage_access_control_db import add_several_user_effective_access from app.iris_engine.demo_builder import create_demo_cases @@ -129,6 +130,7 @@ {'asset_name': 'Windows Account - AD - Service', 'asset_description': 'Windows Account - AD - krbtgt', 'asset_icon_not_compromised': 'user.png', 'asset_icon_compromised': 'ioc_user.png'} ] +_DEFAULT_ORGANISATION_NAME = 'Default Org' def connect_to_database(host: str, port: int) -> bool: @@ -1290,6 +1292,13 @@ def create_safe_server_settings(is_mfa_enabled): password_policy_special_chars="", enforce_mfa=is_mfa_enabled) +def create_safe_default_organisation(): + try: + return organisations_get(_DEFAULT_ORGANISATION_NAME) + except ObjectNotFoundError: + return organisations_create(_DEFAULT_ORGANISATION_NAME, 'Default Organisation') + + class PostInit: def __init__(self, app): @@ -1330,9 +1339,7 @@ def _create_safe_auth_model(self): It also updates the attributes of the existing Group objects if they have changed. """ - # Create new Organisation object - def_org = get_or_create(db.session, Organisation, org_name='Default Org', - org_description='Default Organisation') + def_org = create_safe_default_organisation() # Create new Administrator Group object try: From 002d7185a4092203d40063cfdd0efc33d2bf2ea6 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 10:01:36 +0100 Subject: [PATCH 152/286] Fixed ruff warning --- source/app/post_init.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/app/post_init.py b/source/app/post_init.py index b2a8be31b..d187ab87e 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -53,7 +53,6 @@ from app.models.alerts import AlertResolutionStatus from app.models.authorization import CaseAccessLevel from app.models.authorization import Group -from app.models.authorization import Organisation from app.models.authorization import User from app.models.cases import Cases from app.models.cases import CaseState From 3af00d3138ecbb8d2a281add9d20ddf6cbb95ef9 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 11:48:35 +0100 Subject: [PATCH 153/286] Added test to check Analysts group autofollow is set to false --- source/app/post_init.py | 2 +- tests/tests_rest_groups.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/source/app/post_init.py b/source/app/post_init.py index d187ab87e..5ac2cfab6 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -1382,7 +1382,7 @@ def _create_safe_auth_model(self): if ganalysts.group_permissions != ac_get_mask_analyst(): ganalysts.group_permissions = ac_get_mask_analyst() - if ganalysts.group_auto_follow is not False: + if ganalysts.group_auto_follow: ganalysts.group_auto_follow = False if ganalysts.group_auto_follow_access_level != CaseAccessLevel.full_access.value: diff --git a/tests/tests_rest_groups.py b/tests/tests_rest_groups.py index ce240bcbd..0ed96e3fe 100644 --- a/tests/tests_rest_groups.py +++ b/tests/tests_rest_groups.py @@ -21,6 +21,7 @@ from iris import Iris from iris import IRIS_PERMISSION_SERVER_ADMINISTRATOR from iris import ADMINISTRATOR_USER_IDENTIFIER +from iris import GROUP_ANALYSTS_IDENTIFIER _IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 @@ -77,6 +78,10 @@ def test_get_group_should_return_200(self): response = self._subject.get(f'/api/v2/manage/groups/{identifier}') self.assertEqual(200, response.status_code) + def test_get_group_analysts_should_return_field_autofollow_to_false(self): + group_analysts = self._subject.get(f'/api/v2/manage/groups/{GROUP_ANALYSTS_IDENTIFIER}').json() + self.assertFalse(group_analysts['group_auto_follow']) + def test_get_group_should_return_group_name(self): body = {'group_name': 'name', 'group_description': 'description'} response = self._subject.create('/api/v2/manage/groups', body).json() From 8e6f5caec27bfd87510f7b12e7944e77a7fa8756 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:31:21 +0100 Subject: [PATCH 154/286] Removed some usages of get_or_create --- source/app/business/groups.py | 9 ++- source/app/post_init.py | 134 +++++++++++++++++----------------- 2 files changed, 74 insertions(+), 69 deletions(-) diff --git a/source/app/business/groups.py b/source/app/business/groups.py index 86d08999a..a07bdfff6 100644 --- a/source/app/business/groups.py +++ b/source/app/business/groups.py @@ -18,7 +18,7 @@ from app.datamgmt.db_operations import db_create from app.models.authorization import Group from app.iris_engine.utils.tracker import track_activity -from app.datamgmt.manage.manage_groups_db import get_group_details +from app.datamgmt.manage.manage_groups_db import get_group_details, get_group_by_name from app.datamgmt.manage.manage_groups_db import update_group from app.datamgmt.manage.manage_groups_db import delete_group from app.models.errors import BusinessProcessingError @@ -40,6 +40,13 @@ def groups_get(identifier) -> Group: return group +def groups_get_by_name(name) -> Group: + group = get_group_by_name(name) + if not group: + raise ObjectNotFoundError() + return group + + def groups_exist(identifier) -> bool: group = get_group_details(identifier) return group is not None diff --git a/source/app/post_init.py b/source/app/post_init.py index 5ac2cfab6..e207b3a99 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -29,14 +29,16 @@ from alembic import command from alembic.config import Config from sqlalchemy import create_engine -from sqlalchemy import exc from sqlalchemy import or_ from sqlalchemy_utils import create_database from sqlalchemy_utils import database_exists from app import bc from app import celery -from app.business.organisations import organisations_get, organisations_create +from app.business.groups import groups_get_by_name +from app.business.groups import groups_create +from app.business.organisations import organisations_get +from app.business.organisations import organisations_create from app.db import db from app.datamgmt.manage.manage_access_control_db import add_several_user_effective_access from app.iris_engine.demo_builder import create_demo_cases @@ -74,7 +76,6 @@ 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.business.customers import customers_create @@ -84,7 +85,6 @@ 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 from app.datamgmt.manage.manage_users_db import add_user_to_organisation -from app.datamgmt.manage.manage_groups_db import get_group_by_name from app.datamgmt.case.case_db import case_db_save @@ -1298,6 +1298,67 @@ def create_safe_default_organisation(): return organisations_create(_DEFAULT_ORGANISATION_NAME, 'Default Organisation') +def create_safe_group(name, description, auto_follow, auto_follow_access_level, permissions): + try: + return groups_get_by_name(name) + except ObjectNotFoundError: + group = Group(group_name=name, group_description=description, + group_auto_follow=auto_follow, group_auto_follow_access_level=auto_follow_access_level, + group_permissions=permissions) + return groups_create(group) + + +# TODO is it really necessary to do all that? +# shouldn't the migration upgrade step already have put the database in the expected state? +# and shouldn't we protect modifications on initial entries that are not supposed to be modified +# (maybe comparing the identifier of the objects that are to be updated with the last identifier after database +# initialization) +def create_safe_auth_model(): + """Creates new Organisation, Group, and User objects if they do not already exist. + + This function creates a new Organisation object with the specified name and description, + and creates new Group objects with the specified name, description, auto-follow status, + auto-follow access level, and permissions if they do not already exist in the database. + It also updates the attributes of the existing Group objects if they have changed. + + """ + def_org = create_safe_default_organisation() + + # Create new Administrator Group object + gadm = create_safe_group('Administrators', 'Administrators', True, + CaseAccessLevel.full_access.value, ac_get_mask_full_permissions()) + + # Update Administrator Group object attributes + if gadm.group_permissions != ac_get_mask_full_permissions(): + gadm.group_permissions = ac_get_mask_full_permissions() + + if gadm.group_auto_follow_access_level != CaseAccessLevel.full_access.value: + gadm.group_auto_follow_access_level = CaseAccessLevel.full_access.value + + if gadm.group_auto_follow is not True: + gadm.group_auto_follow = True + + db.session.commit() + + # Create new Analysts Group object + ganalysts = create_safe_group('Analysts', 'Standard Analysts', False, + CaseAccessLevel.full_access.value, ac_get_mask_analyst()) + + # Update Analysts Group object attributes + if ganalysts.group_permissions != ac_get_mask_analyst(): + ganalysts.group_permissions = ac_get_mask_analyst() + + if ganalysts.group_auto_follow: + ganalysts.group_auto_follow = False + + if ganalysts.group_auto_follow_access_level != CaseAccessLevel.full_access.value: + ganalysts.group_auto_follow_access_level = CaseAccessLevel.full_access.value + + db.session.commit() + + return def_org, gadm, ganalysts + + class PostInit: def __init__(self, app): @@ -1329,69 +1390,6 @@ def _create_safe_classifications(self): name_expanded=f"{predicate.title()}: {entry.get('expanded')}", description=entry['description']) - def _create_safe_auth_model(self): - """Creates new Organisation, Group, and User objects if they do not already exist. - - This function creates a new Organisation object with the specified name and description, - and creates new Group objects with the specified name, description, auto-follow status, - auto-follow access level, and permissions if they do not already exist in the database. - It also updates the attributes of the existing Group objects if they have changed. - - """ - def_org = create_safe_default_organisation() - - # Create new Administrator Group object - try: - gadm = get_or_create(db.session, Group, group_name='Administrators', group_description='Administrators', - group_auto_follow=True, - group_auto_follow_access_level=CaseAccessLevel.full_access.value, - group_permissions=ac_get_mask_full_permissions()) - - except exc.IntegrityError: - db.session.rollback() - self._logger.warning('Administrator group integrity error. Group permissions were probably changed. Updating.') - gadm = Group.query.filter( - Group.group_name == 'Administrators' - ).first() - - # Update Administrator Group object attributes - if gadm.group_permissions != ac_get_mask_full_permissions(): - gadm.group_permissions = ac_get_mask_full_permissions() - - if gadm.group_auto_follow_access_level != CaseAccessLevel.full_access.value: - gadm.group_auto_follow_access_level = CaseAccessLevel.full_access.value - - if gadm.group_auto_follow is not True: - gadm.group_auto_follow = True - - db.session.commit() - - # Create new Analysts Group object - try: - ganalysts = get_or_create(db.session, Group, group_name='Analysts', group_description='Standard Analysts', - group_auto_follow=False, - group_auto_follow_access_level=CaseAccessLevel.full_access.value, - group_permissions=ac_get_mask_analyst()) - - except exc.IntegrityError: - db.session.rollback() - self._logger.warning('Analysts group integrity error. Group permissions were probably changed. Updating.') - ganalysts = get_group_by_name('Analysts') - - # Update Analysts Group object attributes - if ganalysts.group_permissions != ac_get_mask_analyst(): - ganalysts.group_permissions = ac_get_mask_analyst() - - if ganalysts.group_auto_follow: - ganalysts.group_auto_follow = False - - if ganalysts.group_auto_follow_access_level != CaseAccessLevel.full_access.value: - ganalysts.group_auto_follow_access_level = CaseAccessLevel.full_access.value - - db.session.commit() - - return def_org, gadm, ganalysts - def _create_safe_admin(self, def_org, gadm, admin_username, admin_email, admin_password, api_key): """Creates a new admin user if one does not already exist. @@ -1649,7 +1647,7 @@ def run(self): # Create initial authorization model, administrative user, and customer self._logger.info("Creating initial authorisation model") - def_org, gadm, ganalysts = self._create_safe_auth_model() + def_org, gadm, ganalysts = create_safe_auth_model() self._logger.info("Creating first administrative user") admin_username = self._configuration.get('IRIS_ADM_USERNAME') From 21c01f45a20d68a7c39ce4f2647ea2a065752ba6 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:35:55 +0100 Subject: [PATCH 155/286] Removed method get_or_create --- source/app/iris_engine/demo_builder.py | 17 ++++++++++++----- source/app/models/models.py | 12 ------------ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/source/app/iris_engine/demo_builder.py b/source/app/iris_engine/demo_builder.py index 4f6ea6941..6a2a1d81c 100644 --- a/source/app/iris_engine/demo_builder.py +++ b/source/app/iris_engine/demo_builder.py @@ -21,6 +21,7 @@ from app import app from app import bc +from app.business.customers import customers_get_by_name, customers_create from app.datamgmt.case.case_db import case_db_save from app.db import db from app.blueprints.iris_user import iris_current_user @@ -30,8 +31,8 @@ from app.datamgmt.manage.manage_users_db import user_exists from app.iris_engine.access_control.utils import ac_add_users_multi_effective_access from app.models.cases import Cases +from app.models.errors import ObjectNotFoundError from app.models.models import Client -from app.models.models import get_or_create from app.models.authorization import CaseAccessLevel from app.models.authorization import User @@ -137,14 +138,20 @@ def create_demo_users(def_org, gadm, ganalystes, users_count, seed_user, adm_cou return users +def safe_create_customer(name, description): + try: + return customers_get_by_name(name) + except ObjectNotFoundError: + customer = Client(name=name, description=description) + customers_create(customer) + return customer + + def create_demo_cases(users_data: dict = None, cases_count: int = 0, clients_count: int = 0): clients = [] for client_index in range(0, clients_count): - client = get_or_create(db.session, - Client, - name=f'Client {client_index}', - description=f'Description for client {client_index}') + client = safe_create_customer(f'Client {client_index}', f'Description for client {client_index}') clients.append(client.client_id) cases_list = [] diff --git a/source/app/models/models.py b/source/app/models/models.py index 1fa3171ff..4b0467630 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -105,18 +105,6 @@ 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: - return instance - - instance = model(**kwargs) - session.add(instance) - session.commit() - return instance - - class Client(db.Model): __tablename__ = 'client' From 6644e6c0722900ec1e9fd0f5631ed96ee40b0518 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:40:18 +0100 Subject: [PATCH 156/286] Moved CaseStatus into app.models.cases --- source/app/blueprints/pages/case/case_routes.py | 2 +- source/app/blueprints/rest/case/case_routes.py | 2 +- source/app/datamgmt/case/case_assets_db.py | 3 +-- source/app/datamgmt/manage/manage_cases_db.py | 3 +-- source/app/models/cases.py | 12 +++++++++++- source/app/models/models.py | 9 --------- source/app/schema/marshables.py | 3 +-- 7 files changed, 16 insertions(+), 18 deletions(-) diff --git a/source/app/blueprints/pages/case/case_routes.py b/source/app/blueprints/pages/case/case_routes.py index 4c4b1f051..706700994 100644 --- a/source/app/blueprints/pages/case/case_routes.py +++ b/source/app/blueprints/pages/case/case_routes.py @@ -32,7 +32,7 @@ from app.forms import PipelinesCaseForm from app.iris_engine.access_control.utils import ac_get_all_access_level from app.iris_engine.module_handler.module_handler import list_available_pipelines -from app.models.models import CaseStatus +from app.models.cases import CaseStatus from app.models.authorization import CaseAccessLevel from app.blueprints.access_controls import ac_case_requires diff --git a/source/app/blueprints/rest/case/case_routes.py b/source/app/blueprints/rest/case/case_routes.py index 9fcef1783..5725500c7 100644 --- a/source/app/blueprints/rest/case/case_routes.py +++ b/source/app/blueprints/rest/case/case_routes.py @@ -41,7 +41,7 @@ from app.business.cases import cases_export_to_json from app.iris_engine.access_control.utils import ac_set_case_access_for_users from app.iris_engine.utils.tracker import track_activity -from app.models.models import CaseStatus +from app.models.cases import CaseStatus from app.models.models import ReviewStatusList from app.models.authorization import CaseAccessLevel from app.schema.marshables import TaskLogSchema diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index 97dd5f77b..8d97d4271 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -30,11 +30,10 @@ from app.datamgmt.filtering import get_filtered_data from app.datamgmt.states import update_assets_state from app.models.models import AnalysisStatus -from app.models.models import CaseStatus from app.models.models import AssetsType from app.models.models import CaseAssets from app.models.models import CaseEventsAssets -from app.models.cases import Cases +from app.models.cases import Cases, CaseStatus from app.models.comments import Comments from app.models.comments import AssetComments from app.models.models import CompromiseStatus diff --git a/source/app/datamgmt/manage/manage_cases_db.py b/source/app/datamgmt/manage/manage_cases_db.py index 062854f94..b933aa984 100644 --- a/source/app/datamgmt/manage/manage_cases_db.py +++ b/source/app/datamgmt/manage/manage_cases_db.py @@ -36,7 +36,6 @@ from app.models.models import NoteRevisions from app.models.models import CaseClassification from app.models.models import alert_assets_association -from app.models.models import CaseStatus from app.models.models import TaskAssignee from app.models.models import NoteDirectory from app.models.models import Tags @@ -45,7 +44,7 @@ from app.models.models import CaseEventsIoc from app.models.models import CaseReceivedFile from app.models.models import CaseTasks -from app.models.cases import Cases +from app.models.cases import Cases, CaseStatus from app.models.cases import CasesEvent from app.models.models import Client from app.models.models import DataStoreFile diff --git a/source/app/models/cases.py b/source/app/models/cases.py index 36f2fa4db..3b2af18cc 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -16,9 +16,10 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import enum import uuid - from datetime import datetime + from sqlalchemy import BigInteger from sqlalchemy import CheckConstraint from sqlalchemy import Boolean @@ -183,3 +184,12 @@ class CaseProtagonist(db.Model): case = relationship('Cases') user = relationship('User') + + +class CaseStatus(enum.Enum): + unknown = 0x0 + false_positive = 0x1 + true_positive_with_impact = 0x2 + not_applicable = 0x3 + true_positive_without_impact = 0x4 + legitimate = 0x5 diff --git a/source/app/models/models.py b/source/app/models/models.py index 4b0467630..724bb5444 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -50,15 +50,6 @@ metadata = Base.metadata -class CaseStatus(enum.Enum): - unknown = 0x0 - false_positive = 0x1 - true_positive_with_impact = 0x2 - not_applicable = 0x3 - true_positive_without_impact = 0x4 - legitimate = 0x5 - - class ReviewStatusList: no_review_required = "No review required" not_reviewed = "Not reviewed" diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 5e601f5b6..0a3679b40 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -60,14 +60,13 @@ from app.models.models import Tags from app.models.models import ReviewStatus from app.models.models import EvidenceTypes -from app.models.models import CaseStatus from app.models.models import NoteDirectory from app.models.models import NoteRevisions from app.models.models import AssetsType from app.models.models import CaseAssets from app.models.models import CaseReceivedFile from app.models.models import CaseTasks -from app.models.cases import Cases +from app.models.cases import Cases, CaseStatus from app.models.cases import CasesEvent from app.models.models import Client from app.models.comments import Comments From be4a6c08b28494083c1f62bbb39983cb617867c9 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:42:28 +0100 Subject: [PATCH 157/286] Moved ReviewStatusList into app.models.cases --- source/app/blueprints/rest/case/case_routes.py | 3 +-- source/app/business/cases.py | 3 +-- source/app/models/cases.py | 8 ++++++++ source/app/models/models.py | 8 -------- source/app/post_init.py | 3 +-- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/source/app/blueprints/rest/case/case_routes.py b/source/app/blueprints/rest/case/case_routes.py index 5725500c7..d3a141205 100644 --- a/source/app/blueprints/rest/case/case_routes.py +++ b/source/app/blueprints/rest/case/case_routes.py @@ -41,8 +41,7 @@ from app.business.cases import cases_export_to_json from app.iris_engine.access_control.utils import ac_set_case_access_for_users from app.iris_engine.utils.tracker import track_activity -from app.models.cases import CaseStatus -from app.models.models import ReviewStatusList +from app.models.cases import CaseStatus, ReviewStatusList from app.models.authorization import CaseAccessLevel from app.schema.marshables import TaskLogSchema from app.schema.marshables import CaseSchema diff --git a/source/app/business/cases.py b/source/app/business/cases.py index ba0ffb810..6fc3f0b9d 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -22,7 +22,6 @@ from app.db import db from app.logger import logger from app.util import add_obj_history_entry -from app.models.models import ReviewStatusList from app.models.errors import BusinessProcessingError from app.models.errors import ObjectNotFoundError from app.business.iocs import iocs_exports_to_json @@ -55,7 +54,7 @@ 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.cases import Cases, ReviewStatusList from app.models.models import Client diff --git a/source/app/models/cases.py b/source/app/models/cases.py index 3b2af18cc..cb9cd797f 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -193,3 +193,11 @@ class CaseStatus(enum.Enum): not_applicable = 0x3 true_positive_without_impact = 0x4 legitimate = 0x5 + + +class ReviewStatusList: + no_review_required = "No review required" + not_reviewed = "Not reviewed" + pending_review = "Pending review" + review_in_progress = "Review in progress" + reviewed = "Reviewed" diff --git a/source/app/models/models.py b/source/app/models/models.py index 724bb5444..b63edd150 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -50,14 +50,6 @@ metadata = Base.metadata -class ReviewStatusList: - no_review_required = "No review required" - not_reviewed = "Not reviewed" - pending_review = "Pending review" - review_in_progress = "Review in progress" - reviewed = "Reviewed" - - class CompromiseStatus(enum.Enum): to_be_determined = 0x0 compromised = 0x1 diff --git a/source/app/post_init.py b/source/app/post_init.py index e207b3a99..c1ce27e57 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -56,13 +56,12 @@ from app.models.authorization import CaseAccessLevel from app.models.authorization import Group from app.models.authorization import User -from app.models.cases import Cases +from app.models.cases import Cases, ReviewStatusList from app.models.cases import CaseState from app.models.models import Client from app.models.models import AnalysisStatus from app.models.models import CaseClassification from app.models.models import ReviewStatus -from app.models.models import ReviewStatusList from app.models.models import EvidenceTypes from app.models.models import EventCategory from app.models.models import IocType From 4479bd2425393bd6e2b4a00f424439a0e85e704b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:45:07 +0100 Subject: [PATCH 158/286] Put Client model in its own module --- source/app/blueprints/rest/context_routes.py | 2 +- source/app/business/cases.py | 2 +- source/app/business/customers.py | 2 +- source/app/datamgmt/alerts/alerts_db.py | 2 +- source/app/datamgmt/case/case_db.py | 2 +- source/app/datamgmt/case/case_iocs_db.py | 2 +- source/app/datamgmt/case/case_notes_db.py | 2 +- source/app/datamgmt/client/client_db.py | 2 +- source/app/datamgmt/comments.py | 2 +- source/app/datamgmt/context/context_db.py | 2 +- .../app/datamgmt/manage/manage_assets_db.py | 2 +- .../datamgmt/manage/manage_attribute_db.py | 2 +- source/app/datamgmt/manage/manage_cases_db.py | 2 +- source/app/datamgmt/manage/manage_users_db.py | 2 +- .../app/iris_engine/access_control/utils.py | 2 +- source/app/iris_engine/demo_builder.py | 2 +- source/app/models/customers.py | 36 +++++++++++++++++++ source/app/models/models.py | 15 -------- source/app/post_init.py | 2 +- source/app/schema/marshables.py | 2 +- 20 files changed, 54 insertions(+), 33 deletions(-) create mode 100644 source/app/models/customers.py diff --git a/source/app/blueprints/rest/context_routes.py b/source/app/blueprints/rest/context_routes.py index 8076220a6..df1b7c18c 100644 --- a/source/app/blueprints/rest/context_routes.py +++ b/source/app/blueprints/rest/context_routes.py @@ -27,7 +27,7 @@ from app.datamgmt.context.context_db import ctx_search_user_cases from app.models.authorization import Permissions from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.blueprints.access_controls import ac_api_requires, not_authenticated_redirection_url from app.blueprints.responses import response_success from app.blueprints.rest.endpoints import endpoint_deprecated diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 6fc3f0b9d..0733c472b 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -55,7 +55,7 @@ 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, ReviewStatusList -from app.models.models import Client +from app.models.customers import Client def cases_filter(current_user, pagination_parameters, name, case_identifiers, customer_identifier, diff --git a/source/app/business/customers.py b/source/app/business/customers.py index 28772c071..9e776cc78 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -19,7 +19,7 @@ from flask_sqlalchemy.pagination import Pagination from app.datamgmt.db_operations import db_create -from app.models.models import Client +from app.models.customers 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_paginated_customers diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 33a3a4e32..2a26ddf16 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -63,7 +63,7 @@ from app.models.models import alert_assets_association from app.models.iocs import alert_iocs_association from app.models.iocs import Ioc -from app.models.models import Client +from app.models.customers import Client from app.models.alerts import Alert from app.models.alerts import AlertStatus from app.models.alerts import AlertCaseAssociation diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index 125b7e65d..9f32ae57c 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -30,7 +30,7 @@ from app.models.cases import Cases from app.models.models import CaseTemplateReport from app.models.models import ReviewStatus -from app.models.models import Client +from app.models.customers import Client from app.models.models import Languages from app.models.models import ReportType from app.datamgmt.manage.manage_tags_db import add_db_tag diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index 6a3831699..c400cf4b0 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -26,7 +26,7 @@ from app.models.alerts import Alert from app.models.cases import Cases from app.models.cases import CasesEvent -from app.models.models import Client +from app.models.customers import Client from app.models.models import CaseAssets from app.models.comments import Comments from app.models.comments import IocComments diff --git a/source/app/datamgmt/case/case_notes_db.py b/source/app/datamgmt/case/case_notes_db.py index 380575601..57d72fff1 100644 --- a/source/app/datamgmt/case/case_notes_db.py +++ b/source/app/datamgmt/case/case_notes_db.py @@ -37,7 +37,7 @@ from app.models.models import NotesGroupLink from app.models.authorization import User from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.pagination_parameters import PaginationParameters from app.datamgmt.filtering import paginate diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index e42d9410f..eea6bc381 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -25,7 +25,7 @@ from app.db import db from app.models.errors import ElementInUseError from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.models import Contact from app.models.authorization import User from app.models.authorization import UserClient diff --git a/source/app/datamgmt/comments.py b/source/app/datamgmt/comments.py index 589ac3099..a33dbe152 100644 --- a/source/app/datamgmt/comments.py +++ b/source/app/datamgmt/comments.py @@ -30,7 +30,7 @@ from app.models.comments import NotesComments from app.models.pagination_parameters import PaginationParameters from app.models.authorization import User -from app.models.models import Client +from app.models.customers import Client def _get_filtered_comments(query, pagination_parameters: PaginationParameters) -> Pagination: diff --git a/source/app/datamgmt/context/context_db.py b/source/app/datamgmt/context/context_db.py index d9654dfa3..a753f387b 100644 --- a/source/app/datamgmt/context/context_db.py +++ b/source/app/datamgmt/context/context_db.py @@ -22,7 +22,7 @@ from sqlalchemy import desc from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.authorization import CaseAccessLevel from app.models.authorization import UserCaseEffectiveAccess from app.datamgmt.authorization import has_deny_all_access_level diff --git a/source/app/datamgmt/manage/manage_assets_db.py b/source/app/datamgmt/manage/manage_assets_db.py index 73a01ae09..5a9524c73 100644 --- a/source/app/datamgmt/manage/manage_assets_db.py +++ b/source/app/datamgmt/manage/manage_assets_db.py @@ -7,7 +7,7 @@ from app.datamgmt.conversions import convert_sort_direction from app.models.cases import Cases from app.models.models import CaseAssets -from app.models.models import Client +from app.models.customers import Client def get_filtered_assets(case_id=None, diff --git a/source/app/datamgmt/manage/manage_attribute_db.py b/source/app/datamgmt/manage/manage_attribute_db.py index fc63c203e..182459a4f 100644 --- a/source/app/datamgmt/manage/manage_attribute_db.py +++ b/source/app/datamgmt/manage/manage_attribute_db.py @@ -27,7 +27,7 @@ from app.models.models import CaseTasks from app.models.cases import Cases from app.models.cases import CasesEvent -from app.models.models import Client +from app.models.customers import Client from app.models.models import CustomAttribute from app.models.iocs import Ioc from app.models.models import Notes diff --git a/source/app/datamgmt/manage/manage_cases_db.py b/source/app/datamgmt/manage/manage_cases_db.py index b933aa984..774b42691 100644 --- a/source/app/datamgmt/manage/manage_cases_db.py +++ b/source/app/datamgmt/manage/manage_cases_db.py @@ -46,7 +46,7 @@ from app.models.models import CaseTasks from app.models.cases import Cases, CaseStatus from app.models.cases import CasesEvent -from app.models.models import Client +from app.models.customers import Client from app.models.models import DataStoreFile from app.models.models import DataStorePath from app.models.models import IocAssetLink diff --git a/source/app/datamgmt/manage/manage_users_db.py b/source/app/datamgmt/manage/manage_users_db.py index 15397ee76..c8f2f1d3c 100644 --- a/source/app/datamgmt/manage/manage_users_db.py +++ b/source/app/datamgmt/manage/manage_users_db.py @@ -36,7 +36,7 @@ from app.iris_engine.access_control.utils import ac_get_detailed_effective_permissions_from_groups from app.iris_engine.access_control.utils import ac_remove_case_access_from_user from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.models import UserActivity from app.models.authorization import CaseAccessLevel, ac_access_level_mask_from_val_list from app.models.authorization import UserClient diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index be38a78a9..3c70e2f34 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -6,7 +6,7 @@ from app.logger import logger from app.blueprints.iris_user import iris_current_user from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.authorization import CaseAccessLevel from app.models.authorization import ac_flag_match_mask from app.models.authorization import UserClient diff --git a/source/app/iris_engine/demo_builder.py b/source/app/iris_engine/demo_builder.py index 6a2a1d81c..51bb69297 100644 --- a/source/app/iris_engine/demo_builder.py +++ b/source/app/iris_engine/demo_builder.py @@ -32,7 +32,7 @@ from app.iris_engine.access_control.utils import ac_add_users_multi_effective_access from app.models.cases import Cases from app.models.errors import ObjectNotFoundError -from app.models.models import Client +from app.models.customers import Client from app.models.authorization import CaseAccessLevel from app.models.authorization import User diff --git a/source/app/models/customers.py b/source/app/models/customers.py new file mode 100644 index 000000000..5acd16c86 --- /dev/null +++ b/source/app/models/customers.py @@ -0,0 +1,36 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 sqlalchemy import Column, BigInteger, UUID, text, Text, DateTime, func, ForeignKey +from sqlalchemy.dialects.postgresql import JSON + +from app import db + + +class Client(db.Model): + __tablename__ = 'client' + + client_id = Column(BigInteger, primary_key=True) + client_uuid = Column(UUID(as_uuid=True), server_default=text("gen_random_uuid()"), nullable=False) + name = Column(Text, unique=True) + description = Column(Text) + sla = Column(Text) + creation_date = Column(DateTime, server_default=func.now(), nullable=True) + created_by = Column(ForeignKey('user.id'), nullable=True) + last_update_date = Column(DateTime, server_default=func.now(), nullable=True) + + custom_attributes = Column(JSON) diff --git a/source/app/models/models.py b/source/app/models/models.py index b63edd150..9bf69f351 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -88,21 +88,6 @@ def create_safe_limited(session, model, keywords_list, **kwargs): return True -class Client(db.Model): - __tablename__ = 'client' - - client_id = Column(BigInteger, primary_key=True) - client_uuid = Column(UUID(as_uuid=True), server_default=text("gen_random_uuid()"), nullable=False) - name = Column(Text, unique=True) - description = Column(Text) - sla = Column(Text) - creation_date = Column(DateTime, server_default=func.now(), nullable=True) - created_by = Column(ForeignKey('user.id'), nullable=True) - last_update_date = Column(DateTime, server_default=func.now(), nullable=True) - - custom_attributes = Column(JSON) - - class AssetsType(db.Model): __tablename__ = 'assets_type' diff --git a/source/app/post_init.py b/source/app/post_init.py index c1ce27e57..dd297e2af 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -58,7 +58,7 @@ from app.models.authorization import User from app.models.cases import Cases, ReviewStatusList from app.models.cases import CaseState -from app.models.models import Client +from app.models.customers import Client from app.models.models import AnalysisStatus from app.models.models import CaseClassification from app.models.models import ReviewStatus diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 0a3679b40..72b25139a 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -68,7 +68,7 @@ from app.models.models import CaseTasks from app.models.cases import Cases, CaseStatus from app.models.cases import CasesEvent -from app.models.models import Client +from app.models.customers import Client from app.models.comments import Comments from app.models.models import Contact from app.models.models import DataStoreFile From 63a55b6880b1e6211de4c8f940753c8ca646d8e7 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:46:40 +0100 Subject: [PATCH 159/286] Deprecated GET /global/tasks/ --- source/app/blueprints/rest/dashboard_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index 430016023..893dfe4e5 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -153,6 +153,7 @@ def get_gtasks(): @dashboard_rest_blueprint.route('/global/tasks/', methods=['GET']) +@endpoint_deprecated('GET', '/api/v2/global-tasks/{identifier}') @ac_api_requires() def view_gtask(cur_id): task = get_global_task(cur_id) From b854cda896067bc92bbb1eae49cd65aeb7df67f4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:04:27 +0100 Subject: [PATCH 160/286] Moved CaseAssets out of app.models.models into app.models.assets --- ...cb34f7c_add_compromise_status_to_assets.py | 2 +- .../pages/manage/manage_assets_type_routes.py | 2 +- .../rest/case/case_timeline_routes.py | 4 +- .../app/blueprints/rest/dim_tasks_routes.py | 2 +- .../rest/manage/manage_assets_type_routes.py | 3 +- source/app/business/alerts.py | 2 +- source/app/business/asset_types.py | 2 +- source/app/business/assets.py | 2 +- source/app/business/comments.py | 2 +- source/app/datamgmt/alerts/alerts_db.py | 4 +- source/app/datamgmt/case/assets_type.py | 2 +- source/app/datamgmt/case/case_assets_db.py | 4 +- source/app/datamgmt/case/case_events_db.py | 3 +- source/app/datamgmt/case/case_iocs_db.py | 2 +- .../app/datamgmt/manage/manage_assets_db.py | 2 +- .../datamgmt/manage/manage_attribute_db.py | 2 +- .../app/datamgmt/manage/manage_case_objs.py | 2 +- source/app/datamgmt/manage/manage_cases_db.py | 3 +- source/app/datamgmt/reporter/report_db.py | 4 +- source/app/models/alerts.py | 2 +- source/app/models/assets.py | 67 +++++++++++++++++++ source/app/models/customers.py | 10 ++- source/app/models/models.py | 63 ----------------- source/app/post_init.py | 2 +- source/app/schema/marshables.py | 3 +- 25 files changed, 98 insertions(+), 98 deletions(-) create mode 100644 source/app/models/assets.py diff --git a/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py b/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py index 7d711ae9d..f5c08402e 100644 --- a/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py +++ b/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. from app.alembic.alembic_utils import _table_has_column -from app.models.models import CompromiseStatus +from app.models.assets import CompromiseStatus revision = '4ecdfcb34f7c' down_revision = 'a929ef458490' diff --git a/source/app/blueprints/pages/manage/manage_assets_type_routes.py b/source/app/blueprints/pages/manage/manage_assets_type_routes.py index ac142f993..185527940 100644 --- a/source/app/blueprints/pages/manage/manage_assets_type_routes.py +++ b/source/app/blueprints/pages/manage/manage_assets_type_routes.py @@ -26,7 +26,7 @@ from app import app from app.forms import AddAssetForm from app.models.authorization import Permissions -from app.models.models import AssetsType +from app.models.assets import AssetsType from app.blueprints.access_controls import ac_requires from app.blueprints.responses import response_error diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index abd1c5512..d62651c36 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -56,12 +56,10 @@ from app.iris_engine.utils.collab import collab_notify from app.iris_engine.utils.common import parse_bf_date_format from app.iris_engine.utils.tracker import track_activity -from app.models.models import CompromiseStatus +from app.models.assets import CompromiseStatus, AssetsType, CaseAssets from app.models.authorization import CaseAccessLevel from app.models.authorization import User from app.models.cases import CasesEvent -from app.models.models import AssetsType -from app.models.models import CaseAssets from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc from app.models.models import EventCategory diff --git a/source/app/blueprints/rest/dim_tasks_routes.py b/source/app/blueprints/rest/dim_tasks_routes.py index 4e3f6c73c..0264b2c7a 100644 --- a/source/app/blueprints/rest/dim_tasks_routes.py +++ b/source/app/blueprints/rest/dim_tasks_routes.py @@ -23,7 +23,7 @@ from app.models.models import IrisHook from app.models.models import IrisModule from app.models.models import IrisModuleHook -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.models import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases diff --git a/source/app/blueprints/rest/manage/manage_assets_type_routes.py b/source/app/blueprints/rest/manage/manage_assets_type_routes.py index da6476869..7c9f445e6 100644 --- a/source/app/blueprints/rest/manage/manage_assets_type_routes.py +++ b/source/app/blueprints/rest/manage/manage_assets_type_routes.py @@ -28,8 +28,7 @@ from app.datamgmt.manage.manage_case_objs import search_asset_type_by_name from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions -from app.models.models import AssetsType -from app.models.models import CaseAssets +from app.models.assets import AssetsType, CaseAssets from app.schema.marshables import AssetTypeSchema from app.blueprints.access_controls import ac_api_requires from app.blueprints.responses import response_error diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index e37fbcb95..17ceb0d4c 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -25,7 +25,7 @@ from app import socket_io from app.models.alerts import Alert from app.models.iocs import Ioc -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.datamgmt.alerts.alerts_db import cache_similar_alert from app.datamgmt.alerts.alerts_db import delete_similar_alert_cache from app.datamgmt.alerts.alerts_db import delete_related_alerts_cache diff --git a/source/app/business/asset_types.py b/source/app/business/asset_types.py index 55094e0e3..60b417ab8 100644 --- a/source/app/business/asset_types.py +++ b/source/app/business/asset_types.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.models.models import AssetsType +from app.models.assets import AssetsType from app.datamgmt.case.assets_type import add_asset_type from app.datamgmt.case.assets_type import exists_asset_type_with_name diff --git a/source/app/business/assets.py b/source/app/business/assets.py index 67ec4d748..40d56f9cb 100644 --- a/source/app/business/assets.py +++ b/source/app/business/assets.py @@ -23,7 +23,7 @@ from app.models.errors import ObjectNotFoundError from app.business.cases import cases_exists from app.datamgmt.states import update_assets_state -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.pagination_parameters import PaginationParameters from app.datamgmt.case.case_assets_db import get_asset from app.datamgmt.case.case_assets_db import filter_assets diff --git a/source/app/business/comments.py b/source/app/business/comments.py index 35903d5ce..e19333a5b 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -55,7 +55,7 @@ from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.models.comments import Comments -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.models import CaseReceivedFile from app.models.iocs import Ioc from app.models.models import Notes diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 2a26ddf16..c89a2d88e 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -57,10 +57,8 @@ from app.models.cases import Cases from app.models.models import EventCategory from app.models.models import Tags -from app.models.models import AssetsType +from app.models.assets import AssetsType, alert_assets_association, CaseAssets from app.models.comments import Comments -from app.models.models import CaseAssets -from app.models.models import alert_assets_association from app.models.iocs import alert_iocs_association from app.models.iocs import Ioc from app.models.customers import Client diff --git a/source/app/datamgmt/case/assets_type.py b/source/app/datamgmt/case/assets_type.py index 410ac5ea9..d470e4574 100644 --- a/source/app/datamgmt/case/assets_type.py +++ b/source/app/datamgmt/case/assets_type.py @@ -18,7 +18,7 @@ from sqlalchemy import func -from app.models.models import AssetsType +from app.models.assets import AssetsType def get_assets_types(): diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index 8d97d4271..8d56bffcb 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -30,13 +30,11 @@ from app.datamgmt.filtering import get_filtered_data from app.datamgmt.states import update_assets_state from app.models.models import AnalysisStatus -from app.models.models import AssetsType -from app.models.models import CaseAssets from app.models.models import CaseEventsAssets from app.models.cases import Cases, CaseStatus from app.models.comments import Comments from app.models.comments import AssetComments -from app.models.models import CompromiseStatus +from app.models.assets import CompromiseStatus, AssetsType, CaseAssets from app.models.iocs import Ioc from app.models.models import IocAssetLink from app.models.models import IocType diff --git a/source/app/datamgmt/case/case_events_db.py b/source/app/datamgmt/case/case_events_db.py index 9d8f3305b..259e0f4f8 100644 --- a/source/app/datamgmt/case/case_events_db.py +++ b/source/app/datamgmt/case/case_events_db.py @@ -22,8 +22,7 @@ from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.states import update_timeline_state -from app.models.models import AssetsType -from app.models.models import CaseAssets +from app.models.assets import AssetsType, CaseAssets from app.models.models import CaseEventCategory from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index c400cf4b0..b8e75748a 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -27,7 +27,7 @@ from app.models.cases import Cases from app.models.cases import CasesEvent from app.models.customers import Client -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.comments import Comments from app.models.comments import IocComments from app.models.iocs import Ioc diff --git a/source/app/datamgmt/manage/manage_assets_db.py b/source/app/datamgmt/manage/manage_assets_db.py index 5a9524c73..164cdbc0e 100644 --- a/source/app/datamgmt/manage/manage_assets_db.py +++ b/source/app/datamgmt/manage/manage_assets_db.py @@ -6,7 +6,7 @@ from app.datamgmt.manage.manage_cases_db import user_list_cases_view from app.datamgmt.conversions import convert_sort_direction from app.models.cases import Cases -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.customers import Client diff --git a/source/app/datamgmt/manage/manage_attribute_db.py b/source/app/datamgmt/manage/manage_attribute_db.py index 182459a4f..74048eff9 100644 --- a/source/app/datamgmt/manage/manage_attribute_db.py +++ b/source/app/datamgmt/manage/manage_attribute_db.py @@ -22,7 +22,7 @@ from app.db import db from app import app -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.models import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases diff --git a/source/app/datamgmt/manage/manage_case_objs.py b/source/app/datamgmt/manage/manage_case_objs.py index 8ed653fcc..24a80d50d 100644 --- a/source/app/datamgmt/manage/manage_case_objs.py +++ b/source/app/datamgmt/manage/manage_case_objs.py @@ -19,7 +19,7 @@ from app.models.models import AnalysisStatus from app.models.models import IocType -from app.models.models import AssetsType +from app.models.assets import AssetsType from app.models.models import EventCategory diff --git a/source/app/datamgmt/manage/manage_cases_db.py b/source/app/datamgmt/manage/manage_cases_db.py index 774b42691..9512d03d2 100644 --- a/source/app/datamgmt/manage/manage_cases_db.py +++ b/source/app/datamgmt/manage/manage_cases_db.py @@ -32,10 +32,9 @@ from app.datamgmt.conversions import convert_sort_direction from app.datamgmt.authorization import has_deny_all_access_level from app.datamgmt.states import delete_case_states -from app.models.models import CaseAssets from app.models.models import NoteRevisions from app.models.models import CaseClassification -from app.models.models import alert_assets_association +from app.models.assets import alert_assets_association, CaseAssets from app.models.models import TaskAssignee from app.models.models import NoteDirectory from app.models.models import Tags diff --git a/source/app/datamgmt/reporter/report_db.py b/source/app/datamgmt/reporter/report_db.py index e4690a72e..280ea2f39 100644 --- a/source/app/datamgmt/reporter/report_db.py +++ b/source/app/datamgmt/reporter/report_db.py @@ -23,10 +23,8 @@ from app.datamgmt.case.case_notes_db import get_notes_from_group from app.datamgmt.case.case_notes_db import get_case_note_comments from app.models.models import AnalysisStatus -from app.models.models import CompromiseStatus +from app.models.assets import CompromiseStatus, AssetsType, CaseAssets from app.models.models import TaskAssignee -from app.models.models import AssetsType -from app.models.models import CaseAssets from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc from app.models.models import CaseReceivedFile diff --git a/source/app/models/alerts.py b/source/app/models/alerts.py index 8dc623c06..1723629e1 100644 --- a/source/app/models/alerts.py +++ b/source/app/models/alerts.py @@ -32,7 +32,7 @@ from sqlalchemy.orm import relationship from app.db import db -from app.models.models import alert_assets_association +from app.models.assets import alert_assets_association from app.models.iocs import alert_iocs_association diff --git a/source/app/models/assets.py b/source/app/models/assets.py new file mode 100644 index 000000000..7db17caf3 --- /dev/null +++ b/source/app/models/assets.py @@ -0,0 +1,67 @@ +import enum + +from sqlalchemy import Column, Integer, String, Table, ForeignKey, BigInteger, UUID, text, Text, DateTime +from sqlalchemy.dialects.postgresql import JSON, JSONB +from sqlalchemy.orm import relationship + +from app import db + + +class CompromiseStatus(enum.Enum): + to_be_determined = 0x0 + compromised = 0x1 + not_compromised = 0x2 + unknown = 0x3 + + @classmethod + def has_value(cls, value): + return value in cls._value2member_map_ + + +class AssetsType(db.Model): + __tablename__ = 'assets_type' + + asset_id = Column(Integer, primary_key=True) + asset_name = Column(String(155)) + asset_description = Column(String(255)) + asset_icon_not_compromised = Column(String(255)) + asset_icon_compromised = Column(String(255)) + + +alert_assets_association = Table( + 'alert_assets_association', + db.Model.metadata, + Column('alert_id', ForeignKey('alerts.alert_id'), primary_key=True), + Column('asset_id', ForeignKey('case_assets.asset_id'), primary_key=True) +) + + +class CaseAssets(db.Model): + __tablename__ = 'case_assets' + + asset_id = Column(BigInteger, primary_key=True) + asset_uuid = Column(UUID(as_uuid=True), server_default=text("gen_random_uuid()"), nullable=False) + asset_name = Column(Text) + asset_description = Column(Text) + asset_domain = Column(Text) + asset_ip = Column(Text) + asset_info = Column(Text) + asset_compromise_status_id = Column(Integer, nullable=True) + asset_type_id = Column(ForeignKey('assets_type.asset_id')) + asset_tags = Column(Text) + case_id = Column(ForeignKey('cases.case_id')) + date_added = Column(DateTime) + date_update = Column(DateTime) + user_id = Column(ForeignKey('user.id')) + analysis_status_id = Column(ForeignKey('analysis_status.id')) + custom_attributes = Column(JSON) + asset_enrichment = Column(JSONB) + modification_history = Column(JSON) + + case = relationship('Cases') + user = relationship('User') + asset_type = relationship('AssetsType') + analysis_status = relationship('AnalysisStatus') + + alerts = relationship('Alert', secondary=alert_assets_association, back_populates='assets') + iocs = relationship('IocAssetLink', back_populates='asset') diff --git a/source/app/models/customers.py b/source/app/models/customers.py index 5acd16c86..8a89da120 100644 --- a/source/app/models/customers.py +++ b/source/app/models/customers.py @@ -15,7 +15,15 @@ # 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 sqlalchemy import Column, BigInteger, UUID, text, Text, DateTime, func, ForeignKey + +from sqlalchemy import Column +from sqlalchemy import BigInteger +from sqlalchemy import UUID +from sqlalchemy import text +from sqlalchemy import Text +from sqlalchemy import DateTime +from sqlalchemy import func +from sqlalchemy import ForeignKey from sqlalchemy.dialects.postgresql import JSON from app import db diff --git a/source/app/models/models.py b/source/app/models/models.py index 9bf69f351..01af2f8f0 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -17,12 +17,10 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import datetime -import enum import uuid from sqlalchemy import BigInteger from sqlalchemy import UniqueConstraint -from sqlalchemy import Table from sqlalchemy import Boolean from sqlalchemy import Column from sqlalchemy import DateTime @@ -36,7 +34,6 @@ from sqlalchemy import create_engine from sqlalchemy import text from sqlalchemy.dialects.postgresql import JSON -from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship @@ -50,17 +47,6 @@ metadata = Base.metadata -class CompromiseStatus(enum.Enum): - to_be_determined = 0x0 - compromised = 0x1 - not_compromised = 0x2 - unknown = 0x3 - - @classmethod - def has_value(cls, value): - return value in cls._value2member_map_ - - def create_safe(session, model, **kwargs): instance = session.query(model).filter_by(**kwargs).first() if instance: @@ -88,55 +74,6 @@ def create_safe_limited(session, model, keywords_list, **kwargs): return True -class AssetsType(db.Model): - __tablename__ = 'assets_type' - - asset_id = Column(Integer, primary_key=True) - asset_name = Column(String(155)) - asset_description = Column(String(255)) - asset_icon_not_compromised = Column(String(255)) - asset_icon_compromised = Column(String(255)) - - -alert_assets_association = Table( - 'alert_assets_association', - db.Model.metadata, - Column('alert_id', ForeignKey('alerts.alert_id'), primary_key=True), - Column('asset_id', ForeignKey('case_assets.asset_id'), primary_key=True) -) - - -class CaseAssets(db.Model): - __tablename__ = 'case_assets' - - asset_id = Column(BigInteger, primary_key=True) - asset_uuid = Column(UUID(as_uuid=True), server_default=text("gen_random_uuid()"), nullable=False) - asset_name = Column(Text) - asset_description = Column(Text) - asset_domain = Column(Text) - asset_ip = Column(Text) - asset_info = Column(Text) - asset_compromise_status_id = Column(Integer, nullable=True) - asset_type_id = Column(ForeignKey('assets_type.asset_id')) - asset_tags = Column(Text) - case_id = Column(ForeignKey('cases.case_id')) - date_added = Column(DateTime) - date_update = Column(DateTime) - user_id = Column(ForeignKey('user.id')) - analysis_status_id = Column(ForeignKey('analysis_status.id')) - custom_attributes = Column(JSON) - asset_enrichment = Column(JSONB) - modification_history = Column(JSON) - - case = relationship('Cases') - user = relationship('User') - asset_type = relationship('AssetsType') - analysis_status = relationship('AnalysisStatus') - - alerts = relationship('Alert', secondary=alert_assets_association, back_populates='assets') - iocs = relationship('IocAssetLink', back_populates='asset') - - class AnalysisStatus(db.Model): __tablename__ = 'analysis_status' diff --git a/source/app/post_init.py b/source/app/post_init.py index dd297e2af..b8e46af23 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -49,7 +49,7 @@ 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 -from app.models.models import AssetsType +from app.models.assets import AssetsType from app.models.alerts import Severity from app.models.alerts import AlertStatus from app.models.alerts import AlertResolutionStatus diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 72b25139a..201809f45 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -62,8 +62,7 @@ from app.models.models import EvidenceTypes from app.models.models import NoteDirectory from app.models.models import NoteRevisions -from app.models.models import AssetsType -from app.models.models import CaseAssets +from app.models.assets import AssetsType, CaseAssets from app.models.models import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases, CaseStatus From 50f88d13c6331503eec390a670f877933923044f Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:05:18 +0100 Subject: [PATCH 161/286] Moved AnalysisStatus in app.models.assets --- source/app/blueprints/rest/case/case_assets_routes.py | 2 +- .../rest/manage/manage_analysis_status_routes.py | 2 +- source/app/datamgmt/case/case_assets_db.py | 3 +-- source/app/datamgmt/manage/manage_case_objs.py | 3 +-- source/app/datamgmt/reporter/report_db.py | 3 +-- source/app/models/assets.py | 7 +++++++ source/app/models/models.py | 7 ------- source/app/post_init.py | 3 +-- source/app/schema/marshables.py | 3 +-- 9 files changed, 14 insertions(+), 19 deletions(-) diff --git a/source/app/blueprints/rest/case/case_assets_routes.py b/source/app/blueprints/rest/case/case_assets_routes.py index f5275cc96..a3eb893ad 100644 --- a/source/app/blueprints/rest/case/case_assets_routes.py +++ b/source/app/blueprints/rest/case/case_assets_routes.py @@ -49,7 +49,7 @@ from app.datamgmt.states import get_assets_state from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity -from app.models.models import AnalysisStatus +from app.models.assets import AnalysisStatus from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseAssetsSchema from app.schema.marshables import CommentSchema diff --git a/source/app/blueprints/rest/manage/manage_analysis_status_routes.py b/source/app/blueprints/rest/manage/manage_analysis_status_routes.py index 17ac1f891..e729476c1 100644 --- a/source/app/blueprints/rest/manage/manage_analysis_status_routes.py +++ b/source/app/blueprints/rest/manage/manage_analysis_status_routes.py @@ -22,7 +22,7 @@ from app.datamgmt.case.case_assets_db import get_compromise_status_dict from app.datamgmt.case.case_assets_db import get_case_outcome_status_dict from app.datamgmt.manage.manage_case_objs import search_analysis_status_by_name -from app.models.models import AnalysisStatus +from app.models.assets import AnalysisStatus from app.schema.marshables import AnalysisStatusSchema from app.blueprints.access_controls import ac_api_requires from app.blueprints.responses import response_error diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index 8d56bffcb..4a3ec616c 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -29,12 +29,11 @@ from app.blueprints.iris_user import iris_current_user from app.datamgmt.filtering import get_filtered_data from app.datamgmt.states import update_assets_state -from app.models.models import AnalysisStatus from app.models.models import CaseEventsAssets from app.models.cases import Cases, CaseStatus from app.models.comments import Comments from app.models.comments import AssetComments -from app.models.assets import CompromiseStatus, AssetsType, CaseAssets +from app.models.assets import CompromiseStatus, AssetsType, CaseAssets, AnalysisStatus from app.models.iocs import Ioc from app.models.models import IocAssetLink from app.models.models import IocType diff --git a/source/app/datamgmt/manage/manage_case_objs.py b/source/app/datamgmt/manage/manage_case_objs.py index 24a80d50d..361bf16d1 100644 --- a/source/app/datamgmt/manage/manage_case_objs.py +++ b/source/app/datamgmt/manage/manage_case_objs.py @@ -17,9 +17,8 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import func -from app.models.models import AnalysisStatus from app.models.models import IocType -from app.models.assets import AssetsType +from app.models.assets import AssetsType, AnalysisStatus from app.models.models import EventCategory diff --git a/source/app/datamgmt/reporter/report_db.py b/source/app/datamgmt/reporter/report_db.py index 280ea2f39..1a94b19da 100644 --- a/source/app/datamgmt/reporter/report_db.py +++ b/source/app/datamgmt/reporter/report_db.py @@ -22,8 +22,7 @@ from app.datamgmt.case.case_notes_db import get_notes_from_group from app.datamgmt.case.case_notes_db import get_case_note_comments -from app.models.models import AnalysisStatus -from app.models.assets import CompromiseStatus, AssetsType, CaseAssets +from app.models.assets import CompromiseStatus, AssetsType, CaseAssets, AnalysisStatus from app.models.models import TaskAssignee from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc diff --git a/source/app/models/assets.py b/source/app/models/assets.py index 7db17caf3..69b4bbb7c 100644 --- a/source/app/models/assets.py +++ b/source/app/models/assets.py @@ -65,3 +65,10 @@ class CaseAssets(db.Model): alerts = relationship('Alert', secondary=alert_assets_association, back_populates='assets') iocs = relationship('IocAssetLink', back_populates='asset') + + +class AnalysisStatus(db.Model): + __tablename__ = 'analysis_status' + + id = Column(Integer, primary_key=True) + name = Column(Text) diff --git a/source/app/models/models.py b/source/app/models/models.py index 01af2f8f0..0cba17598 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -74,13 +74,6 @@ def create_safe_limited(session, model, keywords_list, **kwargs): return True -class AnalysisStatus(db.Model): - __tablename__ = 'analysis_status' - - id = Column(Integer, primary_key=True) - name = Column(Text) - - class CaseClassification(db.Model): __tablename__ = 'case_classification' diff --git a/source/app/post_init.py b/source/app/post_init.py index b8e46af23..9ee8cbd28 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -49,7 +49,7 @@ 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 -from app.models.assets import AssetsType +from app.models.assets import AssetsType, AnalysisStatus from app.models.alerts import Severity from app.models.alerts import AlertStatus from app.models.alerts import AlertResolutionStatus @@ -59,7 +59,6 @@ from app.models.cases import Cases, ReviewStatusList from app.models.cases import CaseState from app.models.customers import Client -from app.models.models import AnalysisStatus from app.models.models import CaseClassification from app.models.models import ReviewStatus from app.models.models import EvidenceTypes diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 201809f45..9c1aeda0b 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -52,7 +52,6 @@ from app.datamgmt.manage.manage_tags_db import add_db_tag from app.datamgmt.case.case_iocs_db import get_ioc_links from app.iris_engine.access_control.utils import ac_mask_from_val_list -from app.models.models import AnalysisStatus from app.models.models import CaseClassification from app.models.models import SavedFilter from app.models.models import DataStorePath @@ -62,7 +61,7 @@ from app.models.models import EvidenceTypes from app.models.models import NoteDirectory from app.models.models import NoteRevisions -from app.models.assets import AssetsType, CaseAssets +from app.models.assets import AssetsType, CaseAssets, AnalysisStatus from app.models.models import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases, CaseStatus From c846f0e185d70e243943ed181a976ea72b7e0332 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:06:55 +0100 Subject: [PATCH 162/286] Moved CaseClassification into app.models.cases --- .../manage/manage_case_classifications_db.py | 2 +- source/app/datamgmt/manage/manage_cases_db.py | 3 +-- source/app/models/cases.py | 16 +++++++++++++++- source/app/models/models.py | 13 ------------- source/app/post_init.py | 3 +-- source/app/schema/marshables.py | 3 +-- 6 files changed, 19 insertions(+), 21 deletions(-) diff --git a/source/app/datamgmt/manage/manage_case_classifications_db.py b/source/app/datamgmt/manage/manage_case_classifications_db.py index 09eb1fca8..9bdb998c2 100644 --- a/source/app/datamgmt/manage/manage_case_classifications_db.py +++ b/source/app/datamgmt/manage/manage_case_classifications_db.py @@ -17,7 +17,7 @@ from sqlalchemy import func from typing import List -from app.models.models import CaseClassification +from app.models.cases import CaseClassification def get_case_classifications_list() -> List[dict]: diff --git a/source/app/datamgmt/manage/manage_cases_db.py b/source/app/datamgmt/manage/manage_cases_db.py index 9512d03d2..6b43bf1a4 100644 --- a/source/app/datamgmt/manage/manage_cases_db.py +++ b/source/app/datamgmt/manage/manage_cases_db.py @@ -33,7 +33,6 @@ from app.datamgmt.authorization import has_deny_all_access_level from app.datamgmt.states import delete_case_states from app.models.models import NoteRevisions -from app.models.models import CaseClassification from app.models.assets import alert_assets_association, CaseAssets from app.models.models import TaskAssignee from app.models.models import NoteDirectory @@ -43,7 +42,7 @@ from app.models.models import CaseEventsIoc from app.models.models import CaseReceivedFile from app.models.models import CaseTasks -from app.models.cases import Cases, CaseStatus +from app.models.cases import Cases, CaseStatus, CaseClassification from app.models.cases import CasesEvent from app.models.customers import Client from app.models.models import DataStoreFile diff --git a/source/app/models/cases.py b/source/app/models/cases.py index cb9cd797f..99c5f424a 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -20,7 +20,7 @@ import uuid from datetime import datetime -from sqlalchemy import BigInteger +from sqlalchemy import BigInteger, Column, Integer, Text, DateTime, func, ForeignKey from sqlalchemy import CheckConstraint from sqlalchemy import Boolean from sqlalchemy import Column @@ -38,6 +38,7 @@ from sqlalchemy.orm import relationship from sqlalchemy.orm import backref +from app import db from app.db import db from app.blueprints.iris_user import iris_current_user @@ -201,3 +202,16 @@ class ReviewStatusList: pending_review = "Pending review" review_in_progress = "Review in progress" reviewed = "Reviewed" + + +class CaseClassification(db.Model): + __tablename__ = 'case_classification' + + id = Column(Integer, primary_key=True) + name = Column(Text) + name_expanded = Column(Text) + description = Column(Text) + creation_date = Column(DateTime, server_default=func.now(), nullable=True) + created_by_id = Column(ForeignKey('user.id'), nullable=True) + + created_by = relationship('User') diff --git a/source/app/models/models.py b/source/app/models/models.py index 0cba17598..6fb87045c 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -74,19 +74,6 @@ def create_safe_limited(session, model, keywords_list, **kwargs): return True -class CaseClassification(db.Model): - __tablename__ = 'case_classification' - - id = Column(Integer, primary_key=True) - name = Column(Text) - name_expanded = Column(Text) - description = Column(Text) - creation_date = Column(DateTime, server_default=func.now(), nullable=True) - created_by_id = Column(ForeignKey('user.id'), nullable=True) - - created_by = relationship('User') - - class EvidenceTypes(db.Model): __tablename__ = 'evidence_type' diff --git a/source/app/post_init.py b/source/app/post_init.py index 9ee8cbd28..c11f6fdc5 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -56,10 +56,9 @@ from app.models.authorization import CaseAccessLevel from app.models.authorization import Group from app.models.authorization import User -from app.models.cases import Cases, ReviewStatusList +from app.models.cases import Cases, ReviewStatusList, CaseClassification from app.models.cases import CaseState from app.models.customers import Client -from app.models.models import CaseClassification from app.models.models import ReviewStatus from app.models.models import EvidenceTypes from app.models.models import EventCategory diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 9c1aeda0b..71b015571 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -52,7 +52,6 @@ from app.datamgmt.manage.manage_tags_db import add_db_tag from app.datamgmt.case.case_iocs_db import get_ioc_links from app.iris_engine.access_control.utils import ac_mask_from_val_list -from app.models.models import CaseClassification from app.models.models import SavedFilter from app.models.models import DataStorePath from app.models.models import IrisModuleHook @@ -64,7 +63,7 @@ from app.models.assets import AssetsType, CaseAssets, AnalysisStatus from app.models.models import CaseReceivedFile from app.models.models import CaseTasks -from app.models.cases import Cases, CaseStatus +from app.models.cases import Cases, CaseStatus, CaseClassification from app.models.cases import CasesEvent from app.models.customers import Client from app.models.comments import Comments From b252df8c8c91d21901565f350130b0d469740459 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:24:34 +0100 Subject: [PATCH 163/286] Fixed ruff warnings --- source/app/models/assets.py | 34 +++++++++++++++++++++++++++++++--- source/app/models/cases.py | 4 ++-- source/app/models/customers.py | 2 +- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/source/app/models/assets.py b/source/app/models/assets.py index 69b4bbb7c..b2f86ba7b 100644 --- a/source/app/models/assets.py +++ b/source/app/models/assets.py @@ -1,10 +1,38 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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. + import enum -from sqlalchemy import Column, Integer, String, Table, ForeignKey, BigInteger, UUID, text, Text, DateTime -from sqlalchemy.dialects.postgresql import JSON, JSONB +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy import ForeignKey +from sqlalchemy import BigInteger +from sqlalchemy import UUID +from sqlalchemy import text +from sqlalchemy import Text +from sqlalchemy import DateTime +from sqlalchemy.dialects.postgresql import JSON +from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import relationship -from app import db +from app.db import db class CompromiseStatus(enum.Enum): diff --git a/source/app/models/cases.py b/source/app/models/cases.py index 99c5f424a..a6dcadd0b 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -20,7 +20,8 @@ import uuid from datetime import datetime -from sqlalchemy import BigInteger, Column, Integer, Text, DateTime, func, ForeignKey +from sqlalchemy import BigInteger +from sqlalchemy import func from sqlalchemy import CheckConstraint from sqlalchemy import Boolean from sqlalchemy import Column @@ -38,7 +39,6 @@ from sqlalchemy.orm import relationship from sqlalchemy.orm import backref -from app import db from app.db import db from app.blueprints.iris_user import iris_current_user diff --git a/source/app/models/customers.py b/source/app/models/customers.py index 8a89da120..39b699093 100644 --- a/source/app/models/customers.py +++ b/source/app/models/customers.py @@ -26,7 +26,7 @@ from sqlalchemy import ForeignKey from sqlalchemy.dialects.postgresql import JSON -from app import db +from app.db import db class Client(db.Model): From 8430792fb4d90ad0d2c4e2c814b83cae8d9a9fec Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 09:05:39 +0100 Subject: [PATCH 164/286] Started implementation of DELETE /api/v2/global-tasks/{identifier} --- source/app/blueprints/rest/v2/global_tasks.py | 25 ++++++++++++++++--- source/app/business/global_tasks.py | 10 ++++++++ source/app/datamgmt/global_tasks.py | 25 +++++++++++++++++++ tests/tests_rest_global_tasks.py | 7 ++++++ 4 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 source/app/datamgmt/global_tasks.py diff --git a/source/app/blueprints/rest/v2/global_tasks.py b/source/app/blueprints/rest/v2/global_tasks.py index a914336d1..fff24ee21 100644 --- a/source/app/blueprints/rest/v2/global_tasks.py +++ b/source/app/blueprints/rest/v2/global_tasks.py @@ -28,8 +28,10 @@ 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_not_found +from app.blueprints.rest.endpoints import response_api_deleted from app.business.global_tasks import global_tasks_create from app.business.global_tasks import global_tasks_get +from app.business.global_tasks import global_tasks_delete from app.models.errors import ObjectNotFoundError @@ -50,12 +52,21 @@ def create(self): def read(self, identifier): try: - customer = global_tasks_get(identifier) - result = self._schema.dump(customer) + global_task = global_tasks_get(identifier) + result = self._schema.dump(global_task) return response_api_success(result) except ObjectNotFoundError: return response_api_not_found() + def delete(self, identifier): + try: + global_task = global_tasks_get(identifier) + global_tasks_delete(global_task) + return response_api_deleted() + + except ObjectNotFoundError: + return response_api_not_found() + global_tasks_blueprint = Blueprint('global_tasks_rest_v2', __name__, url_prefix='/global-tasks') @@ -64,11 +75,17 @@ def read(self, identifier): @global_tasks_blueprint.post('') @ac_api_requires() -def create_customer(): +def create_global_task(): return global_tasks_operations.create() @global_tasks_blueprint.get('/') @ac_api_requires() -def get_customer(identifier): +def get_global_task(identifier): return global_tasks_operations.read(identifier) + + +@global_tasks_blueprint.delete('/') +@ac_api_requires() +def delete_global_task(identifier): + return global_tasks_operations.delete(identifier) diff --git a/source/app/business/global_tasks.py b/source/app/business/global_tasks.py index 7c796493c..9f0d07cb4 100644 --- a/source/app/business/global_tasks.py +++ b/source/app/business/global_tasks.py @@ -23,6 +23,7 @@ from app.iris_engine.utils.tracker import track_activity from app.datamgmt.db_operations import db_create from app.datamgmt.dashboard.dashboard_db import get_global_task_by_identifier +from app.datamgmt.global_tasks import delete_global_task def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: @@ -44,3 +45,12 @@ def global_tasks_get(identifier) -> GlobalTasks: if not task: raise ObjectNotFoundError() return task + + +def global_tasks_delete(task: GlobalTasks): + call_modules_hook('on_preload_global_task_delete', task.id) + + delete_global_task(task) + + call_modules_hook('on_postload_global_task_delete', task) + track_activity(f'deleted global task ID {task.id}') diff --git a/source/app/datamgmt/global_tasks.py b/source/app/datamgmt/global_tasks.py new file mode 100644 index 000000000..d878cc5bf --- /dev/null +++ b/source/app/datamgmt/global_tasks.py @@ -0,0 +1,25 @@ +# IRIS Source Code +# Copyright (C) ${current_year} - 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.db import db +from app.models.models import GlobalTasks + + +def delete_global_task(task: GlobalTasks): + GlobalTasks.query.filter(GlobalTasks.id == task.id).delete() + db.session.commit() diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py index eaef193f9..3617db1d6 100644 --- a/tests/tests_rest_global_tasks.py +++ b/tests/tests_rest_global_tasks.py @@ -47,3 +47,10 @@ def test_get_global_task_should_return_field_task_id(self): identifier = response['task_id'] response = self._subject.get(f'/api/v2/global-tasks/{identifier}').json() self.assertEqual(identifier, response['task_id']) + + def test_delete_global_task_should_return_204(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + response = self._subject.delete(f'/api/v2/global-tasks/{identifier}') + self.assertEqual(204, response.status_code) From 72457f4f0d2f5e4f2f2cfecc847c8c11f0e7334b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 09:13:51 +0100 Subject: [PATCH 165/286] Made calls more uniform --- source/app/business/alerts.py | 10 +++++----- source/app/business/assets.py | 6 +++--- source/app/business/cases.py | 10 +++++----- source/app/business/comments.py | 14 +++++++------- source/app/business/evidences.py | 8 ++++---- source/app/business/global_tasks.py | 2 +- source/app/business/iocs.py | 8 ++++---- source/app/business/notes.py | 8 ++++---- source/app/business/reports/reports.py | 10 +++++----- source/app/business/tasks.py | 8 ++++---- 10 files changed, 42 insertions(+), 42 deletions(-) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 17ceb0d4c..7acc44454 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -85,7 +85,7 @@ def alerts_create(alert: Alert, iocs: list[Ioc], assets: list[CaseAssets]) -> Al cache_similar_alert(alert.alert_customer_id, assets, iocs, alert.alert_id, alert.alert_source_event_time) - alert = call_modules_hook('on_postload_alert_create', data=alert) + alert = call_modules_hook('on_postload_alert_create', alert) track_activity(f"created alert #{alert.alert_id} - {alert.alert_title}", ctx_less=True) @@ -135,13 +135,13 @@ def alerts_update(alert: Alert, updated_alert: Alert, activity_data) -> Alert: if alert.alert_status_id != updated_alert.alert_status_id: do_status_hook = True - updated_alert = call_modules_hook('on_postload_alert_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_update', updated_alert) if do_resolution_hook: - updated_alert = call_modules_hook('on_postload_alert_resolution_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_resolution_update', updated_alert) if do_status_hook: - updated_alert = call_modules_hook('on_postload_alert_status_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_status_update', updated_alert) if activity_data: activity_data_as_string = ','.join(activity_data) @@ -161,5 +161,5 @@ def alerts_delete(alert: Alert): delete_related_alerts_cache([alert.alert_id]) delete_alert(alert) - call_modules_hook('on_postload_alert_delete', data=alert.alert_id) + call_modules_hook('on_postload_alert_delete', alert.alert_id) track_activity(f'delete alert #{alert.alert_id}', ctx_less=True) diff --git a/source/app/business/assets.py b/source/app/business/assets.py index 40d56f9cb..13ec08193 100644 --- a/source/app/business/assets.py +++ b/source/app/business/assets.py @@ -47,7 +47,7 @@ def assets_create(user, case_identifier, asset: CaseAssets, ioc_links): errors, _ = set_ioc_links(ioc_links, asset.asset_id) if errors: raise BusinessProcessingError('Encountered errors while linking IOC. Asset has still been created.') - asset = call_modules_hook('on_postload_asset_create', data=asset, caseid=case_identifier) + asset = call_modules_hook('on_postload_asset_create', asset, caseid=case_identifier) add_obj_history_entry(asset, 'created') @@ -59,10 +59,10 @@ def assets_create(user, case_identifier, asset: CaseAssets, ioc_links): def assets_delete(asset: CaseAssets): - call_modules_hook('on_preload_asset_delete', data=asset.asset_id) + call_modules_hook('on_preload_asset_delete', asset.asset_id) # Deletes an asset and the potential links with the IoCs from the database delete_asset(asset) - call_modules_hook('on_postload_asset_delete', data=asset.asset_id, caseid=asset.case_id) + call_modules_hook('on_postload_asset_delete', asset.asset_id, caseid=asset.case_id) track_activity(f'removed asset ID {asset.asset_name}', caseid=asset.case_id) diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 0733c472b..c74ae7f6d 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -132,7 +132,7 @@ def cases_create(user, case: Cases, case_template_id) -> Cases: 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) + case = call_modules_hook('on_postload_case_create', case) add_obj_history_entry(case, 'created') track_activity(f'new case "{case.name}" created', caseid=case.case_id, ctx_less=False) @@ -148,12 +148,12 @@ def cases_delete(case_identifier): raise BusinessProcessingError('Cannot delete a primary case to keep consistency') try: - call_modules_hook('on_preload_case_delete', data=case_identifier, caseid=case_identifier) + call_modules_hook('on_preload_case_delete', case_identifier, caseid=case_identifier) if not delete_case(case_identifier): track_activity(f'tried to delete case {case_identifier}, but it doesn\'t exist', caseid=case_identifier, ctx_less=True) raise BusinessProcessingError('Tried to delete a non-existing case') - call_modules_hook('on_postload_case_delete', data=case_identifier, caseid=case_identifier) + call_modules_hook('on_postload_case_delete', case_identifier, caseid=case_identifier) track_activity(f'case {case_identifier} deleted successfully', ctx_less=True) except Exception as e: logger.exception(e) @@ -182,11 +182,11 @@ def cases_update(case: Cases, updated_case, protagonists, tags) -> Cases: for alert in updated_case.alerts: if alert.alert_status_id != close_status.status_id: alert.alert_status_id = close_status.status_id - alert = call_modules_hook('on_postload_alert_update', data=alert, caseid=case.case_id) + alert = call_modules_hook('on_postload_alert_update', alert, caseid=case.case_id) if alert.alert_resolution_status_id != case_status_id_mapped: alert.alert_resolution_status_id = case_status_id_mapped - alert = call_modules_hook('on_postload_alert_resolution_update', data=alert, + alert = call_modules_hook('on_postload_alert_resolution_update', alert, caseid=case.case_id) track_activity( diff --git a/source/app/business/comments.py b/source/app/business/comments.py index e19333a5b..a458f4c1e 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -115,7 +115,7 @@ def comments_update_for_case(current_user, comment_text, comment_id, object_type if hook.endswith('s'): hook = hook[:-1] - call_modules_hook(f'on_postload_{hook}_comment_update', data=comment, caseid=caseid) + call_modules_hook(f'on_postload_{hook}_comment_update', comment, caseid=caseid) track_activity(f'comment {comment.comment_id} on {object_type} edited', caseid=caseid) return comment @@ -152,7 +152,7 @@ def comments_create_for_asset(current_user, asset: CaseAssets, comment: Comments 'comment': comment, 'asset': asset } - call_modules_hook('on_postload_asset_commented', data=hook_data, caseid=asset.case_id) + call_modules_hook('on_postload_asset_commented', hook_data, caseid=asset.case_id) track_activity(f'asset "{asset.asset_name}" commented', caseid=asset.case_id) @@ -168,7 +168,7 @@ def comments_create_for_evidence(current_user, evidence: CaseReceivedFile, comme 'comment': comment, 'evidence': evidence } - call_modules_hook('on_postload_evidence_commented', data=hook_data, caseid=evidence.case_id) + call_modules_hook('on_postload_evidence_commented', hook_data, caseid=evidence.case_id) track_activity(f'evidence "{evidence.filename}" commented', caseid=evidence.case_id) @@ -183,7 +183,7 @@ def comments_create_for_ioc(current_user, ioc: Ioc, comment: Comments): 'comment': comment, 'ioc': ioc } - call_modules_hook('on_postload_ioc_commented', data=hook_data, caseid=ioc.case_id) + call_modules_hook('on_postload_ioc_commented', hook_data, caseid=ioc.case_id) track_activity(f'ioc "{ioc.ioc_value}" commented', caseid=ioc.case_id) @@ -198,7 +198,7 @@ def comments_create_for_note(current_user, note: Notes, comment: Comments): 'comment': comment, 'note': note } - call_modules_hook('on_postload_note_commented', data=hook_data, caseid=note.note_case_id) + call_modules_hook('on_postload_note_commented', hook_data, caseid=note.note_case_id) track_activity(f'note "{note.note_title}" commented', caseid=note.note_case_id) @@ -214,7 +214,7 @@ def comments_create_for_task(current_user, task: CaseTasks, comment: Comments): 'comment': comment, 'task': task } - call_modules_hook('on_postload_task_commented', data=hook_data, caseid=task.task_case_id) + call_modules_hook('on_postload_task_commented', hook_data, caseid=task.task_case_id) track_activity(f'task "{task.task_title}" commented', caseid=task.task_case_id) @@ -232,7 +232,7 @@ def comments_create_for_event(current_user, event: CasesEvent, comment: Comments 'comment': comment, 'event': event } - call_modules_hook('on_postload_event_commented', data=hook_data, caseid=event.case_id) + call_modules_hook('on_postload_event_commented', hook_data, caseid=event.case_id) track_activity(f'event "{event.event_title}" commented', caseid=event.case_id) diff --git a/source/app/business/evidences.py b/source/app/business/evidences.py index b531e5083..9a07f74fe 100644 --- a/source/app/business/evidences.py +++ b/source/app/business/evidences.py @@ -35,7 +35,7 @@ def evidences_create(case_identifier, evidence: CaseReceivedFile) -> CaseReceivedFile: crf = add_rfile(evidence, case_identifier, iris_current_user.id) - crf = call_modules_hook('on_postload_evidence_create', data=crf, caseid=case_identifier) + crf = call_modules_hook('on_postload_evidence_create', crf, caseid=case_identifier) if not crf: raise BusinessProcessingError('Unable to create evidence for internal reasons') @@ -52,7 +52,7 @@ def evidences_get(identifier) -> CaseReceivedFile: def evidences_update(evidence: CaseReceivedFile) -> CaseReceivedFile: evidence = update_rfile(evidence=evidence, user_id=iris_current_user.id, caseid=evidence.case_id) - evidence = call_modules_hook('on_postload_evidence_update', data=evidence, caseid=evidence.case_id) + evidence = call_modules_hook('on_postload_evidence_update', evidence, caseid=evidence.case_id) if not evidence: raise BusinessProcessingError('Unable to update task for internal reasons') track_activity(f'updated evidence "{evidence.filename}"', caseid=evidence.case_id) @@ -67,7 +67,7 @@ def evidences_filter(case_identifier, pagination_parameters: PaginationParameter def evidences_delete(evidence: CaseReceivedFile): - call_modules_hook('on_preload_evidence_delete', data=evidence.id, caseid=evidence.case_id) + call_modules_hook('on_preload_evidence_delete', evidence.id, caseid=evidence.case_id) delete_rfile(evidence) - call_modules_hook('on_postload_evidence_delete', data=evidence.id, caseid=evidence.case_id) + call_modules_hook('on_postload_evidence_delete', evidence.id, caseid=evidence.case_id) track_activity(f'deleted evidence "{evidence.filename}" from registry', evidence.case_id) diff --git a/source/app/business/global_tasks.py b/source/app/business/global_tasks.py index 9f0d07cb4..14d67546e 100644 --- a/source/app/business/global_tasks.py +++ b/source/app/business/global_tasks.py @@ -34,7 +34,7 @@ def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: db_create(global_task) - global_task = call_modules_hook('on_postload_global_task_create', data=global_task) + global_task = call_modules_hook('on_postload_global_task_create', global_task) track_activity(f'created new global task "{global_task.task_title}"') return global_task diff --git a/source/app/business/iocs.py b/source/app/business/iocs.py index 6582235dd..7247a991d 100644 --- a/source/app/business/iocs.py +++ b/source/app/business/iocs.py @@ -56,7 +56,7 @@ def iocs_create(ioc: Ioc): add_ioc(ioc, iris_current_user.id, ioc.case_id) - ioc = call_modules_hook('on_postload_ioc_create', data=ioc, caseid=ioc.case_id) + ioc = call_modules_hook('on_postload_ioc_create', ioc, caseid=ioc.case_id) if ioc: track_activity(f'added ioc "{ioc.ioc_value}"', caseid=ioc.case_id) @@ -81,7 +81,7 @@ def iocs_update(ioc: Ioc, ioc_sc: Ioc) -> (Ioc, str): add_obj_history_entry(ioc, 'updated ioc') db.session.commit() - ioc_sc = call_modules_hook('on_postload_ioc_update', data=ioc_sc, caseid=ioc.case_id) + ioc_sc = call_modules_hook('on_postload_ioc_update', ioc_sc, caseid=ioc.case_id) if ioc_sc: track_activity(f'updated ioc "{ioc_sc.ioc_value}"', caseid=ioc.case_id) @@ -94,11 +94,11 @@ def iocs_update(ioc: Ioc, ioc_sc: Ioc) -> (Ioc, str): def iocs_delete(ioc: Ioc): - call_modules_hook('on_preload_ioc_delete', data=ioc.ioc_id) + call_modules_hook('on_preload_ioc_delete', ioc.ioc_id) delete_ioc(ioc) - call_modules_hook('on_postload_ioc_delete', data=ioc.ioc_id, caseid=ioc.case_id) + call_modules_hook('on_postload_ioc_delete', ioc.ioc_id, caseid=ioc.case_id) track_activity(f'deleted IOC "{ioc.ioc_value}"', caseid=ioc.case_id) return f'IOC {ioc.ioc_id} deleted' diff --git a/source/app/business/notes.py b/source/app/business/notes.py index 147ea9657..51cedca40 100644 --- a/source/app/business/notes.py +++ b/source/app/business/notes.py @@ -62,7 +62,7 @@ def notes_create(note: Notes, case_identifier): db.session.add(note_revision) add_obj_history_entry(note, 'created note', commit=True) - note = call_modules_hook('on_postload_note_create', data=note, caseid=case_identifier) + note = call_modules_hook('on_postload_note_create', note, caseid=case_identifier) track_activity(f'created note "{note.note_title}"', caseid=case_identifier) @@ -89,7 +89,7 @@ def notes_update(note: Notes): note.user_id = iris_current_user.id add_obj_history_entry(note, 'updated note', commit=True) - note = call_modules_hook('on_postload_note_update', data=note, caseid=note.note_case_id) + note = call_modules_hook('on_postload_note_update', note, caseid=note.note_case_id) track_activity(f'updated note "{note.note_title}"', caseid=note.note_case_id) @@ -104,9 +104,9 @@ def notes_update(note: Notes): def notes_delete(note: Notes): - call_modules_hook('on_preload_note_delete', data=note.note_id, caseid=note.note_case_id) + call_modules_hook('on_preload_note_delete', note.note_id, caseid=note.note_case_id) delete_note(note.note_id, note.note_case_id) - call_modules_hook('on_postload_note_delete', data=note.note_id, caseid=note.note_case_id) + call_modules_hook('on_postload_note_delete', note.note_id, caseid=note.note_case_id) track_activity(f'deleted note "{note.note_title}"', caseid=note.note_case_id) diff --git a/source/app/business/reports/reports.py b/source/app/business/reports/reports.py index 6bdfee93b..6271a8a73 100644 --- a/source/app/business/reports/reports.py +++ b/source/app/business/reports/reports.py @@ -28,7 +28,7 @@ def generate_investigation_report(caseid, report_id, safe_mode, tmp_dir): - call_modules_hook('on_preload_report_create', data=report_id, caseid=caseid) + call_modules_hook('on_preload_report_create', report_id, caseid=caseid) report = CaseTemplateReport.query.filter(CaseTemplateReport.id == report_id).first() if not report: raise ObjectNotFoundError() @@ -47,20 +47,20 @@ def generate_investigation_report(caseid, report_id, safe_mode, tmp_dir): with open(fpath, 'rb') as rfile: encoded_file = base64.b64encode(rfile.read()).decode('utf-8') res = get_case(caseid) - _data = { + data = { 'report_id': report_id, 'file_path': fpath, 'case_id': res.case_id, 'user_name': res.user.name, 'file': encoded_file } - call_modules_hook('on_postload_report_create', data=_data, caseid=caseid) + call_modules_hook('on_postload_report_create', data, caseid=caseid) track_activity('generated a report') return fpath def generate_activities_report(caseid, report_id, safe_mode, tmp_dir): - call_modules_hook('on_preload_activities_report_create', data=report_id, caseid=caseid) + call_modules_hook('on_preload_activities_report_create', report_id, caseid=caseid) report = CaseTemplateReport.query.filter(CaseTemplateReport.id == report_id).first() if not report: raise ObjectNotFoundError() @@ -77,6 +77,6 @@ def generate_activities_report(caseid, report_id, safe_mode, tmp_dir): else: raise BusinessProcessingError('Report error', data='Unknown report format.') - call_modules_hook('on_postload_activities_report_create', data=report_id, caseid=caseid) + call_modules_hook('on_postload_activities_report_create', report_id, caseid=caseid) track_activity('generated a report') return fpath diff --git a/source/app/business/tasks.py b/source/app/business/tasks.py index a6090f858..564bda67b 100644 --- a/source/app/business/tasks.py +++ b/source/app/business/tasks.py @@ -38,11 +38,11 @@ def tasks_delete(task: CaseTasks): - call_modules_hook('on_preload_task_delete', data=task.id) + call_modules_hook('on_preload_task_delete', task.id) delete_task(task.id) update_tasks_state(caseid=task.task_case_id) - call_modules_hook('on_postload_task_delete', data=task.id, caseid=task.task_case_id) + call_modules_hook('on_postload_task_delete', task.id, caseid=task.task_case_id) track_activity(f'deleted task "{task.task_title}"') @@ -54,7 +54,7 @@ def tasks_create(task: CaseTasks, task_assignee_list) -> CaseTasks: caseid=task.task_case_id ) - ctask = call_modules_hook('on_postload_task_create', data=ctask, caseid=task.task_case_id) + ctask = call_modules_hook('on_postload_task_create', ctask, caseid=task.task_case_id) if not ctask: raise BusinessProcessingError('Unable to create task for internal reasons') @@ -86,7 +86,7 @@ def tasks_update(task: CaseTasks, task_assignee_list): db.session.commit() - task = call_modules_hook('on_postload_task_update', data=task, caseid=task.task_case_id) + task = call_modules_hook('on_postload_task_update', task, caseid=task.task_case_id) if not task: raise BusinessProcessingError('Unable to update task for internal reasons') From 55e1be523417041a5265e868848498f2a6f44c30 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:20:29 +0100 Subject: [PATCH 166/286] Removed TODO, added test and documented the reason why the code simplification is not straightforward (visible in user interface) --- source/app/business/cases.py | 1 - .../module_handler/module_handler.py | 5 +++ tests/tests_rest_module_tasks.py | 37 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/tests_rest_module_tasks.py diff --git a/source/app/business/cases.py b/source/app/business/cases.py index c74ae7f6d..8309d1fcf 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -131,7 +131,6 @@ def cases_create(user, case: Cases, case_template_id) -> Cases: 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) add_obj_history_entry(case, 'created') diff --git a/source/app/iris_engine/module_handler/module_handler.py b/source/app/iris_engine/module_handler/module_handler.py index 45281aa82..e86298569 100644 --- a/source/app/iris_engine/module_handler/module_handler.py +++ b/source/app/iris_engine/module_handler/module_handler.py @@ -416,6 +416,11 @@ def task_hook_wrapper(self, module_name, hook_name, hook_ui_name, data, init_use """ Wrap a hook call into a Celery task to run asynchronously + note: even if the caseid parameter does not seem very useful, it is used when tasks are retrieved to display the + table of "DFIR-IRIS Module Tasks". + see endpoint /dim/tasks/list + see tests_rest_module_tasks.TestsRestModuleTasks.test_get_module_tasks_should_return_case_identifier + :param self: Task instance :param module_name: Module name to instanciate and call :param hook_name: Name of the hook which was triggered diff --git a/tests/tests_rest_module_tasks.py b/tests/tests_rest_module_tasks.py new file mode 100644 index 000000000..5509c4e07 --- /dev/null +++ b/tests/tests_rest_module_tasks.py @@ -0,0 +1,37 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 TestsRestModuleTasks(TestCase): + + def setUp(self) -> None: + self._subject = Iris() + + def tearDown(self): + self._subject.clear_database() + + def test_get_module_tasks_should_return_case_identifier(self): + case_identifier = self._subject.create_dummy_case() + body = {'asset_type_id': 1, 'asset_name': 'admin_laptop_test'} + self._subject.create(f'/api/v2/cases/{case_identifier}/assets', body) + response = self._subject.get('/dim/tasks/list/1').json() + print(response['data'][0]['case']) + self.assertEqual(f'Case #{case_identifier}', response['data'][0]['case']) From f6717e6184d8b53152e34caac98aa5d85cf53f02 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:33:54 +0100 Subject: [PATCH 167/286] Made calls to call_modules_hook more uniform --- source/app/blueprints/rest/alerts_routes.py | 30 +++++++++---------- .../rest/case/case_assets_routes.py | 12 ++++---- .../rest/case/case_evidences_routes.py | 4 +-- .../blueprints/rest/case/case_ioc_routes.py | 8 ++--- .../blueprints/rest/case/case_notes_routes.py | 8 ++--- .../blueprints/rest/case/case_tasks_routes.py | 4 +-- .../rest/case/case_timeline_routes.py | 14 ++++----- .../app/blueprints/rest/dashboard_routes.py | 15 ++++------ .../app/blueprints/rest/dim_tasks_routes.py | 3 +- .../rest/manage/manage_cases_routes.py | 8 ++--- .../manage/manage_case_templates_db.py | 8 ++--- 11 files changed, 54 insertions(+), 60 deletions(-) diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index ebb158555..7d7276828 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -369,13 +369,13 @@ def alerts_update_route(alert_id) -> Response: # Save the changes db.session.commit() - updated_alert = call_modules_hook('on_postload_alert_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_update', updated_alert) if do_resolution_hook: - updated_alert = call_modules_hook('on_postload_alert_resolution_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_resolution_update', updated_alert) if do_status_hook: - updated_alert = call_modules_hook('on_postload_alert_status_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_status_update', updated_alert) if activity_data: activity_data_as_string = ','.join(activity_data) @@ -455,7 +455,7 @@ def alerts_batch_update_route() -> Response: db.session.commit() - alert = call_modules_hook('on_postload_alert_update', data=alert) + alert = call_modules_hook('on_postload_alert_update', alert) if activity_data: track_activity(f"updated alert #{alert_id}: {','.join(activity_data)}", ctx_less=True) @@ -509,7 +509,7 @@ def alerts_batch_delete_route() -> Response: if not success: return response_error(logs) - alert = call_modules_hook('on_postload_alert_delete', data={"alert_ids": alert_ids}) + alert = call_modules_hook('on_postload_alert_delete', {"alert_ids": alert_ids}) track_activity(f"deleted alerts #{','.join(str(alert_id) for alert_id in alert_ids)}", ctx_less=True) @@ -551,7 +551,7 @@ def alerts_delete_route(alert_id) -> Response: db.session.delete(alert) db.session.commit() - alert = call_modules_hook('on_postload_alert_delete', data=alert_id) + alert = call_modules_hook('on_postload_alert_delete', alert_id) track_activity(f"delete alert #{alert_id}", ctx_less=True) @@ -613,7 +613,7 @@ def alerts_escalate_route(alert_id) -> Response: ac_set_new_case_access(iris_current_user, case.case_id, case.client_id) - case = call_modules_hook('on_postload_case_create', data=case) + case = call_modules_hook('on_postload_case_create', case) add_obj_history_entry(case, 'created') track_activity(f"new case {case.name} created from alert", @@ -621,7 +621,7 @@ def alerts_escalate_route(alert_id) -> Response: add_obj_history_entry(alert, f"Alert escalated to case #{case.case_id}") - alert = call_modules_hook('on_postload_alert_escalate', data=alert) + alert = call_modules_hook('on_postload_alert_escalate', alert) # Return the updated alert as JSON return response_success(data=CaseSchema().dump(case)) @@ -687,7 +687,7 @@ def alerts_merge_route(alert_id) -> Response: iocs_list=iocs_import_list, assets_list=assets_import_list, note=note, import_as_event=import_as_event, case_tags=case_tags) - alert = call_modules_hook('on_postload_alert_merge', data=alert, caseid=target_case_id) + alert = call_modules_hook('on_postload_alert_merge', alert, caseid=target_case_id) track_activity(f"merge alert #{alert_id} into existing case #{target_case_id}", caseid=target_case_id) add_obj_history_entry(alert, f"Alert merged into existing case #{target_case_id}") @@ -747,7 +747,7 @@ def alerts_unmerge_route(alert_id) -> Response: track_activity(f"unmerge alert #{alert_id} from case #{target_case_id}", caseid=target_case_id) add_obj_history_entry(alert, f"Alert unmerged from case #{target_case_id}") - alert = call_modules_hook('on_postload_alert_unmerge', data=alert) + alert = call_modules_hook('on_postload_alert_unmerge', alert) # Return the updated case as JSON return response_success(data=AlertSchema().dump(alert), msg=message) @@ -816,7 +816,7 @@ def alerts_batch_merge_route() -> Response: add_obj_history_entry(alert, f"Alert merged into existing case #{target_case_id}") - alert = call_modules_hook('on_postload_alert_merge', data=alert) + alert = call_modules_hook('on_postload_alert_merge', alert) if note: case.description += f"\n\n### Escalation note\n\n{note}\n\n" if case.description else f"\n\n{note}\n\n" @@ -877,7 +877,7 @@ def alerts_batch_escalate_route() -> Response: alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id db.session.commit() - alert = call_modules_hook('on_postload_alert_escalate', data=alert) + alert = call_modules_hook('on_postload_alert_escalate', alert) alerts_list.append(alert) @@ -890,7 +890,7 @@ def alerts_batch_escalate_route() -> Response: ac_set_new_case_access(iris_current_user, case.case_id, case.client_id) - case = call_modules_hook('on_postload_case_create', data=case) + case = call_modules_hook('on_postload_case_create', case) add_obj_history_entry(case, 'created') track_activity(f"new case {case.name} created from alerts", @@ -963,7 +963,7 @@ def alert_comment_delete(alert_id, com_id): if not success: return response_error(msg) - call_modules_hook('on_postload_alert_comment_delete', data=com_id) + call_modules_hook('on_postload_alert_comment_delete', com_id) track_activity(f"comment {com_id} on alert {alert_id} deleted", ctx_less=True) @@ -1065,7 +1065,7 @@ def case_comment_add(alert_id): "comment": comment_schema.dump(comment), "alert": AlertSchema().dump(alert) } - call_modules_hook('on_postload_alert_commented', data=hook_data) + call_modules_hook('on_postload_alert_commented', hook_data) track_activity(f"alert \"{alert.alert_id}\" commented", ctx_less=True) return response_success("Alert commented", data=comment_schema.dump(comment)) diff --git a/source/app/blueprints/rest/case/case_assets_routes.py b/source/app/blueprints/rest/case/case_assets_routes.py index a3eb893ad..d9a222a95 100644 --- a/source/app/blueprints/rest/case/case_assets_routes.py +++ b/source/app/blueprints/rest/case/case_assets_routes.py @@ -166,7 +166,7 @@ def case_assets_state(caseid): def deprecated_add_asset(caseid): asset_schema = CaseAssetsSchema() try: - request_data = call_modules_hook('on_preload_asset_create', data=request.get_json(), caseid=caseid) + request_data = call_modules_hook('on_preload_asset_create', request.get_json(), caseid=caseid) ioc_links = request_data.get('ioc_links') asset = asset_schema.load(request_data) created_asset = assets_create(iris_current_user, caseid, asset, ioc_links) @@ -243,7 +243,7 @@ def case_upload_asset(caseid): row['analysis_status_id'] = analysis_status_id - request_data = call_modules_hook('on_preload_asset_create', data=row, caseid=caseid) + request_data = call_modules_hook('on_preload_asset_create', row, caseid=caseid) add_asset_schema.is_unique_for_cid(caseid, request_data) asset_sc = add_asset_schema.load(request_data) @@ -253,7 +253,7 @@ def case_upload_asset(caseid): user_id=iris_current_user.id ) - asset = call_modules_hook('on_postload_asset_create', data=asset, caseid=caseid) + asset = call_modules_hook('on_postload_asset_create', asset, caseid=caseid) if not asset: errors.append('Unable to add asset for internal reason') @@ -305,7 +305,7 @@ def asset_update(cur_id, caseid): if not asset: return response_error("Invalid asset ID for this case") - request_data = call_modules_hook('on_preload_asset_update', data=request.get_json(), caseid=caseid) + request_data = call_modules_hook('on_preload_asset_update', request.get_json(), caseid=caseid) request_data['asset_id'] = asset.asset_id schema = CaseAssetsSchema() updated_asset = schema.load(request_data, instance=asset, partial=True) @@ -375,7 +375,7 @@ def case_comment_asset_add(cur_id, caseid): "comment": comment_schema.dump(comment), "asset": CaseAssetsSchema().dump(asset) } - call_modules_hook('on_postload_asset_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_asset_commented', hook_data, caseid=caseid) track_activity(f"asset \"{asset.asset_name}\" commented", caseid=caseid) return response_success("Asset commented", data=comment_schema.dump(comment)) @@ -411,7 +411,7 @@ def case_comment_asset_delete(cur_id, com_id, caseid): if not success: return response_error(msg) - call_modules_hook('on_postload_asset_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_asset_comment_delete', com_id, caseid=caseid) track_activity(f'comment {com_id} on asset {cur_id} deleted', caseid=caseid) return response_success(msg) diff --git a/source/app/blueprints/rest/case/case_evidences_routes.py b/source/app/blueprints/rest/case/case_evidences_routes.py index 583aa221b..2f3dadf6d 100644 --- a/source/app/blueprints/rest/case/case_evidences_routes.py +++ b/source/app/blueprints/rest/case/case_evidences_routes.py @@ -189,7 +189,7 @@ def case_comment_evidence_add(cur_id, caseid): "comment": comment_schema.dump(comment), "evidence": CaseEvidenceSchema().dump(evidence) } - call_modules_hook('on_postload_evidence_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_evidence_commented', hook_data, caseid=caseid) track_activity(f"evidence \"{evidence.filename}\" commented", caseid=caseid) return response_success("Evidence commented", data=comment_schema.dump(comment)) @@ -225,7 +225,7 @@ def case_comment_evidence_delete(cur_id, com_id, caseid): if not success: return response_error(msg) - call_modules_hook('on_postload_evidence_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_evidence_comment_delete', com_id, caseid=caseid) track_activity(f"comment {com_id} on evidence {cur_id} deleted", caseid=caseid) return response_success(msg) diff --git a/source/app/blueprints/rest/case/case_ioc_routes.py b/source/app/blueprints/rest/case/case_ioc_routes.py index 8383b4116..663f6bccd 100644 --- a/source/app/blueprints/rest/case/case_ioc_routes.py +++ b/source/app/blueprints/rest/case/case_ioc_routes.py @@ -179,7 +179,7 @@ def case_upload_ioc(caseid): row['ioc_type_id'] = type_id.type_id row.pop('ioc_type', None) - request_data = call_modules_hook('on_preload_ioc_create', data=row, caseid=caseid) + request_data = call_modules_hook('on_preload_ioc_create', row, caseid=caseid) ioc = add_ioc_schema.load(request_data) ioc.custom_attributes = get_default_custom_attributes('ioc') @@ -191,7 +191,7 @@ def case_upload_ioc(caseid): continue add_ioc(ioc, iris_current_user.id, caseid) - ioc = call_modules_hook('on_postload_ioc_create', data=ioc, caseid=caseid) + ioc = call_modules_hook('on_postload_ioc_create', ioc, caseid=caseid) ret.append(request_data) track_activity(f'added ioc "{ioc.ioc_value}"', caseid=caseid) @@ -304,7 +304,7 @@ def case_comment_ioc_add(cur_id, caseid): 'comment': comment_schema.dump(comment), 'ioc': IocSchema().dump(ioc) } - call_modules_hook('on_postload_ioc_commented', data=hook_data, caseid=ioc.case_id) + call_modules_hook('on_postload_ioc_commented', hook_data, caseid=ioc.case_id) track_activity(f'ioc "{ioc.ioc_value}" commented', caseid=ioc.case_id) return response_success('IOC commented', data=comment_schema.dump(comment)) @@ -342,7 +342,7 @@ def case_comment_ioc_delete(cur_id, com_id, caseid): if not success: return response_error(msg) - call_modules_hook('on_postload_ioc_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_ioc_comment_delete', com_id, caseid=caseid) track_activity(f'comment {com_id} on ioc {cur_id} deleted', caseid=caseid) return response_success(msg) diff --git a/source/app/blueprints/rest/case/case_notes_routes.py b/source/app/blueprints/rest/case/case_notes_routes.py index f1432c9dc..e20b22852 100644 --- a/source/app/blueprints/rest/case/case_notes_routes.py +++ b/source/app/blueprints/rest/case/case_notes_routes.py @@ -138,7 +138,7 @@ def case_note_save(cur_id, caseid): try: note = notes_get(cur_id) - request_data = call_modules_hook('on_preload_note_update', data=request.get_json(), caseid=note.note_case_id) + request_data = call_modules_hook('on_preload_note_update', request.get_json(), caseid=note.note_case_id) request_data['note_id'] = note.note_id addnote_schema.load(request_data, partial=True, instance=note) @@ -207,7 +207,7 @@ def case_note_add(caseid): try: - request_data = call_modules_hook('on_preload_note_create', data=request.get_json(), caseid=caseid) + request_data = call_modules_hook('on_preload_note_create', request.get_json(), caseid=caseid) note_schema = CaseNoteSchema() note_schema.verify_directory_id(request_data, caseid=caseid) @@ -426,7 +426,7 @@ def case_comment_note_add(cur_id, caseid): "comment": comment_schema.dump(comment), "note": CaseNoteSchema().dump(note) } - call_modules_hook('on_postload_note_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_note_commented', hook_data, caseid=caseid) track_activity(f"note \"{note.note_title}\" commented", caseid=caseid) return response_success("Note commented", data=comment_schema.dump(comment)) @@ -462,7 +462,7 @@ def case_comment_note_delete(cur_id, com_id, caseid): if not success: return response_error(msg) - call_modules_hook('on_postload_note_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_note_comment_delete', com_id, caseid=caseid) track_activity(f"comment {com_id} on note {cur_id} deleted", caseid=caseid) return response_success(msg) diff --git a/source/app/blueprints/rest/case/case_tasks_routes.py b/source/app/blueprints/rest/case/case_tasks_routes.py index 01d5ce2fc..cefb7d8f2 100644 --- a/source/app/blueprints/rest/case/case_tasks_routes.py +++ b/source/app/blueprints/rest/case/case_tasks_routes.py @@ -230,7 +230,7 @@ def case_comment_task_add(cur_id: int, caseid: int): "comment": comment_schema.dump(comment), "task": CaseTaskSchema().dump(task) } - call_modules_hook('on_postload_task_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_task_commented', hook_data, caseid=caseid) track_activity(f"task \"{task.task_title}\" commented", caseid=caseid) return response_success("Task commented", data=comment_schema.dump(comment)) @@ -270,7 +270,7 @@ def case_comment_task_delete(cur_id: int, com_id: int, caseid: int): if not success: return response_error(msg) - call_modules_hook('on_postload_task_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_task_comment_delete', com_id, caseid=caseid) track_activity(f"comment {com_id} on task {cur_id} deleted", caseid=caseid) return response_success(msg) diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index d62651c36..0159b4cab 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -100,7 +100,7 @@ def case_comment_delete(cur_id, com_id, caseid): if not success: return response_error(msg) - call_modules_hook('on_postload_event_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_event_comment_delete', com_id, caseid=caseid) track_activity(f"comment {com_id} on event {cur_id} deleted", caseid=caseid) return response_success(msg) @@ -155,7 +155,7 @@ def case_comment_add(cur_id, caseid): "comment": comment_schema.dump(comment), "event": EventSchema().dump(event) } - call_modules_hook('on_postload_event_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_event_commented', hook_data, caseid=caseid) track_activity(f"event \"{event.event_title}\" commented", caseid=caseid) return response_success("Event commented", data=comment_schema.dump(comment)) @@ -625,7 +625,7 @@ def _extract_timeline(assets: str | None, assets_id: str | None, caseid, categor @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_delete_event(cur_id, caseid): - call_modules_hook('on_preload_event_delete', data=cur_id, caseid=caseid) + call_modules_hook('on_preload_event_delete', cur_id, caseid=caseid) event = get_case_event(cur_id) if not event: @@ -748,7 +748,7 @@ def case_add_event(caseid): @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_duplicate_event(cur_id, caseid): - call_modules_hook('on_preload_event_duplicate', data=cur_id, caseid=caseid) + call_modules_hook('on_preload_event_duplicate', cur_id, caseid=caseid) try: event_schema = EventSchema() @@ -793,7 +793,7 @@ def case_duplicate_event(cur_id, caseid): if not success: return response_error('Error while saving linked iocs', data=log) - event = call_modules_hook('on_postload_event_create', data=event, caseid=caseid) + event = call_modules_hook('on_postload_event_create', event, caseid=caseid) track_activity(f"added event \"{event.event_title}\"", caseid=caseid) return response_success("Event duplicated", data=event_schema.dump(event)) @@ -948,7 +948,7 @@ def case_events_upload_csv(caseid): continue line += 1 - request_data = call_modules_hook('on_preload_event_create', data=row, caseid=caseid) + request_data = call_modules_hook('on_preload_event_create', row, caseid=caseid) event = event_schema.load(request_data) event.event_date, event.event_date_wtz = event_schema.validate_date(request_data.get(u'event_date'), request_data.get(u'event_tz')) @@ -976,7 +976,7 @@ def case_events_upload_csv(caseid): setattr(event, 'event_category_id', request_data.get('event_category_id')) - event = call_modules_hook('on_postload_event_create', data=event, caseid=caseid) + event = call_modules_hook('on_postload_event_create', event, caseid=caseid) track_activity(f"added event {event.event_id}", caseid=caseid) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index 893dfe4e5..a7d557b3b 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -209,8 +209,7 @@ def add_gtask(caseid): gtask_schema = GlobalTasksSchema() - request_data = call_modules_hook( - 'on_preload_global_task_create', data=request.get_json(), caseid=caseid) + request_data = call_modules_hook('on_preload_global_task_create', request.get_json(), caseid=caseid) gtask = gtask_schema.load(request_data) @@ -230,8 +229,7 @@ def add_gtask(caseid): except Exception as e: return response_error(msg="Data error", data=e.__str__()) - gtask = call_modules_hook( - 'on_postload_global_task_create', data=gtask, caseid=caseid) + gtask = call_modules_hook('on_postload_global_task_create', gtask, caseid=caseid) track_activity(f"created new global task \'{gtask.task_title}\'", caseid=caseid) return response_success('Task added', data=gtask_schema.dump(gtask)) @@ -254,8 +252,7 @@ def edit_gtask(cur_id, caseid): try: gtask_schema = GlobalTasksSchema() - request_data = call_modules_hook('on_preload_global_task_update', data=request.get_json(), - caseid=caseid) + request_data = call_modules_hook('on_preload_global_task_update', request.get_json(), caseid=caseid) gtask = gtask_schema.load(request_data, instance=task) gtask.task_userid_update = iris_current_user.id @@ -278,8 +275,7 @@ def edit_gtask(cur_id, caseid): @ac_api_requires() @ac_requires_case_identifier() def gtask_delete(cur_id, caseid): - call_modules_hook('on_preload_global_task_delete', - data=cur_id, caseid=caseid) + call_modules_hook('on_preload_global_task_delete', cur_id, caseid=caseid) if not cur_id: return response_error("Missing parameter") @@ -291,8 +287,7 @@ def gtask_delete(cur_id, caseid): GlobalTasks.query.filter(GlobalTasks.id == cur_id).delete() db.session.commit() - call_modules_hook('on_postload_global_task_delete', - data=request.get_json(), caseid=caseid) + call_modules_hook('on_postload_global_task_delete', request.get_json(), caseid=caseid) track_activity(f"deleted global task ID {cur_id}", caseid=caseid) return response_success("Task deleted") diff --git a/source/app/blueprints/rest/dim_tasks_routes.py b/source/app/blueprints/rest/dim_tasks_routes.py index 0264b2c7a..4d275a844 100644 --- a/source/app/blueprints/rest/dim_tasks_routes.py +++ b/source/app/blueprints/rest/dim_tasks_routes.py @@ -139,8 +139,7 @@ def dim_hooks_call(caseid): index += 1 if len(obj_targets) > 0: - call_modules_hook(hook_name=hook_name, hook_ui_name=hook_ui_name, data=obj_targets, - caseid=caseid, module_name=module_name) + call_modules_hook(hook_name, obj_targets, caseid=caseid, hook_ui_name=hook_ui_name, module_name=module_name) if len(logs) > 0: return response_error(f"Errors encountered during processing of data. Queued task with {index} objects", diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index 51a7bf1eb..a68de7505 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -188,7 +188,7 @@ def api_reopen_case(identifier): db.session.add(alert) - case = call_modules_hook('on_postload_case_update', data=case, caseid=identifier) + case = call_modules_hook('on_postload_case_update', case, caseid=identifier) add_obj_history_entry(case, 'case reopen') track_activity(f"reopen case ID {identifier}", caseid=identifier) @@ -222,18 +222,18 @@ def api_case_close(identifier): for alert in case.alerts: if alert.alert_status_id != close_status.status_id: alert.alert_status_id = close_status.status_id - alert = call_modules_hook('on_postload_alert_update', data=alert, caseid=identifier) + alert = call_modules_hook('on_postload_alert_update', alert, caseid=identifier) if alert.alert_resolution_status_id != case_status_id_mapped: alert.alert_resolution_status_id = case_status_id_mapped - alert = call_modules_hook('on_postload_alert_resolution_update', data=alert, caseid=identifier) + alert = call_modules_hook('on_postload_alert_resolution_update', alert, caseid=identifier) track_activity(f'closing alert ID {alert.alert_id} due to case #{identifier} being closed', caseid=identifier, ctx_less=False) db.session.add(alert) - case = call_modules_hook('on_postload_case_update', data=case, caseid=identifier) + case = call_modules_hook('on_postload_case_update', case, caseid=identifier) add_obj_history_entry(case, 'case closed') track_activity(f'closed case ID {identifier}', caseid=identifier, ctx_less=False) diff --git a/source/app/datamgmt/manage/manage_case_templates_db.py b/source/app/datamgmt/manage/manage_case_templates_db.py index 9075b964c..db58aedbd 100644 --- a/source/app/datamgmt/manage/manage_case_templates_db.py +++ b/source/app/datamgmt/manage/manage_case_templates_db.py @@ -189,7 +189,7 @@ def case_template_populate_tasks(case: Cases, case_template: CaseTemplate): 'task_status_id': 1 } - mapped_task_template = call_modules_hook('on_preload_task_create', data=mapped_task_template, caseid=case.case_id) + mapped_task_template = call_modules_hook('on_preload_task_create', mapped_task_template, caseid=case.case_id) task = task_schema.load(mapped_task_template) tasks.append(task) @@ -200,7 +200,7 @@ def case_template_populate_tasks(case: Cases, case_template: CaseTemplate): for task in tasks: ctask = add_task(task=task, assignee_id_list=[], user_id=case.user_id, caseid=case.case_id) - ctask = call_modules_hook('on_postload_task_create', data=ctask, caseid=case.case_id) + ctask = call_modules_hook('on_postload_task_create', ctask, caseid=case.case_id) if not ctask: logs.append('Unable to create task for internal reasons') @@ -222,7 +222,7 @@ def case_template_populate_notes(case: Cases, note_dir_template: dict, ng: NoteD } mapped_note_template = call_modules_hook('on_preload_note_create', - data=mapped_note_template, + mapped_note_template, caseid=case.case_id) note_schema.verify_directory_id(mapped_note_template, caseid=ng.case_id) @@ -235,7 +235,7 @@ def case_template_populate_notes(case: Cases, note_dir_template: dict, ng: NoteD db.session.add(note) - note = call_modules_hook('on_postload_note_create', data=note, caseid=case.case_id) + note = call_modules_hook('on_postload_note_create', note, caseid=case.case_id) if not note: logs.append("Unable to add note for internal reasons") From 068d79de0b4ca26879009051a6299c319179d1ca Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:47:08 +0100 Subject: [PATCH 168/286] Fixed failing test --- tests/tests_rest_module_tasks.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/tests_rest_module_tasks.py b/tests/tests_rest_module_tasks.py index 5509c4e07..8101c4940 100644 --- a/tests/tests_rest_module_tasks.py +++ b/tests/tests_rest_module_tasks.py @@ -30,8 +30,15 @@ def tearDown(self): def test_get_module_tasks_should_return_case_identifier(self): case_identifier = self._subject.create_dummy_case() + + module_identifier = self._subject.get_module_identifier_by_name('IrisCheck') + self._subject.create(f'/manage/modules/enable/{module_identifier}', {}) body = {'asset_type_id': 1, 'asset_name': 'admin_laptop_test'} self._subject.create(f'/api/v2/cases/{case_identifier}/assets', body) + self._subject.create(f'/manage/modules/disable/{module_identifier}', {}) + response = self._subject.get('/dim/tasks/list/1').json() - print(response['data'][0]['case']) + print('--------------------------------------------------------------------------------------------') + print(response['data'][0]) + print('--------------------------------------------------------------------------------------------') self.assertEqual(f'Case #{case_identifier}', response['data'][0]['case']) From 88e9e5feb31cdf4c2afa87ae393b74bdf96511ef Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:53:45 +0100 Subject: [PATCH 169/286] Grouped all methods related to global tasks --- .../app/blueprints/rest/dashboard_routes.py | 3 +- source/app/business/global_tasks.py | 3 +- source/app/datamgmt/dashboard/dashboard_db.py | 74 ------------------ source/app/datamgmt/global_tasks.py | 76 +++++++++++++++++++ 4 files changed, 78 insertions(+), 78 deletions(-) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index a7d557b3b..caf11dfd8 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -33,11 +33,10 @@ from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user -from app.datamgmt.dashboard.dashboard_db import get_global_task from app.datamgmt.dashboard.dashboard_db import list_user_cases from app.datamgmt.dashboard.dashboard_db import list_user_reviews from app.datamgmt.dashboard.dashboard_db import get_tasks_status -from app.datamgmt.dashboard.dashboard_db import list_global_tasks +from app.datamgmt.global_tasks import list_global_tasks, get_global_task from app.datamgmt.dashboard.dashboard_db import list_user_tasks from app.forms import CaseGlobalTaskForm from app.iris_engine.module_handler.module_handler import call_modules_hook diff --git a/source/app/business/global_tasks.py b/source/app/business/global_tasks.py index 14d67546e..426c834a5 100644 --- a/source/app/business/global_tasks.py +++ b/source/app/business/global_tasks.py @@ -22,8 +22,7 @@ from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.datamgmt.db_operations import db_create -from app.datamgmt.dashboard.dashboard_db import get_global_task_by_identifier -from app.datamgmt.global_tasks import delete_global_task +from app.datamgmt.global_tasks import delete_global_task, get_global_task_by_identifier def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index 383002be6..d79c261ee 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -24,65 +24,7 @@ from app.models.models import TaskAssignee from app.models.models import ReviewStatus from app.models.cases import Cases -from app.models.models import GlobalTasks from app.models.models import TaskStatus -from app.models.authorization import User - - -def list_global_tasks(): - ct = GlobalTasks.query.with_entities( - GlobalTasks.id.label("task_id"), - GlobalTasks.task_uuid, - GlobalTasks.task_title, - GlobalTasks.task_description, - GlobalTasks.task_last_update, - GlobalTasks.task_tags, - User.name.label('user_name'), - GlobalTasks.task_assignee_id, - GlobalTasks.task_status_id, - TaskStatus.status_name, - TaskStatus.status_bscolor - ).join( - GlobalTasks.user_assigned - ).order_by( - desc(TaskStatus.status_name) - ).join( - GlobalTasks.status - ).all() - - return ct - - -def get_global_task(task_id): - ct = GlobalTasks.query.with_entities( - GlobalTasks.id.label("task_id"), - GlobalTasks.task_uuid, - GlobalTasks.task_title, - GlobalTasks.task_description, - GlobalTasks.task_last_update, - GlobalTasks.task_tags, - User.name.label('user_name'), - GlobalTasks.task_assignee_id, - GlobalTasks.task_status_id, - TaskStatus.status_name, - TaskStatus.status_bscolor - ).filter( - GlobalTasks.id == task_id - ).join( - GlobalTasks.user_assigned - ).join( - GlobalTasks.status - ).order_by( - desc(TaskStatus.status_name) - ).first() - - return ct - - -def get_global_task_by_identifier(identifier): - return GlobalTasks.query.filter( - GlobalTasks.id == identifier - ).first() def get_tasks_status(): @@ -135,22 +77,6 @@ def list_user_tasks(user_identifier): return ct -def update_gtask_status(task_id, status): - if task_id != 0: - task = GlobalTasks.query.filter( - GlobalTasks.id == task_id - ).first() - - try: - task.task_status_id = status - db.session.commit() - return task - except: - pass - - return None - - def update_utask_status(task_id, status, case_id): if task_id != 0: task = CaseTasks.query.filter( diff --git a/source/app/datamgmt/global_tasks.py b/source/app/datamgmt/global_tasks.py index d878cc5bf..3af8a7251 100644 --- a/source/app/datamgmt/global_tasks.py +++ b/source/app/datamgmt/global_tasks.py @@ -16,10 +16,86 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from sqlalchemy import desc + from app.db import db +from app.models.authorization import User from app.models.models import GlobalTasks +from app.models.models import TaskStatus def delete_global_task(task: GlobalTasks): GlobalTasks.query.filter(GlobalTasks.id == task.id).delete() db.session.commit() + + +def list_global_tasks(): + ct = GlobalTasks.query.with_entities( + GlobalTasks.id.label("task_id"), + GlobalTasks.task_uuid, + GlobalTasks.task_title, + GlobalTasks.task_description, + GlobalTasks.task_last_update, + GlobalTasks.task_tags, + User.name.label('user_name'), + GlobalTasks.task_assignee_id, + GlobalTasks.task_status_id, + TaskStatus.status_name, + TaskStatus.status_bscolor + ).join( + GlobalTasks.user_assigned + ).order_by( + desc(TaskStatus.status_name) + ).join( + GlobalTasks.status + ).all() + + return ct + + +def get_global_task(task_id): + ct = GlobalTasks.query.with_entities( + GlobalTasks.id.label("task_id"), + GlobalTasks.task_uuid, + GlobalTasks.task_title, + GlobalTasks.task_description, + GlobalTasks.task_last_update, + GlobalTasks.task_tags, + User.name.label('user_name'), + GlobalTasks.task_assignee_id, + GlobalTasks.task_status_id, + TaskStatus.status_name, + TaskStatus.status_bscolor + ).filter( + GlobalTasks.id == task_id + ).join( + GlobalTasks.user_assigned + ).join( + GlobalTasks.status + ).order_by( + desc(TaskStatus.status_name) + ).first() + + return ct + + +def get_global_task_by_identifier(identifier): + return GlobalTasks.query.filter( + GlobalTasks.id == identifier + ).first() + + +def update_gtask_status(task_id, status): + if task_id != 0: + task = GlobalTasks.query.filter( + GlobalTasks.id == task_id + ).first() + + try: + task.task_status_id = status + db.session.commit() + return task + except: + pass + + return None From 0608f9f80ca99b0b70240a889b69ea98011e1e9d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:56:50 +0100 Subject: [PATCH 170/286] Moved method related to case_tasks out of dashboard_routes to group them --- .../app/blueprints/rest/dashboard_routes.py | 2 +- source/app/business/tasks.py | 2 +- source/app/datamgmt/case/case_tasks_db.py | 48 +++++++++++++++++ source/app/datamgmt/dashboard/dashboard_db.py | 54 ------------------- 4 files changed, 50 insertions(+), 56 deletions(-) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index caf11dfd8..10765827b 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -37,7 +37,7 @@ from app.datamgmt.dashboard.dashboard_db import list_user_reviews from app.datamgmt.dashboard.dashboard_db import get_tasks_status from app.datamgmt.global_tasks import list_global_tasks, get_global_task -from app.datamgmt.dashboard.dashboard_db import list_user_tasks +from app.datamgmt.case.case_tasks_db import list_user_tasks from app.forms import CaseGlobalTaskForm from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity diff --git a/source/app/business/tasks.py b/source/app/business/tasks.py index 564bda67b..15345c69a 100644 --- a/source/app/business/tasks.py +++ b/source/app/business/tasks.py @@ -23,12 +23,12 @@ from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.case.case_tasks_db import delete_task +from app.datamgmt.case.case_tasks_db import list_user_tasks from app.datamgmt.case.case_tasks_db import add_task from app.datamgmt.case.case_tasks_db import update_task_assignees from app.datamgmt.case.case_tasks_db import get_task from app.datamgmt.case.case_tasks_db import get_filtered_tasks from app.datamgmt.states import update_tasks_state -from app.datamgmt.dashboard.dashboard_db import list_user_tasks from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.models.models import CaseTasks diff --git a/source/app/datamgmt/case/case_tasks_db.py b/source/app/datamgmt/case/case_tasks_db.py index 9dc368201..054eb161e 100644 --- a/source/app/datamgmt/case/case_tasks_db.py +++ b/source/app/datamgmt/case/case_tasks_db.py @@ -343,3 +343,51 @@ def get_tasks_cases_mapping(open_cases_only=False): ).join( CaseTasks.case ).all() + + +def list_user_tasks(user_identifier): + ct = CaseTasks.query.with_entities( + CaseTasks.id.label("task_id"), + CaseTasks.task_title, + CaseTasks.task_description, + CaseTasks.task_last_update, + CaseTasks.task_tags, + Cases.name.label('task_case'), + CaseTasks.task_case_id.label('case_id'), + CaseTasks.task_status_id, + TaskStatus.status_name, + TaskStatus.status_bscolor + ).join( + CaseTasks.case + ).order_by( + desc(TaskStatus.status_name) + ).filter(and_( + TaskStatus.status_name != 'Done', + TaskStatus.status_name != 'Canceled' + )).join( + CaseTasks.status, + ).filter(and_( + TaskAssignee.task_id == CaseTasks.id, + TaskAssignee.user_id == user_identifier + )).all() + + return ct + + +def update_utask_status(task_id, status, case_id): + if task_id != 0: + task = CaseTasks.query.filter( + CaseTasks.id == task_id, + CaseTasks.task_case_id == case_id + ).first() + if task: + try: + task.task_status_id = status + + db.session.commit() + return True + + except: + pass + + return False diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index d79c261ee..7c30a62eb 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -16,12 +16,6 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from sqlalchemy import and_ -from sqlalchemy import desc - -from app.db import db -from app.models.models import CaseTasks -from app.models.models import TaskAssignee from app.models.models import ReviewStatus from app.models.cases import Cases from app.models.models import TaskStatus @@ -48,54 +42,6 @@ def list_user_reviews(user_identifier): return ct -def list_user_tasks(user_identifier): - ct = CaseTasks.query.with_entities( - CaseTasks.id.label("task_id"), - CaseTasks.task_title, - CaseTasks.task_description, - CaseTasks.task_last_update, - CaseTasks.task_tags, - Cases.name.label('task_case'), - CaseTasks.task_case_id.label('case_id'), - CaseTasks.task_status_id, - TaskStatus.status_name, - TaskStatus.status_bscolor - ).join( - CaseTasks.case - ).order_by( - desc(TaskStatus.status_name) - ).filter(and_( - TaskStatus.status_name != 'Done', - TaskStatus.status_name != 'Canceled' - )).join( - CaseTasks.status, - ).filter(and_( - TaskAssignee.task_id == CaseTasks.id, - TaskAssignee.user_id == user_identifier - )).all() - - return ct - - -def update_utask_status(task_id, status, case_id): - if task_id != 0: - task = CaseTasks.query.filter( - CaseTasks.id == task_id, - CaseTasks.task_case_id == case_id - ).first() - if task: - try: - task.task_status_id = status - - db.session.commit() - return True - - except: - pass - - return False - - def get_task_status(task_status_id): ret = TaskStatus.query.filter( TaskStatus.id == task_status_id From 97cc5c81a30107e49d4166dafccc3a1deb558175 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:58:27 +0100 Subject: [PATCH 171/286] Moved code out of dashboard_db --- source/app/blueprints/rest/dashboard_routes.py | 2 +- source/app/business/cases.py | 5 +++-- source/app/datamgmt/case/case_db.py | 17 +++++++++++++++++ source/app/datamgmt/dashboard/dashboard_db.py | 18 ------------------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index 10765827b..38ce6334a 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -34,7 +34,7 @@ from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user 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 list_user_reviews from app.datamgmt.dashboard.dashboard_db import get_tasks_status from app.datamgmt.global_tasks import list_global_tasks, get_global_task from app.datamgmt.case.case_tasks_db import list_user_tasks diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 8309d1fcf..d21a85571 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -28,7 +28,9 @@ from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.iris_engine.access_control.utils import ac_set_new_case_access -from app.datamgmt.case.case_db import case_db_exists, case_db_save +from app.datamgmt.case.case_db import case_db_exists +from app.datamgmt.case.case_db import case_db_save +from app.datamgmt.case.case_db import list_user_reviews from app.datamgmt.case.case_db import save_case_tags from app.datamgmt.case.case_db import register_case_protagonists from app.datamgmt.case.case_db import get_review_id_from_name @@ -52,7 +54,6 @@ from app.datamgmt.reporter.report_db import export_case_notes_json 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, ReviewStatusList from app.models.customers import Client diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index 9f32ae57c..4a27956a6 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -247,3 +247,20 @@ def case_db_save(case: Cases): update_notes_state(caseid=case.case_id, userid=case.user_id) db.session.commit() + + +def list_user_reviews(user_identifier): + ct = Cases.query.with_entities( + Cases.case_id, + Cases.name, + ReviewStatus.status_name, + ReviewStatus.id.label('status_id') + ).join( + Cases.review_status + ).filter( + Cases.reviewer_id == user_identifier, + ReviewStatus.status_name != 'Reviewed', + ReviewStatus.status_name != 'Not reviewed' + ).all() + + return ct diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index 7c30a62eb..e0a5824ac 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -16,7 +16,6 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from app.models.models import ReviewStatus from app.models.cases import Cases from app.models.models import TaskStatus @@ -25,23 +24,6 @@ def get_tasks_status(): return TaskStatus.query.all() -def list_user_reviews(user_identifier): - ct = Cases.query.with_entities( - Cases.case_id, - Cases.name, - ReviewStatus.status_name, - ReviewStatus.id.label('status_id') - ).join( - Cases.review_status - ).filter( - Cases.reviewer_id == user_identifier, - ReviewStatus.status_name != 'Reviewed', - ReviewStatus.status_name != 'Not reviewed' - ).all() - - return ct - - def get_task_status(task_status_id): ret = TaskStatus.query.filter( TaskStatus.id == task_status_id From 066de8d66d770ad5c97241235d229a5e082dc9cb Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:04:10 +0100 Subject: [PATCH 172/286] Removed duplicated method --- source/app/blueprints/pages/dashboard/dashboard_routes.py | 2 +- source/app/blueprints/rest/dashboard_routes.py | 2 +- source/app/datamgmt/dashboard/dashboard_db.py | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/source/app/blueprints/pages/dashboard/dashboard_routes.py b/source/app/blueprints/pages/dashboard/dashboard_routes.py index 7887726fa..16079ad93 100644 --- a/source/app/blueprints/pages/dashboard/dashboard_routes.py +++ b/source/app/blueprints/pages/dashboard/dashboard_routes.py @@ -24,7 +24,7 @@ from app import app from app.blueprints.iris_user import iris_current_user -from app.datamgmt.dashboard.dashboard_db import get_tasks_status +from app.datamgmt.case.case_tasks_db import get_tasks_status from app.forms import CaseGlobalTaskForm from app.iris_engine.access_control.utils import ac_get_user_case_counts from app.models.authorization import User diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index 38ce6334a..e75e8f512 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -35,7 +35,7 @@ from app.blueprints.iris_user import iris_current_user from app.datamgmt.dashboard.dashboard_db import list_user_cases from app.datamgmt.case.case_db import list_user_reviews -from app.datamgmt.dashboard.dashboard_db import get_tasks_status +from app.datamgmt.case.case_tasks_db import get_tasks_status from app.datamgmt.global_tasks import list_global_tasks, get_global_task from app.datamgmt.case.case_tasks_db import list_user_tasks from app.forms import CaseGlobalTaskForm diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index e0a5824ac..8245dd3e7 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -20,10 +20,6 @@ from app.models.models import TaskStatus -def get_tasks_status(): - return TaskStatus.query.all() - - def get_task_status(task_status_id): ret = TaskStatus.query.filter( TaskStatus.id == task_status_id From a7c0483dff6e505411f506e3045ec9c400c22943 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:05:05 +0100 Subject: [PATCH 173/286] Removed seemingly dead method --- .vulture.ignore | 1 - source/app/datamgmt/dashboard/dashboard_db.py | 9 --------- 2 files changed, 10 deletions(-) diff --git a/.vulture.ignore b/.vulture.ignore index 053d1f142..9bf206a32 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -269,7 +269,6 @@ get_case_client # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/ ctx_get_user_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/context/context_db.py:31) update_gtask_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:131) update_utask_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:147) -get_task_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:166) _.is_evidence_registered # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:24) _.add_evidence # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:32) _.added_by_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/modules_db.py:54) diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index 8245dd3e7..6f51c8d1b 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -17,15 +17,6 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from app.models.cases import Cases -from app.models.models import TaskStatus - - -def get_task_status(task_status_id): - ret = TaskStatus.query.filter( - TaskStatus.id == task_status_id - ).first() - - return ret def list_user_cases(user_identifier, show_all=False): From 2eb0b28671f4c1aadda8a481220299c94bfa2bf4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:06:58 +0100 Subject: [PATCH 174/286] Grouped code --- source/app/blueprints/rest/dashboard_routes.py | 5 +++-- source/app/business/cases.py | 5 +++-- source/app/datamgmt/case/case_db.py | 12 ++++++++++++ source/app/datamgmt/dashboard/dashboard_db.py | 12 ------------ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index e75e8f512..57bf24bf4 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -33,10 +33,11 @@ from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user -from app.datamgmt.dashboard.dashboard_db import list_user_cases from app.datamgmt.case.case_db import list_user_reviews +from app.datamgmt.case.case_db import list_user_cases from app.datamgmt.case.case_tasks_db import get_tasks_status -from app.datamgmt.global_tasks import list_global_tasks, get_global_task +from app.datamgmt.global_tasks import list_global_tasks +from app.datamgmt.global_tasks import get_global_task from app.datamgmt.case.case_tasks_db import list_user_tasks from app.forms import CaseGlobalTaskForm from app.iris_engine.module_handler.module_handler import call_modules_hook diff --git a/source/app/business/cases.py b/source/app/business/cases.py index d21a85571..5ffc80433 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -29,6 +29,7 @@ from app.iris_engine.utils.tracker import track_activity from app.iris_engine.access_control.utils import ac_set_new_case_access from app.datamgmt.case.case_db import case_db_exists +from app.datamgmt.case.case_db import list_user_cases from app.datamgmt.case.case_db import case_db_save from app.datamgmt.case.case_db import list_user_reviews from app.datamgmt.case.case_db import save_case_tags @@ -53,9 +54,9 @@ from app.datamgmt.reporter.report_db import export_case_comments_json from app.datamgmt.reporter.report_db import export_case_notes_json from app.datamgmt.manage.manage_cases_db import get_filtered_cases -from app.datamgmt.dashboard.dashboard_db import list_user_cases from app.datamgmt.case.case_db import get_first_case_with_customer -from app.models.cases import Cases, ReviewStatusList +from app.models.cases import Cases +from app.models.cases import ReviewStatusList from app.models.customers import Client diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index 4a27956a6..1a2e26aa8 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -264,3 +264,15 @@ def list_user_reviews(user_identifier): ).all() return ct + + +def list_user_cases(user_identifier, show_all=False): + if show_all: + return Cases.query.filter( + Cases.owner_id == user_identifier + ).all() + + return Cases.query.filter( + Cases.owner_id == user_identifier, + Cases.close_date == None + ).all() diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py index 6f51c8d1b..5c8276fe2 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/dashboard/dashboard_db.py @@ -16,16 +16,4 @@ # 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.models.cases import Cases - -def list_user_cases(user_identifier, show_all=False): - if show_all: - return Cases.query.filter( - Cases.owner_id == user_identifier - ).all() - - return Cases.query.filter( - Cases.owner_id == user_identifier, - Cases.close_date == None - ).all() From 8c167dce48e4a8a78882d8d72d8ac462af8d8487 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:07:46 +0100 Subject: [PATCH 175/286] Removed now unused module --- source/app/datamgmt/dashboard/__init__.py | 0 source/app/datamgmt/dashboard/dashboard_db.py | 19 ------------------- 2 files changed, 19 deletions(-) delete mode 100644 source/app/datamgmt/dashboard/__init__.py delete mode 100644 source/app/datamgmt/dashboard/dashboard_db.py diff --git a/source/app/datamgmt/dashboard/__init__.py b/source/app/datamgmt/dashboard/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/dashboard/dashboard_db.py deleted file mode 100644 index 5c8276fe2..000000000 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ /dev/null @@ -1,19 +0,0 @@ -# IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net -# -# 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 d6c90e0e5167758de5ee1588e437a52d2c636a80 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:32:29 +0100 Subject: [PATCH 176/286] Updated lint-import constraints to the fact that dashboard does not exist anymore --- pyproject.toml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 149781051..6b8c41ea7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,13 +42,6 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the persistence layer" type = "forbidden" -source_modules = "app.datamgmt.dashboard" -forbidden_modules = "app.blueprints" -allow_indirect_imports = true - -[[tool.importlinter.contracts]] -name = "Do not import API layer from the persistence layer (access_controls)" -type = "forbidden" source_modules = "app.datamgmt" forbidden_modules = "app.blueprints.access_controls" allow_indirect_imports = true From 04e3c9fccf43953b2df6a57410eb8a0a8e60560c Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:34:12 +0100 Subject: [PATCH 177/286] Added test --- tests/tests_rest_global_tasks.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py index 3617db1d6..a6bb8474e 100644 --- a/tests/tests_rest_global_tasks.py +++ b/tests/tests_rest_global_tasks.py @@ -54,3 +54,11 @@ def test_delete_global_task_should_return_204(self): identifier = response['task_id'] response = self._subject.delete(f'/api/v2/global-tasks/{identifier}') self.assertEqual(204, response.status_code) + + def test_get_global_task_should_return_404_when_deleted(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + self._subject.delete(f'/api/v2/global-tasks/{identifier}') + response = self._subject.get(f'/api/v2/global-tasks/{identifier}') + self.assertEqual(404, response.status_code) From 55d1cadf7b4380d39161c9fb1aa669a32bfa7e60 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:36:20 +0100 Subject: [PATCH 178/286] Deprecated POST /global/tasks/delete/ --- source/app/blueprints/rest/dashboard_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index 57bf24bf4..b2b135729 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -272,6 +272,7 @@ def edit_gtask(cur_id, caseid): @dashboard_rest_blueprint.route('/global/tasks/delete/', methods=['POST']) +@endpoint_deprecated('DELETE', '/api/v2/global-tasks/{identifier}') @ac_api_requires() @ac_requires_case_identifier() def gtask_delete(cur_id, caseid): From 5fa70af607fe5cd0a462961910e140e3aad6ea7d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:48:08 +0100 Subject: [PATCH 179/286] Added an import constraint --- pyproject.toml | 9 ++++++++- .../rest/case/case_assets_routes.py | 11 +++++++---- source/app/business/comments.py | 2 +- source/app/datamgmt/case/case_assets_db.py | 19 ++++++------------- source/app/datamgmt/comments.py | 8 ++++++++ 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6b8c41ea7..7e96309fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,12 +40,19 @@ forbidden_modules = "sqlalchemy" allow_indirect_imports = true [[tool.importlinter.contracts]] -name = "Do not import API layer from the persistence layer" +name = "Do not import API layer from the persistence layer (access_controls)" type = "forbidden" source_modules = "app.datamgmt" forbidden_modules = "app.blueprints.access_controls" allow_indirect_imports = true +[[tool.importlinter.contracts]] +name = "Do not import API layer from the persistence layer (iris_user)" +type = "forbidden" +source_modules = "app.datamgmt.case.case_assets_db" +forbidden_modules = "app.blueprints.iris_user" +allow_indirect_imports = true + [[tool.importlinter.contracts]] name = "Do not import marshables from the persistence layer" type = "forbidden" diff --git a/source/app/blueprints/rest/case/case_assets_routes.py b/source/app/blueprints/rest/case/case_assets_routes.py index d9a222a95..1cb93b1a1 100644 --- a/source/app/blueprints/rest/case/case_assets_routes.py +++ b/source/app/blueprints/rest/case/case_assets_routes.py @@ -44,6 +44,7 @@ from app.datamgmt.case.case_assets_db import get_case_asset_comments from app.datamgmt.case.case_assets_db import get_similar_assets from app.datamgmt.case.case_db import get_case_client_id +from app.datamgmt.comments import get_comment from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes from app.datamgmt.manage.manage_users_db import get_user_cases_fast from app.datamgmt.states import get_assets_state @@ -407,11 +408,13 @@ def case_comment_asset_edit(cur_id, com_id, caseid): @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_comment_asset_delete(cur_id, com_id, caseid): - success, msg = delete_asset_comment(cur_id, com_id) - if not success: - return response_error(msg) + comment = get_comment(iris_current_user, com_id) + if not comment: + return response_error('You are not allowed to delete this comment') + + delete_asset_comment(cur_id, comment) call_modules_hook('on_postload_asset_comment_delete', com_id, caseid=caseid) track_activity(f'comment {com_id} on asset {cur_id} deleted', caseid=caseid) - return response_success(msg) + return response_success('Comment deleted') diff --git a/source/app/business/comments.py b/source/app/business/comments.py index a458f4c1e..1e317de0b 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -303,7 +303,7 @@ def comments_delete_for_alert(comment: Comments): def comments_delete_for_asset(asset: CaseAssets, comment: Comments): - delete_asset_comment(asset.asset_id, comment.comment_id) + delete_asset_comment(asset.asset_id, comment) call_modules_hook('on_postload_asset_comment_delete', comment.comment_id, caseid=comment.comment_case_id) track_activity(f'comment {comment.comment_id} on asset {asset.asset_id} deleted', caseid=comment.comment_case_id) diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index 4a3ec616c..e82c1c37c 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -26,14 +26,16 @@ from app.datamgmt.db_operations import db_create from app.db import db from app.logger import logger -from app.blueprints.iris_user import iris_current_user from app.datamgmt.filtering import get_filtered_data from app.datamgmt.states import update_assets_state from app.models.models import CaseEventsAssets from app.models.cases import Cases, CaseStatus from app.models.comments import Comments from app.models.comments import AssetComments -from app.models.assets import CompromiseStatus, AssetsType, CaseAssets, AnalysisStatus +from app.models.assets import CompromiseStatus +from app.models.assets import AssetsType +from app.models.assets import CaseAssets +from app.models.assets import AnalysisStatus from app.models.iocs import Ioc from app.models.models import IocAssetLink from app.models.models import IocType @@ -369,24 +371,15 @@ def get_case_asset_comment(asset_id, comment_id) -> Optional[Comments]: ).first() -def delete_asset_comment(asset_id, comment_id): - comment = Comments.query.filter( - Comments.comment_id == comment_id, - Comments.comment_user_id == iris_current_user.id - ).first() - if not comment: - return False, "You are not allowed to delete this comment" - +def delete_asset_comment(asset_id, comment: Comments): AssetComments.query.filter( AssetComments.comment_asset_id == asset_id, - AssetComments.comment_id == comment_id + AssetComments.comment_id == comment.comment_alert_id ).delete() db.session.delete(comment) db.session.commit() - return True, "Comment deleted" - def get_asset_by_name(asset_name, caseid): asset = CaseAssets.query.filter( diff --git a/source/app/datamgmt/comments.py b/source/app/datamgmt/comments.py index a33dbe152..802384c8a 100644 --- a/source/app/datamgmt/comments.py +++ b/source/app/datamgmt/comments.py @@ -15,6 +15,7 @@ # 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 typing import Optional from flask_sqlalchemy.pagination import Pagination from sqlalchemy import and_ @@ -136,3 +137,10 @@ def search_comments(search_value): ).all() return [row._asdict() for row in comments] + + +def get_comment(user, identifier) -> Optional[Comments]: + return Comments.query.filter( + Comments.comment_id == identifier, + Comments.comment_user_id == user.id + ).first() From 022dbd93a9df4b9637754574f34d5a50108c49ab Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:53:55 +0100 Subject: [PATCH 180/286] Factored code to delete in db somewhat --- source/app/datamgmt/alerts/alerts_db.py | 9 +++------ source/app/datamgmt/case/case_assets_db.py | 5 ++--- source/app/datamgmt/case/case_events_db.py | 5 ++--- source/app/datamgmt/case/case_iocs_db.py | 5 ++--- source/app/datamgmt/case/case_notes_db.py | 9 +++------ source/app/datamgmt/case/case_rfiles_db.py | 5 ++--- source/app/datamgmt/case/case_tasks_db.py | 5 ++--- source/app/datamgmt/client/client_db.py | 9 +++------ source/app/datamgmt/comments.py | 4 ++-- source/app/datamgmt/datastore/datastore_db.py | 11 ++++------- source/app/datamgmt/db_operations.py | 5 +++++ source/app/datamgmt/manage/manage_groups_db.py | 4 ++-- 12 files changed, 32 insertions(+), 44 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index c89a2d88e..1e91ce048 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -72,8 +72,7 @@ from app.models.authorization import User from app.schema.marshables import EventSchema from app.util import add_obj_history_entry -from app.datamgmt.db_operations import db_create - +from app.datamgmt.db_operations import db_create, db_delete relationship_model_map = { 'owner': User, @@ -1283,8 +1282,7 @@ def delete_alert_comment(comment_id: int, alert_id: int) -> Tuple[bool, str]: if not comment: return False, "You are not allowed to delete this comment" - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted successfully" @@ -1423,5 +1421,4 @@ def get_alert_status_by_name(name: str) -> AlertStatus: def delete_alert(alert): - db.session.delete(alert) - db.session.commit() + db_delete(alert) diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index e82c1c37c..b2b0a4816 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -23,7 +23,7 @@ from sqlalchemy import func from flask_sqlalchemy.pagination import Pagination -from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_create, db_delete from app.db import db from app.logger import logger from app.datamgmt.filtering import get_filtered_data @@ -377,8 +377,7 @@ def delete_asset_comment(asset_id, comment: Comments): AssetComments.comment_id == comment.comment_alert_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) def get_asset_by_name(asset_name, caseid): diff --git a/source/app/datamgmt/case/case_events_db.py b/source/app/datamgmt/case/case_events_db.py index 259e0f4f8..046de3794 100644 --- a/source/app/datamgmt/case/case_events_db.py +++ b/source/app/datamgmt/case/case_events_db.py @@ -18,7 +18,7 @@ from sqlalchemy import and_ -from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_create, db_delete from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.states import update_timeline_state @@ -171,8 +171,7 @@ def delete_event_comment(event_id, comment_id): EventComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted" diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index b8e75748a..828736a4a 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -18,7 +18,7 @@ from sqlalchemy import and_ -from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_create, db_delete from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.filtering import get_filtered_data @@ -275,8 +275,7 @@ def delete_ioc_comment(ioc_id, comment_id): IocComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted" diff --git a/source/app/datamgmt/case/case_notes_db.py b/source/app/datamgmt/case/case_notes_db.py index 57d72fff1..a47ea433b 100644 --- a/source/app/datamgmt/case/case_notes_db.py +++ b/source/app/datamgmt/case/case_notes_db.py @@ -22,7 +22,7 @@ from datetime import datetime from flask_sqlalchemy.pagination import Pagination -from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_create, db_delete from app.db import db from app.datamgmt.persistence_error import PersistenceError from app.blueprints.iris_user import iris_current_user @@ -67,9 +67,7 @@ def delete_directory(directory: NoteDirectory): for subdirectory in directory.subdirectories: delete_directory(subdirectory) - # Delete the directory - db.session.delete(directory) - db.session.commit() + db_delete(directory) return True @@ -430,8 +428,7 @@ def delete_note_comment(note_id, comment_id): NotesComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, 'Comment deleted' diff --git a/source/app/datamgmt/case/case_rfiles_db.py b/source/app/datamgmt/case/case_rfiles_db.py index ba5539306..5e116daa3 100644 --- a/source/app/datamgmt/case/case_rfiles_db.py +++ b/source/app/datamgmt/case/case_rfiles_db.py @@ -20,7 +20,7 @@ from sqlalchemy import desc from flask_sqlalchemy.pagination import Pagination -from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_create, db_delete from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes @@ -179,7 +179,6 @@ def delete_evidence_comment(evidence_id, comment_id): EvidencesComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted" diff --git a/source/app/datamgmt/case/case_tasks_db.py b/source/app/datamgmt/case/case_tasks_db.py index 054eb161e..0d65f1c95 100644 --- a/source/app/datamgmt/case/case_tasks_db.py +++ b/source/app/datamgmt/case/case_tasks_db.py @@ -22,7 +22,7 @@ from sqlalchemy import desc from sqlalchemy import and_ -from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_create, db_delete from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.conversions import convert_sort_direction @@ -313,8 +313,7 @@ def delete_task_comment(task_id, comment_id): TaskComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted" diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index eea6bc381..8851eb07d 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -22,6 +22,7 @@ from typing import Optional from flask_sqlalchemy.pagination import Pagination +from app.datamgmt.db_operations import db_delete from app.db import db from app.models.errors import ElementInUseError from app.models.cases import Cases @@ -140,18 +141,14 @@ def get_client_contact(contact_id: int) -> Contact: def delete_contact(contact: Contact): try: - - db.session.delete(contact) - db.session.commit() - + db_delete(contact) except Exception: raise ElementInUseError('A currently referenced contact cannot be deleted') def delete_client(customer: Client) -> None: try: - db.session.delete(customer) - db.session.commit() + db_delete(customer) except Exception: raise ElementInUseError('Cannot delete a referenced customer') diff --git a/source/app/datamgmt/comments.py b/source/app/datamgmt/comments.py index 802384c8a..ad23b1fdc 100644 --- a/source/app/datamgmt/comments.py +++ b/source/app/datamgmt/comments.py @@ -20,6 +20,7 @@ from flask_sqlalchemy.pagination import Pagination from sqlalchemy import and_ +from app.datamgmt.db_operations import db_delete from app.db import db from app.models.cases import Cases from app.models.comments import Comments @@ -107,8 +108,7 @@ def get_filtered_event_comments(event_identifier, pagination_parameters: Paginat def delete_comment(comment: Comments): - db.session.delete(comment) - db.session.commit() + db_delete(comment) def user_has_comments(user: User): diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 7632e6640..420de57bb 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -25,7 +25,7 @@ from sqlalchemy import func from app import app -from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_create, db_delete from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.models import CaseReceivedFile @@ -249,8 +249,7 @@ def datastore_iter_deletion(dsp, cid): datastore_delete_files_of_path(dsp.path_id, cid) - db.session.delete(dsp) - db.session.commit() + db_delete(dsp) return None @@ -268,8 +267,7 @@ def datastore_delete_files_of_path(node_id, cid): if fln.is_file(): fln.unlink(missing_ok=True) - db.session.delete(dsf_list_item) - db.session.commit() + db_delete(dsf_list_item) return @@ -340,8 +338,7 @@ def datastore_delete_file(cur_id, cid): if fln.is_file(): fln.unlink(missing_ok=True) - db.session.delete(dsf) - db.session.commit() + db_delete(dsf) return False, f'File {cur_id} deleted' diff --git a/source/app/datamgmt/db_operations.py b/source/app/datamgmt/db_operations.py index 3269a46a8..ddf789009 100644 --- a/source/app/datamgmt/db_operations.py +++ b/source/app/datamgmt/db_operations.py @@ -22,3 +22,8 @@ def db_create(element): db.session.add(element) db.session.commit() + + +def db_delete(element): + db.session.delete(element) + db.session.commit() diff --git a/source/app/datamgmt/manage/manage_groups_db.py b/source/app/datamgmt/manage/manage_groups_db.py index e5d28feba..157acd70b 100644 --- a/source/app/datamgmt/manage/manage_groups_db.py +++ b/source/app/datamgmt/manage/manage_groups_db.py @@ -16,6 +16,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import and_ +from app.datamgmt.db_operations import db_delete from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.case.case_db import get_case @@ -205,8 +206,7 @@ def delete_group(group): UserGroup.query.filter(UserGroup.group_id == group.group_id).delete() GroupCaseAccess.query.filter(GroupCaseAccess.group_id == group.group_id).delete() - db.session.delete(group) - db.session.commit() + db_delete(group) def add_case_access_to_group(group, cases_list, access_level): From 84f7d3e9a2113f7068594afa58ec3c5999503007 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:17:47 +0100 Subject: [PATCH 181/286] Fixed delete comment --- source/app/datamgmt/case/case_assets_db.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index b2b0a4816..5855c56f4 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -351,30 +351,16 @@ def get_case_assets_comments_count(asset_id): def get_case_asset_comment(asset_id, comment_id) -> Optional[Comments]: - return AssetComments.query.filter( + return Comments.query.join(AssetComments.comment).filter( AssetComments.comment_asset_id == asset_id, - AssetComments.comment_id == comment_id - ).with_entities( - Comments.comment_id, - Comments.comment_text, - Comments.comment_date, - Comments.comment_update_date, - Comments.comment_uuid, - Comments.comment_user_id, - Comments.comment_case_id, - User.name, - User.user - ).join( - AssetComments.comment - ).join( - Comments.user + Comments.comment_id == comment_id ).first() def delete_asset_comment(asset_id, comment: Comments): AssetComments.query.filter( AssetComments.comment_asset_id == asset_id, - AssetComments.comment_id == comment.comment_alert_id + AssetComments.comment_id == comment.comment_id ).delete() db_delete(comment) From 9027e9c0f78b2ba9e92f135fe365f1270949f82b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:24:08 +0100 Subject: [PATCH 182/286] Put some more information in .vulture.ignore --- .vulture.ignore | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.vulture.ignore b/.vulture.ignore index 9bf206a32..9cf1360dc 100644 --- a/.vulture.ignore +++ b/.vulture.ignore @@ -1,4 +1,8 @@ -EvidenceStorage # Do not remove: this is used by package iris-module-interface (see https://github.com/dfir-iris/iris-module-interface, iris_interface/IrisModuleInterface.py) +########## Do not remove: used by package iris-module-interface (see https://github.com/dfir-iris/iris-module-interface, iris_interface/IrisModuleInterface.py) +EvidenceStorage +deregister_from_hook +get_mod_config_by_name +register_hook ######### Alembic variables revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:14) down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:15) @@ -318,11 +322,8 @@ case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/sour CaseNoteForm # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:223) ac_get_effective_permissions_from_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/access_control/utils.py:138) check_module_compatibility # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:46) -get_mod_config_by_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:234) -register_hook # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:323) _.wait_till_return # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:372) _.max_retry # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:374) -deregister_from_hook # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:391) on_task_init # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/tasker/tasks.py:33) chunks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/tasker/tasks.py:92) notify_server_ready_to_reboot # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:70) From aa477fb383c1d13e8fb8cad42b4888ebae5cd4c1 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:25:36 +0100 Subject: [PATCH 183/286] Fixed ruff warning --- source/app/datamgmt/comments.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/app/datamgmt/comments.py b/source/app/datamgmt/comments.py index ad23b1fdc..1640ff111 100644 --- a/source/app/datamgmt/comments.py +++ b/source/app/datamgmt/comments.py @@ -21,7 +21,6 @@ from sqlalchemy import and_ from app.datamgmt.db_operations import db_delete -from app.db import db from app.models.cases import Cases from app.models.comments import Comments from app.models.comments import EventComments From 8e847c88570d501c26df1120bdba41b8e5422b11 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:28:05 +0100 Subject: [PATCH 184/286] Removed an occurence of from app import app --- source/app/datamgmt/iris_engine/modules_db.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/source/app/datamgmt/iris_engine/modules_db.py b/source/app/datamgmt/iris_engine/modules_db.py index f5e4f316a..68409fd12 100644 --- a/source/app/datamgmt/iris_engine/modules_db.py +++ b/source/app/datamgmt/iris_engine/modules_db.py @@ -20,15 +20,13 @@ from app.datamgmt.db_operations import db_create from app.db import db -from app import app +from app.logger import logger from app.blueprints.iris_user import iris_current_user from app.models.models import IrisHook from app.models.models import IrisModule from app.models.models import IrisModuleHook from app.models.authorization import User -log = app.logger - def iris_module_exists(module_name): return IrisModule.query.filter(IrisModule.module_name == module_name).first() is not None @@ -244,7 +242,7 @@ def parse_module_parameter(module_parameter): param_name = param.split('##')[1] except Exception as e: - log.exception(e) + logger.exception(e) return None, None, None, None mod_config, mod_name, mod_iname = get_module_config_from_id(mod_id) From 55479592e4233f2753a93fa3a046ee9965628a31 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:43:20 +0100 Subject: [PATCH 185/286] Moved down code into the persistence layer --- source/app/datamgmt/iris_engine/modules_db.py | 10 ++++++++++ .../iris_engine/module_handler/module_handler.py | 14 ++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/source/app/datamgmt/iris_engine/modules_db.py b/source/app/datamgmt/iris_engine/modules_db.py index 68409fd12..f11d37851 100644 --- a/source/app/datamgmt/iris_engine/modules_db.py +++ b/source/app/datamgmt/iris_engine/modules_db.py @@ -257,3 +257,13 @@ def parse_module_parameter(module_parameter): return None, None, None, None return mod_config, mod_id, mod_name, mod_iname, parameter + + +def deregister_module_from_hook(module_id: int, iris_hook_name: str): + hooks = IrisModuleHook.query.filter( + IrisModuleHook.module_id == module_id, + IrisHook.hook_name == iris_hook_name, + IrisModuleHook.hook_id == IrisHook.id + ).all() + for hook in hooks: + db.session.delete(hook) diff --git a/source/app/iris_engine/module_handler/module_handler.py b/source/app/iris_engine/module_handler/module_handler.py index e86298569..8ac12cf7c 100644 --- a/source/app/iris_engine/module_handler/module_handler.py +++ b/source/app/iris_engine/module_handler/module_handler.py @@ -17,8 +17,8 @@ # 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. -import traceback +import traceback import base64 import importlib from packaging import version @@ -32,6 +32,7 @@ from app import celery from app.db import db from app.datamgmt.iris_engine.modules_db import get_module_config_from_hname +from app.datamgmt.iris_engine.modules_db import deregister_module_from_hook from app.datamgmt.iris_engine.modules_db import iris_module_add from app.datamgmt.iris_engine.modules_db import iris_module_exists from app.datamgmt.iris_engine.modules_db import modules_list_pipelines @@ -398,15 +399,8 @@ def deregister_from_hook(module_id: int, iris_hook_name: str): :return: IrisInterfaceStatus object """ logger.info(f'Deregistering module #{module_id} from {iris_hook_name}') - hooks = IrisModuleHook.query.filter( - IrisModuleHook.module_id == module_id, - IrisHook.hook_name == iris_hook_name, - IrisModuleHook.hook_id == IrisHook.id - ).all() - if hooks: - for hook in hooks: - logger.info(f'Deregistered module #{module_id} from {iris_hook_name}') - db.session.delete(hook) + deregister_module_from_hook(module_id, iris_hook_name) + logger.info(f'Deregistered module #{module_id} from {iris_hook_name}') return True, ['Hook deregistered'] From de4607f13f6b071b8fd49d211a54f1cc77136be1 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:49:04 +0100 Subject: [PATCH 186/286] One import per line --- source/app/datamgmt/case/case_assets_db.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index 5855c56f4..f3666de60 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -23,13 +23,15 @@ from sqlalchemy import func from flask_sqlalchemy.pagination import Pagination -from app.datamgmt.db_operations import db_create, db_delete +from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_delete from app.db import db from app.logger import logger from app.datamgmt.filtering import get_filtered_data from app.datamgmt.states import update_assets_state from app.models.models import CaseEventsAssets -from app.models.cases import Cases, CaseStatus +from app.models.cases import Cases +from app.models.cases import CaseStatus from app.models.comments import Comments from app.models.comments import AssetComments from app.models.assets import CompromiseStatus From 8391039e7832c2a843a0e1935edc7d76f78e9390 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:49:41 +0100 Subject: [PATCH 187/286] Removed unnecessary print in test --- tests/tests_rest_module_tasks.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/tests_rest_module_tasks.py b/tests/tests_rest_module_tasks.py index 8101c4940..9ca5c9463 100644 --- a/tests/tests_rest_module_tasks.py +++ b/tests/tests_rest_module_tasks.py @@ -38,7 +38,4 @@ def test_get_module_tasks_should_return_case_identifier(self): self._subject.create(f'/manage/modules/disable/{module_identifier}', {}) response = self._subject.get('/dim/tasks/list/1').json() - print('--------------------------------------------------------------------------------------------') - print(response['data'][0]) - print('--------------------------------------------------------------------------------------------') self.assertEqual(f'Case #{case_identifier}', response['data'][0]['case']) From 30756fc857f059cf48a987d85ebf08a078b1273d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:56:08 +0100 Subject: [PATCH 188/286] Removed unused parameter, as indicated by deepsource --- source/app/blueprints/rest/datastore_routes.py | 4 ++-- source/app/datamgmt/datastore/datastore_db.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/datastore_routes.py b/source/app/blueprints/rest/datastore_routes.py index 31cdb80ef..b6128d865 100644 --- a/source/app/blueprints/rest/datastore_routes.py +++ b/source/app/blueprints/rest/datastore_routes.py @@ -137,7 +137,7 @@ def datastore_update_file(cur_id: int, caseid: int): msg_added_as = '' if dsf.file_is_ioc: - datastore_add_file_as_ioc(dsf, caseid) + datastore_add_file_as_ioc(dsf) msg_added_as += 'and added in IOC' if dsf.file_is_evidence: @@ -262,7 +262,7 @@ def datastore_add_file(cur_id: int, caseid: int): msg_added_as = '' if dsf_sc.file_is_ioc: - datastore_add_file_as_ioc(dsf_sc, caseid) + datastore_add_file_as_ioc(dsf_sc) msg_added_as += 'and added in IOC' if dsf_sc.file_is_evidence: diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 420de57bb..e78e41551 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -343,7 +343,7 @@ def datastore_delete_file(cur_id, cid): return False, f'File {cur_id} deleted' -def datastore_add_file_as_ioc(dsf, caseid): +def datastore_add_file_as_ioc(dsf): ioc = Ioc.query.filter( Ioc.ioc_value == dsf.file_sha256 ).first() From 13def3270f939fc66ddb11fd4ccc93af031d0eef Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:01:23 +0100 Subject: [PATCH 189/286] Fixed some deepsource warnings --- source/app/blueprints/rest/v2/global_tasks.py | 3 ++- source/app/blueprints/rest/v2/manage_routes/customers.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/global_tasks.py b/source/app/blueprints/rest/v2/global_tasks.py index fff24ee21..0c2b90623 100644 --- a/source/app/blueprints/rest/v2/global_tasks.py +++ b/source/app/blueprints/rest/v2/global_tasks.py @@ -58,7 +58,8 @@ def read(self, identifier): except ObjectNotFoundError: return response_api_not_found() - def delete(self, identifier): + @staticmethod + def delete(identifier): try: global_task = global_tasks_get(identifier) global_tasks_delete(global_task) diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index f921e38b8..e8b6cadbc 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -84,7 +84,8 @@ def update(self, identifier): except ObjectNotFoundError: return response_api_not_found() - def delete(self, identifier): + @staticmethod + def delete(identifier): try: customer = customers_get(identifier) customers_delete(customer) From 313aab0c085aa60a5b5ce7785110fd5bd8417a5d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 07:44:08 +0100 Subject: [PATCH 190/286] Started implementation of PUT /api/v2/global-tasks/{identifier} --- source/app/blueprints/rest/v2/global_tasks.py | 22 +++++++++++++++++++ source/app/business/global_tasks.py | 16 +++++++++++++- source/app/datamgmt/global_tasks.py | 4 ++++ tests/tests_rest_global_tasks.py | 8 +++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/v2/global_tasks.py b/source/app/blueprints/rest/v2/global_tasks.py index 0c2b90623..89c59e066 100644 --- a/source/app/blueprints/rest/v2/global_tasks.py +++ b/source/app/blueprints/rest/v2/global_tasks.py @@ -31,6 +31,7 @@ from app.blueprints.rest.endpoints import response_api_deleted from app.business.global_tasks import global_tasks_create from app.business.global_tasks import global_tasks_get +from app.business.global_tasks import global_tasks_update from app.business.global_tasks import global_tasks_delete from app.models.errors import ObjectNotFoundError @@ -50,6 +51,21 @@ def create(self): except ValidationError as e: return response_api_error('Data error', data=e.messages) + def update(self, identifier): + try: + task = global_tasks_get(identifier) + request_data = call_deprecated_on_preload_modules_hook('global_task_update', request.get_json()) + task = self._schema.load(request_data, instance=task) + task = global_tasks_update(iris_current_user, task) + result = self._schema.dump(task) + return response_api_success(result) + + except ValidationError as e: + return response_api_error('Data error', data=e.messages) + + except ObjectNotFoundError: + return response_api_not_found() + def read(self, identifier): try: global_task = global_tasks_get(identifier) @@ -86,6 +102,12 @@ def get_global_task(identifier): return global_tasks_operations.read(identifier) +@global_tasks_blueprint.put('/') +@ac_api_requires() +def put_glboal_task(identifier): + return global_tasks_operations.update(identifier) + + @global_tasks_blueprint.delete('/') @ac_api_requires() def delete_global_task(identifier): diff --git a/source/app/business/global_tasks.py b/source/app/business/global_tasks.py index 426c834a5..927a05b2b 100644 --- a/source/app/business/global_tasks.py +++ b/source/app/business/global_tasks.py @@ -22,7 +22,9 @@ from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.datamgmt.db_operations import db_create -from app.datamgmt.global_tasks import delete_global_task, get_global_task_by_identifier +from app.datamgmt.global_tasks import get_global_task_by_identifier +from app.datamgmt.global_tasks import update_global_task +from app.datamgmt.global_tasks import delete_global_task def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: @@ -46,6 +48,18 @@ def global_tasks_get(identifier) -> GlobalTasks: return task +def global_tasks_update(user, task: GlobalTasks) -> GlobalTasks: + task.task_userid_update = user.id + task.task_last_update = datetime.utcnow() + + update_global_task() + + task = call_modules_hook('on_postload_global_task_update', data=task) + track_activity(f'updated global task {task.task_title} (status {task.task_status_id})') + + return task + + def global_tasks_delete(task: GlobalTasks): call_modules_hook('on_preload_global_task_delete', task.id) diff --git a/source/app/datamgmt/global_tasks.py b/source/app/datamgmt/global_tasks.py index 3af8a7251..729f3736a 100644 --- a/source/app/datamgmt/global_tasks.py +++ b/source/app/datamgmt/global_tasks.py @@ -29,6 +29,10 @@ def delete_global_task(task: GlobalTasks): db.session.commit() +def update_global_task(): + db.session.commit() + + def list_global_tasks(): ct = GlobalTasks.query.with_entities( GlobalTasks.id.label("task_id"), diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py index a6bb8474e..21a40eb6c 100644 --- a/tests/tests_rest_global_tasks.py +++ b/tests/tests_rest_global_tasks.py @@ -62,3 +62,11 @@ def test_get_global_task_should_return_404_when_deleted(self): self._subject.delete(f'/api/v2/global-tasks/{identifier}') response = self._subject.get(f'/api/v2/global-tasks/{identifier}') self.assertEqual(404, response.status_code) + + def test_update_global_task_should_return_200(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + body = {'task_title': 'new title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.update(f'/api/v2/global-tasks/{identifier}', body) + self.assertEqual(200, response.status_code) From 833bb6e5ef9c9c0ab55a5145aec20ea98a060d5c Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 07:58:26 +0100 Subject: [PATCH 191/286] Do not import iris_current_user from app.datamgmt.case.case_events_db --- pyproject.toml | 2 +- .../app/blueprints/rest/case/case_timeline_routes.py | 2 +- .../app/blueprints/rest/v2/events_routes/comments.py | 2 +- source/app/business/comments.py | 4 ++-- source/app/datamgmt/case/case_events_db.py | 11 ++++++----- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7e96309fb..6e9f9be28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the persistence layer (iris_user)" type = "forbidden" -source_modules = "app.datamgmt.case.case_assets_db" +source_modules = ["app.datamgmt.case.case_assets_db", "app.datamgmt.case.case_events_db"] forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index 0159b4cab..09462dc77 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -96,7 +96,7 @@ def case_comments_get(cur_id, caseid): @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_comment_delete(cur_id, com_id, caseid): - success, msg = delete_event_comment(cur_id, com_id) + success, msg = delete_event_comment(iris_current_user, cur_id, com_id) if not success: return response_error(msg) diff --git a/source/app/blueprints/rest/v2/events_routes/comments.py b/source/app/blueprints/rest/v2/events_routes/comments.py index ae428fae7..91b62382d 100644 --- a/source/app/blueprints/rest/v2/events_routes/comments.py +++ b/source/app/blueprints/rest/v2/events_routes/comments.py @@ -103,7 +103,7 @@ def delete(self, event_identifier, identifier): if comment.comment_user_id != iris_current_user.id: return ac_api_return_access_denied() - comments_delete_for_event(event, comment) + comments_delete_for_event(iris_current_user, event, comment) return response_api_deleted() except ObjectNotFoundError: return response_api_not_found() diff --git a/source/app/business/comments.py b/source/app/business/comments.py index 1e317de0b..7e4048fe9 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -337,8 +337,8 @@ def comments_delete_for_task(task: CaseTasks, comment: Comments): track_activity(f'comment {comment.comment_id} on task {task.id} deleted', caseid=comment.comment_case_id) -def comments_delete_for_event(event: CasesEvent, comment: Comments): - delete_event_comment(event.event_id, comment.comment_id) +def comments_delete_for_event(user, event: CasesEvent, comment: Comments): + delete_event_comment(user.id, event.event_id, comment.comment_id) call_modules_hook('on_postload_event_comment_delete', comment.comment_id, caseid=comment.comment_case_id) track_activity(f'comment {comment.comment_id} on event {event.event_id} deleted', caseid=comment.comment_case_id) diff --git a/source/app/datamgmt/case/case_events_db.py b/source/app/datamgmt/case/case_events_db.py index 046de3794..b23bf4840 100644 --- a/source/app/datamgmt/case/case_events_db.py +++ b/source/app/datamgmt/case/case_events_db.py @@ -18,11 +18,12 @@ from sqlalchemy import and_ -from app.datamgmt.db_operations import db_create, db_delete +from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_delete from app.db import db -from app.blueprints.iris_user import iris_current_user from app.datamgmt.states import update_timeline_state -from app.models.assets import AssetsType, CaseAssets +from app.models.assets import AssetsType +from app.models.assets import CaseAssets from app.models.models import CaseEventCategory from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc @@ -158,10 +159,10 @@ def get_case_event_comment(event_id, comment_id): ).first() -def delete_event_comment(event_id, comment_id): +def delete_event_comment(user_identifier, event_id, comment_id): comment = Comments.query.filter( Comments.comment_id == comment_id, - Comments.comment_user_id == iris_current_user.id + Comments.comment_user_id == user_identifier ).first() if not comment: return False, "You are not allowed to delete this comment" From c04290cb35581680790d445009a7888f4f2c023d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:05:11 +0100 Subject: [PATCH 192/286] Moved methods in persistence layer --- source/app/datamgmt/db_operations.py | 27 +++++++++++++++++++++++++++ source/app/models/models.py | 27 --------------------------- source/app/post_init.py | 11 +++++++---- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/source/app/datamgmt/db_operations.py b/source/app/datamgmt/db_operations.py index ddf789009..183555658 100644 --- a/source/app/datamgmt/db_operations.py +++ b/source/app/datamgmt/db_operations.py @@ -24,6 +24,33 @@ def db_create(element): db.session.commit() +def create_safe(session, model, **kwargs): + instance = session.query(model).filter_by(**kwargs).first() + if instance: + return False + else: + instance = model(**kwargs) + session.add(instance) + session.commit() + return True + + +def create_safe_limited(session, model, keywords_list, **kwargs): + kwdup = kwargs.keys() + for kw in list(kwdup): + if kw not in keywords_list: + kwargs.pop(kw) + + instance = session.query(model).filter_by(**kwargs).first() + if instance: + return False + else: + instance = model(**kwargs) + session.add(instance) + session.commit() + return True + + def db_delete(element): db.session.delete(element) db.session.commit() diff --git a/source/app/models/models.py b/source/app/models/models.py index 6fb87045c..467b19a95 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -47,33 +47,6 @@ metadata = Base.metadata -def create_safe(session, model, **kwargs): - instance = session.query(model).filter_by(**kwargs).first() - if instance: - return False - else: - instance = model(**kwargs) - session.add(instance) - session.commit() - return True - - -def create_safe_limited(session, model, keywords_list, **kwargs): - kwdup = kwargs.keys() - for kw in list(kwdup): - if kw not in keywords_list: - kwargs.pop(kw) - - instance = session.query(model).filter_by(**kwargs).first() - if instance: - return False - else: - instance = model(**kwargs) - session.add(instance) - session.commit() - return True - - class EvidenceTypes(db.Model): __tablename__ = 'evidence_type' diff --git a/source/app/post_init.py b/source/app/post_init.py index c11f6fdc5..e6ec33197 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -48,15 +48,17 @@ 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 -from app.models.assets import AssetsType, AnalysisStatus +from app.models.assets import AssetsType +from app.models.assets import AnalysisStatus from app.models.alerts import Severity from app.models.alerts import AlertStatus from app.models.alerts import AlertResolutionStatus from app.models.authorization import CaseAccessLevel from app.models.authorization import Group from app.models.authorization import User -from app.models.cases import Cases, ReviewStatusList, CaseClassification +from app.models.cases import Cases +from app.models.cases import ReviewStatusList +from app.models.cases import CaseClassification from app.models.cases import CaseState from app.models.customers import Client from app.models.models import ReviewStatus @@ -71,7 +73,8 @@ from app.models.models import ServerSettings from app.models.models import TaskStatus from app.models.iocs import Tlp -from app.models.models import create_safe +from app.datamgmt.db_operations import create_safe +from app.datamgmt.db_operations import create_safe_limited 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 40b7ecf703f6645f49418de993c3d73a07354c97 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:07:11 +0100 Subject: [PATCH 193/286] Factored duplicated code --- source/app/datamgmt/db_operations.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/source/app/datamgmt/db_operations.py b/source/app/datamgmt/db_operations.py index 183555658..cc5ea2312 100644 --- a/source/app/datamgmt/db_operations.py +++ b/source/app/datamgmt/db_operations.py @@ -41,14 +41,7 @@ def create_safe_limited(session, model, keywords_list, **kwargs): if kw not in keywords_list: kwargs.pop(kw) - instance = session.query(model).filter_by(**kwargs).first() - if instance: - return False - else: - instance = model(**kwargs) - session.add(instance) - session.commit() - return True + return create_safe(session, model, **kwargs) def db_delete(element): From abdd7557d979e511b72aa61c242ca0a05a3a4d60 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:48:51 +0100 Subject: [PATCH 194/286] Separated code from data --- source/app/datamgmt/db_operations.py | 9 - source/app/post_init.py | 630 +++++++-------------------- 2 files changed, 164 insertions(+), 475 deletions(-) diff --git a/source/app/datamgmt/db_operations.py b/source/app/datamgmt/db_operations.py index cc5ea2312..2d17f4f41 100644 --- a/source/app/datamgmt/db_operations.py +++ b/source/app/datamgmt/db_operations.py @@ -35,15 +35,6 @@ def create_safe(session, model, **kwargs): return True -def create_safe_limited(session, model, keywords_list, **kwargs): - kwdup = kwargs.keys() - for kw in list(kwdup): - if kw not in keywords_list: - kwargs.pop(kw) - - return create_safe(session, model, **kwargs) - - def db_delete(element): db.session.delete(element) db.session.commit() diff --git a/source/app/post_init.py b/source/app/post_init.py index e6ec33197..e6ce758b1 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -74,7 +74,6 @@ from app.models.models import TaskStatus from app.models.iocs import Tlp from app.datamgmt.db_operations import create_safe -from app.datamgmt.db_operations import create_safe_limited 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 @@ -129,6 +128,168 @@ {'asset_name': 'Windows Account - AD - Service', 'asset_description': 'Windows Account - AD - krbtgt', 'asset_icon_not_compromised': 'user.png', 'asset_icon_compromised': 'ioc_user.png'} ] +_IOC_TYPES = [ + {'type_name': 'AS', 'type_description': 'Autonomous system'}, + {'type_name': 'aba-rtn', 'type_description': 'ABA routing transit number'}, + {'type_name': 'account', 'type_description': 'Account of any type'}, + {'type_name': 'anonymised', 'type_description': 'Anonymised value - described with the anonymisation object via a relationship'}, + {'type_name': 'attachment', 'type_description': 'Attachment with external information'}, + {'type_name': 'authentihash', 'type_description': 'Authenticode executable signature hash'}, + {'type_name': 'boolean', 'type_description': 'Boolean value - to be used in objects'}, + {'type_name': 'btc', 'type_description': 'Bitcoin Address'}, + {'type_name': 'campaign-id', 'type_description': 'Associated campaign ID'}, + {'type_name': 'campaign-name', 'type_description': 'Associated campaign name'}, + {'type_name': 'cdhash', 'type_description': 'An Apple Code Directory Hash, identifying a code-signed Mach-O executable file'}, + {'type_name': 'chrome-extension-id', 'type_description': 'Chrome extension id'}, + {'type_name': 'community-id', 'type_description': 'a community ID flow hashing algorithm to map multiple traffic monitors into common flow id'}, + {'type_name': 'cookie', 'type_description': 'HTTP cookie as often stored on the user web client. This can include authentication cookie or session cookie.'}, + {'type_name': 'dash', 'type_description': 'Dash Address'}, + {'type_name': 'datetime', 'type_description': 'Datetime in the ISO 8601 format'}, + {'type_name': 'dkim', 'type_description': 'DKIM public key'}, + {'type_name': 'dkim-signature', 'type_description': 'DKIM signature'}, + {'type_name': 'dns-soa-email', 'type_description': 'RFC1035 mandates that DNS zones should have a SOA (Statement Of Authority}, record that contains an email address where a PoC for the domain could be contacted. This can sometimes be used for attribution/linkage between different domains even if protected by whois privacy'}, + {'type_name': 'domain', 'type_description': 'A domain name used in the malware'}, + {'type_name': 'domain|ip', 'type_description': 'A domain name and its IP address (as found in DNS lookup}, separated by a |'}, + {'type_name': 'email', 'type_description': 'An e-mail address'}, + {'type_name': 'email-attachment', 'type_description': 'File name of the email attachment.'}, + {'type_name': 'email-body', 'type_description': 'Email body'}, + {'type_name': 'email-dst', 'type_description': 'The destination email address. Used to describe the recipient when describing an e-mail.'}, + {'type_name': 'email-dst-display-name', 'type_description': 'Email destination display name'}, + {'type_name': 'email-header', 'type_description': 'Email header'}, + {'type_name': 'email-message-id', 'type_description': 'The email message ID'}, + {'type_name': 'email-mime-boundary', 'type_description': 'The email mime boundary separating parts in a multipart email'}, + {'type_name': 'email-reply-to', 'type_description': 'Email reply to header'}, + {'type_name': 'email-src', 'type_description': 'The source email address. Used to describe the sender when describing an e-mail.'}, + {'type_name': 'email-src-display-name', 'type_description': 'Email source display name'}, + {'type_name': 'email-subject', 'type_description': 'The subject of the email'}, + {'type_name': 'email-thread-index', 'type_description': 'The email thread index header'}, + {'type_name': 'email-x-mailer', 'type_description': 'Email x-mailer header'}, + {'type_name': 'favicon-mmh3', 'type_description': 'favicon-mmh3 is the murmur3 hash of a favicon as used in Shodan.'}, + {'type_name': 'filename', 'type_description': 'Filename'}, + {'type_name': 'filename-pattern', 'type_description': 'A pattern in the name of a file'}, + {'type_name': 'filename|authentihash', 'type_description': 'A checksum in md5 format'}, + {'type_name': 'filename|impfuzzy', 'type_description': 'Import fuzzy hash - a fuzzy hash created based on the imports in the sample.'}, + {'type_name': 'filename|imphash', 'type_description': 'Import hash - a hash created based on the imports in the sample.'}, + {'type_name': 'filename|md5', 'type_description': 'A filename and an md5 hash separated by a |'}, + {'type_name': 'filename|pehash', 'type_description': 'A filename and a PEhash separated by a |'}, + {'type_name': 'filename|sha1', 'type_description': 'A filename and an sha1 hash separated by a |'}, + {'type_name': 'filename|sha224', 'type_description': 'A filename and a sha-224 hash separated by a |'}, + {'type_name': 'filename|sha256', 'type_description': 'A filename and an sha256 hash separated by a |'}, + {'type_name': 'filename|sha3-224', 'type_description': 'A filename and an sha3-224 hash separated by a |'}, + {'type_name': 'filename|sha3-256', 'type_description': 'A filename and an sha3-256 hash separated by a |'}, + {'type_name': 'filename|sha3-384', 'type_description': 'A filename and an sha3-384 hash separated by a |'}, + {'type_name': 'filename|sha3-512', 'type_description': 'A filename and an sha3-512 hash separated by a |'}, + {'type_name': 'filename|sha384', 'type_description': 'A filename and a sha-384 hash separated by a |'}, + {'type_name': 'filename|sha512', 'type_description': 'A filename and a sha-512 hash separated by a |'}, + {'type_name': 'filename|sha512/224', 'type_description': 'A filename and a sha-512/224 hash separated by a |'}, + {'type_name': 'filename|sha512/256', 'type_description': 'A filename and a sha-512/256 hash separated by a |'}, + {'type_name': 'filename|ssdeep', 'type_description': 'A checksum in ssdeep format'}, + {'type_name': 'filename|tlsh', 'type_description': 'A filename and a Trend Micro Locality Sensitive Hash separated by a |'}, + {'type_name': 'filename|vhash', 'type_description': 'A filename and a VirusTotal hash separated by a |'}, + {'type_name': 'first-name', 'type_description': 'First name of a natural person'}, + {'type_name': 'float', 'type_description': 'A floating point value.'}, + {'type_name': 'full-name', 'type_description': 'Full name of a natural person'}, + {'type_name': 'gene', 'type_description': 'GENE - Go Evtx sigNature Engine'}, + {'type_name': 'git-commit-id', 'type_description': 'A git commit ID.'}, + {'type_name': 'github-organisation', 'type_description': 'A github organisation'}, + {'type_name': 'github-repository', 'type_description': 'A github repository'}, + {'type_name': 'github-username', 'type_description': 'A github user name'}, + {'type_name': 'hassh-md5', 'type_description': 'hassh is a network fingerprinting standard which can be used to identify specific Client SSH implementations. The fingerprints can be easily stored, searched and shared in the form of an MD5 fingerprint.'}, + {'type_name': 'hasshserver-md5', 'type_description': 'hasshServer is a network fingerprinting standard which can be used to identify specific Server SSH implementations. The fingerprints can be easily stored, searched and shared in the form of an MD5 fingerprint.'}, + {'type_name': 'hex', 'type_description': 'A value in hexadecimal format'}, + {'type_name': 'hostname', 'type_description': 'A full host/dnsname of an attacker'}, + {'type_name': 'hostname|port', 'type_description': 'Hostname and port number separated by a |'}, + {'type_name': 'http-method', 'type_description': 'HTTP method used by the malware (e.g. POST, GET, …},.'}, + {'type_name': 'iban', 'type_description': 'International Bank Account Number'}, + {'type_name': 'identity-card-number', 'type_description': 'Identity card number'}, + {'type_name': 'impfuzzy', 'type_description': 'A fuzzy hash of import table of Portable Executable format'}, + {'type_name': 'imphash', 'type_description': 'Import hash - a hash created based on the imports in the sample.'}, + {'type_name': 'ip-any', 'type_description': 'A source or destination IP address of the attacker or C&C server'}, + {'type_name': 'ip-dst', 'type_description': 'A destination IP address of the attacker or C&C server'}, + {'type_name': 'ip-dst|port', 'type_description': 'IP destination and port number separated by a |'}, + {'type_name': 'ip-src', 'type_description': 'A source IP address of the attacker'}, + {'type_name': 'ip-src|port', 'type_description': 'IP source and port number separated by a |'}, + {'type_name': 'ja3-fingerprint-md5', 'type_description': 'JA3 is a method for creating SSL/TLS client fingerprints that should be easy to produce on any platform and can be easily shared for threat intelligence.'}, + {'type_name': 'jabber-id', 'type_description': 'Jabber ID'}, + {'type_name': 'jarm-fingerprint', 'type_description': 'JARM is a method for creating SSL/TLS server fingerprints.'}, + {'type_name': 'kusto-query', 'type_description': 'Kusto query - Kusto from Microsoft Azure is a service for storing and running interactive analytics over Big Data.'}, + {'type_name': 'link', 'type_description': 'Link to an external information'}, + {'type_name': 'mac-address', 'type_description': 'Mac address'}, + {'type_name': 'mac-eui-64', 'type_description': 'Mac EUI-64 address'}, + {'type_name': 'malware-sample', 'type_description': 'Attachment containing encrypted malware sample'}, + {'type_name': 'malware-type', 'type_description': 'Malware type'}, + {'type_name': 'md5', 'type_description': 'A checksum in md5 format'}, + {'type_name': 'middle-name', 'type_description': 'Middle name of a natural person'}, + {'type_name': 'mime-type', 'type_description': 'A media type (also MIME type and content type}, is a two-part identifier for file formats and format contents transmitted on the Internet'}, + {'type_name': 'mobile-application-id', 'type_description': 'The application id of a mobile application'}, + {'type_name': 'mutex', 'type_description': 'Mutex, use the format \\BaseNamedObjects'}, + {'type_name': 'named pipe', 'type_description': 'Named pipe, use the format .\\pipe'}, + {'type_name': 'other', 'type_description': 'Other attribute'}, + {'type_name': 'file-path', 'type_description': 'Path of file'}, + {'type_name': 'pattern-in-file', 'type_description': 'Pattern in file that identifies the malware'}, + {'type_name': 'pattern-in-memory', 'type_description': 'Pattern in memory dump that identifies the malware'}, + {'type_name': 'pattern-in-traffic', 'type_description': 'Pattern in network traffic that identifies the malware'}, + {'type_name': 'pdb', 'type_description': 'Microsoft Program database (PDB}, path information'}, + {'type_name': 'pehash', 'type_description': 'PEhash - a hash calculated based of certain pieces of a PE executable file'}, + {'type_name': 'pgp-private-key', 'type_description': 'A PGP private key'}, + {'type_name': 'pgp-public-key', 'type_description': 'A PGP public key'}, + {'type_name': 'phone-number', 'type_description': 'Telephone Number'}, + {'type_name': 'port', 'type_description': 'Port number'}, + {'type_name': 'process-state', 'type_description': 'State of a process'}, + {'type_name': 'prtn', 'type_description': 'Premium-Rate Telephone Number'}, + {'type_name': 'regkey', 'type_description': 'Registry key or value'}, + {'type_name': 'regkey|value', 'type_description': 'Registry value + data separated by |'}, + {'type_name': 'sha1', 'type_description': 'A checksum in sha1 format'}, + {'type_name': 'sha224', 'type_description': 'A checksum in sha-224 format'}, + {'type_name': 'sha256', 'type_description': 'A checksum in sha256 format'}, + {'type_name': 'sha3-224', 'type_description': 'A checksum in sha3-224 format'}, + {'type_name': 'sha3-256', 'type_description': 'A checksum in sha3-256 format'}, + {'type_name': 'sha3-384', 'type_description': 'A checksum in sha3-384 format'}, + {'type_name': 'sha3-512', 'type_description': 'A checksum in sha3-512 format'}, + {'type_name': 'sha384', 'type_description': 'A checksum in sha-384 format'}, + {'type_name': 'sha512', 'type_description': 'A checksum in sha-512 format'}, + {'type_name': 'sha512/224', 'type_description': 'A checksum in the sha-512/224 format'}, + {'type_name': 'sha512/256', 'type_description': 'A checksum in the sha-512/256 format'}, + {'type_name': 'sigma', 'type_description': 'Sigma - Generic Signature Format for SIEM Systems'}, + {'type_name': 'size-in-bytes', 'type_description': 'Size expressed in bytes'}, + {'type_name': 'snort', 'type_description': 'An IDS rule in Snort rule-format'}, + {'type_name': 'ssdeep', 'type_description': 'A checksum in ssdeep format'}, + {'type_name': 'ssh-fingerprint', 'type_description': 'A fingerprint of SSH key material'}, + {'type_name': 'stix2-pattern', 'type_description': 'STIX 2 pattern'}, + {'type_name': 'target-email', 'type_description': 'Attack Targets Email(s},'}, + {'type_name': 'target-external', 'type_description': 'External Target Organizations Affected by this Attack'}, + {'type_name': 'target-location', 'type_description': 'Attack Targets Physical Location(s},'}, + {'type_name': 'target-machine', 'type_description': 'Attack Targets Machine Name(s},'}, + {'type_name': 'target-org', 'type_description': 'Attack Targets Department or Organization(s},'}, + {'type_name': 'target-user', 'type_description': 'Attack Targets Username(s},'}, + {'type_name': 'telfhash', 'type_description': 'telfhash is symbol hash for ELF files, just like imphash is imports hash for PE files.'}, + {'type_name': 'text', 'type_description': 'Name, ID or a reference'}, + {'type_name': 'threat-actor', 'type_description': 'A string identifying the threat actor'}, + {'type_name': 'tlsh', 'type_description': 'A checksum in the Trend Micro Locality Sensitive Hash format'}, + {'type_name': 'travel-details', 'type_description': 'Travel details'}, + {'type_name': 'twitter-id', 'type_description': 'Twitter ID'}, + {'type_name': 'uri', 'type_description': 'Uniform Resource Identifier'}, + {'type_name': 'url', 'type_description': 'url'}, + {'type_name': 'user-agent', 'type_description': 'The user-agent used by the malware in the HTTP request.'}, + {'type_name': 'vhash', 'type_description': 'A VirusTotal checksum'}, + {'type_name': 'vulnerability', 'type_description': 'A reference to the vulnerability used in the exploit'}, + {'type_name': 'weakness', 'type_description': 'A reference to the weakness used in the exploit'}, + {'type_name': 'whois-creation-date', 'type_description': 'The date of domain’s creation, obtained from the WHOIS information.'}, + {'type_name': 'whois-registrant-email', 'type_description': 'The e-mail of a domain’s registrant, obtained from the WHOIS information.'}, + {'type_name': 'whois-registrant-name', 'type_description': 'The name of a domain’s registrant, obtained from the WHOIS information.'}, + {'type_name': 'whois-registrant-org', 'type_description': 'The org of a domain’s registrant, obtained from the WHOIS information.'}, + {'type_name': 'whois-registrant-phone', 'type_description': 'The phone number of a domain’s registrant, obtained from the WHOIS information.'}, + {'type_name': 'whois-registrar', 'type_description': 'The registrar of the domain, obtained from the WHOIS information.'}, + {'type_name': 'windows-scheduled-task', 'type_description': 'A scheduled task in windows'}, + {'type_name': 'windows-service-displayname', 'type_description': 'A windows service’s displayname, not to be confused with the windows-service-name. This is the name that applications will generally display as the service’s name in applications.'}, + {'type_name': 'windows-service-name', 'type_description': 'A windows service name. This is the name used internally by windows. Not to be confused with the windows-service-displayname.'}, + {'type_name': 'x509-fingerprint-md5', 'type_description': 'X509 fingerprint in MD5 format'}, + {'type_name': 'x509-fingerprint-sha1', 'type_description': 'X509 fingerprint in SHA-1 format'}, + {'type_name': 'x509-fingerprint-sha256', 'type_description': 'X509 fingerprint in SHA-256 format'}, + {'type_name': 'xmr', 'type_description': 'Monero Address'}, + {'type_name': 'yara', 'type_description': 'Yara signature'}, + {'type_name': 'zeek', 'type_description': 'An NIDS rule in the Zeek rule-format'} +] _DEFAULT_ORGANISATION_NAME = 'Default Org' @@ -796,471 +957,8 @@ def create_safe_attributes(): def create_safe_ioctypes(): - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="AS", - type_description="Autonomous system", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="aba-rtn", - type_description="ABA routing transit number", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="account", - type_description="Account of any type", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="anonymised", - type_description="Anonymised value - described with the anonymisation object via a relationship", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="attachment", - type_description="Attachment with external information", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="authentihash", - type_description="Authenticode executable signature hash", type_taxonomy="", - type_validation_regex=r"[a-f0-9]{64}", type_validation_expect="64 hexadecimal characters" - ) - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="boolean", - type_description="Boolean value - to be used in objects", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="btc", - type_description="Bitcoin Address", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="campaign-id", - type_description="Associated campaign ID", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="campaign-name", - type_description="Associated campaign name", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="cdhash", - type_description="An Apple Code Directory Hash, identifying a code-signed Mach-O executable file", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="chrome-extension-id", - type_description="Chrome extension id", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="community-id", - type_description="a community ID flow hashing algorithm to map multiple traffic monitors into common flow id", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="cookie", - type_description="HTTP cookie as often stored on the user web client. This can include authentication cookie or session cookie.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="dash", - type_description="Dash Address", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="datetime", - type_description="Datetime in the ISO 8601 format", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="dkim", - type_description="DKIM public key", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="dkim-signature", - type_description="DKIM signature", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="dns-soa-email", - type_description="RFC1035 mandates that DNS zones should have a SOA (Statement Of Authority) record that contains an email address where a PoC for the domain could be contacted. This can sometimes be used for attribution/linkage between different domains even if protected by whois privacy", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="domain", - type_description="A domain name used in the malware", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="domain|ip", - type_description="A domain name and its IP address (as found in DNS lookup) separated by a |", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email", - type_description="An e-mail address", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-attachment", - type_description="File name of the email attachment.", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-body", - type_description="Email body", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-dst", - type_description="The destination email address. Used to describe the recipient when describing an e-mail.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-dst-display-name", - type_description="Email destination display name", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-header", - type_description="Email header", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-message-id", - type_description="The email message ID", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-mime-boundary", - type_description="The email mime boundary separating parts in a multipart email", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-reply-to", - type_description="Email reply to header", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-src", - type_description="The source email address. Used to describe the sender when describing an e-mail.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-src-display-name", - type_description="Email source display name", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-subject", - type_description="The subject of the email", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-thread-index", - type_description="The email thread index header", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="email-x-mailer", - type_description="Email x-mailer header", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="favicon-mmh3", - type_description="favicon-mmh3 is the murmur3 hash of a favicon as used in Shodan.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename", - type_description="Filename", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename-pattern", - type_description="A pattern in the name of a file", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|authentihash", - type_description="A checksum in md5 format", - type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{64}', - type_validation_expect="filename|64 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|impfuzzy", - type_description="Import fuzzy hash - a fuzzy hash created based on the imports in the sample.", - type_taxonomy="", ) - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|imphash", - type_description="Import hash - a hash created based on the imports in the sample.", - type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{32}', - type_validation_expect="filename|32 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|md5", - type_description="A filename and an md5 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{32}', - type_validation_expect="filename|32 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|pehash", - type_description="A filename and a PEhash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{40}', - type_validation_expect="filename|40 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha1", - type_description="A filename and an sha1 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{40}', - type_validation_expect="filename|40 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha224", - type_description="A filename and a sha-224 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{56}', - type_validation_expect="filename|56 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha256", - type_description="A filename and an sha256 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{64}', - type_validation_expect="filename|64 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha3-224", - type_description="A filename and an sha3-224 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{56}', - type_validation_expect="filename|56 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha3-256", - type_description="A filename and an sha3-256 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{64}', - type_validation_expect="filename|64 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha3-384", - type_description="A filename and an sha3-384 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{96}', - type_validation_expect="filename|96 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha3-512", - type_description="A filename and an sha3-512 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{128}', - type_validation_expect="filename|128 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha384", - type_description="A filename and a sha-384 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{96}', - type_validation_expect="filename|96 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha512", - type_description="A filename and a sha-512 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{128}', - type_validation_expect="filename|128 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha512/224", - type_description="A filename and a sha-512/224 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{56}', - type_validation_expect="filename|56 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|sha512/256", - type_description="A filename and a sha-512/256 hash separated by a |", type_taxonomy="", - type_validation_regex=r'.+\|[a-f0-9]{64}', - type_validation_expect="filename|64 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|ssdeep", - type_description="A checksum in ssdeep format", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|tlsh", - type_description="A filename and a Trend Micro Locality Sensitive Hash separated by a |", - type_taxonomy="", - type_validation_regex=r'.+\|t?[a-f0-9]{35,}', - type_validation_expect="filename|at least 35 hexadecimal characters, optionally starting with t1 instead of hexadecimal characters" - ) - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="filename|vhash", - type_description="A filename and a VirusTotal hash separated by a |", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="first-name", - type_description="First name of a natural person", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="float", - type_description="A floating point value.", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="full-name", - type_description="Full name of a natural person", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="gene", - type_description="GENE - Go Evtx sigNature Engine", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="git-commit-id", - type_description="A git commit ID.", type_taxonomy="", - type_validation_regex=r"[a-f0-9]{40}", type_validation_expect="40 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="github-organisation", - type_description="A github organisation", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="github-repository", - type_description="A github repository", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="github-username", - type_description="A github user name", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="hassh-md5", - type_description="hassh is a network fingerprinting standard which can be used to identify specific Client SSH implementations. The fingerprints can be easily stored, searched and shared in the form of an MD5 fingerprint.", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{32}", type_validation_expect="32 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="hasshserver-md5", - type_description="hasshServer is a network fingerprinting standard which can be used to identify specific Server SSH implementations. The fingerprints can be easily stored, searched and shared in the form of an MD5 fingerprint.", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{32}", type_validation_expect="32 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="hex", - type_description="A value in hexadecimal format", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="hostname", - type_description="A full host/dnsname of an attacker", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="hostname|port", - type_description="Hostname and port number separated by a |", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="http-method", - type_description="HTTP method used by the malware (e.g. POST, GET, …).", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="iban", - type_description="International Bank Account Number", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="identity-card-number", - type_description="Identity card number", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="impfuzzy", - type_description="A fuzzy hash of import table of Portable Executable format", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="imphash", - type_description="Import hash - a hash created based on the imports in the sample.", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{32}", type_validation_expect="32 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="ip-any", - type_description="A source or destination IP address of the attacker or C&C server", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="ip-dst", - type_description="A destination IP address of the attacker or C&C server", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="ip-dst|port", - type_description="IP destination and port number separated by a |", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="ip-src", - type_description="A source IP address of the attacker", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="ip-src|port", - type_description="IP source and port number separated by a |", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="ja3-fingerprint-md5", - type_description="JA3 is a method for creating SSL/TLS client fingerprints that should be easy to produce on any platform and can be easily shared for threat intelligence.", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{32}", type_validation_expect="32 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="jabber-id", - type_description="Jabber ID", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="jarm-fingerprint", - type_description="JARM is a method for creating SSL/TLS server fingerprints.", type_taxonomy="", - type_validation_regex=r"[a-f0-9]{62}", type_validation_expect="62 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="kusto-query", - type_description="Kusto query - Kusto from Microsoft Azure is a service for storing and running interactive analytics over Big Data.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="link", - type_description="Link to an external information", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="mac-address", - type_description="Mac address", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="mac-eui-64", - type_description="Mac EUI-64 address", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="malware-sample", - type_description="Attachment containing encrypted malware sample", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="malware-type", - type_description="Malware type", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="md5", - type_description="A checksum in md5 format", type_taxonomy="", - type_validation_regex=r"[a-f0-9]{32}", type_validation_expect="32 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="middle-name", - type_description="Middle name of a natural person", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="mime-type", - type_description="A media type (also MIME type and content type) is a two-part identifier for file formats and format contents transmitted on the Internet", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="mobile-application-id", - type_description="The application id of a mobile application", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="mutex", - type_description="Mutex, use the format \\BaseNamedObjects", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="named pipe", - type_description="Named pipe, use the format .\\pipe", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="other", - type_description="Other attribute", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="file-path", - type_description="Path of file", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="pattern-in-file", - type_description="Pattern in file that identifies the malware", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="pattern-in-memory", - type_description="Pattern in memory dump that identifies the malware", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="pattern-in-traffic", - type_description="Pattern in network traffic that identifies the malware", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="pdb", - type_description="Microsoft Program database (PDB) path information", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="pehash", - type_description="PEhash - a hash calculated based of certain pieces of a PE executable file", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{40}", type_validation_expect="40 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="pgp-private-key", - type_description="A PGP private key", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="pgp-public-key", - type_description="A PGP public key", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="phone-number", - type_description="Telephone Number", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="port", - type_description="Port number", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="process-state", - type_description="State of a process", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="prtn", - type_description="Premium-Rate Telephone Number", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="regkey", - type_description="Registry key or value", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="regkey|value", - type_description="Registry value + data separated by |", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha1", - type_description="A checksum in sha1 format", type_taxonomy="", - type_validation_regex=r"[a-f0-9]{40}", type_validation_expect="40 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha224", - type_description="A checksum in sha-224 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{56}", type_validation_expect="56 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha256", - type_description="A checksum in sha256 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{64}", type_validation_expect="64 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha3-224", - type_description="A checksum in sha3-224 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{56}", type_validation_expect="56 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha3-256", - type_description="A checksum in sha3-256 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{64}", type_validation_expect="64 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha3-384", - type_description="A checksum in sha3-384 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{96}", type_validation_expect="96 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha3-512", - type_description="A checksum in sha3-512 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{128}", type_validation_expect="128 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha384", - type_description="A checksum in sha-384 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{96}", type_validation_expect="96 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha512", - type_description="A checksum in sha-512 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{128}", type_validation_expect="128 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha512/224", - type_description="A checksum in the sha-512/224 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{56}", type_validation_expect="56 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sha512/256", - type_description="A checksum in the sha-512/256 format", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{64}", type_validation_expect="64 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="sigma", - type_description="Sigma - Generic Signature Format for SIEM Systems", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="size-in-bytes", - type_description="Size expressed in bytes", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="snort", - type_description="An IDS rule in Snort rule-format", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="ssdeep", - type_description="A checksum in ssdeep format", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="ssh-fingerprint", - type_description="A fingerprint of SSH key material", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="stix2-pattern", - type_description="STIX 2 pattern", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="target-email", - type_description="Attack Targets Email(s)", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="target-external", - type_description="External Target Organizations Affected by this Attack", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="target-location", - type_description="Attack Targets Physical Location(s)", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="target-machine", - type_description="Attack Targets Machine Name(s)", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="target-org", - type_description="Attack Targets Department or Organization(s)", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="target-user", - type_description="Attack Targets Username(s)", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="telfhash", - type_description="telfhash is symbol hash for ELF files, just like imphash is imports hash for PE files.", - type_taxonomy="", - type_validation_regex=r"[a-f0-9]{70}", type_validation_expect="70 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="text", - type_description="Name, ID or a reference", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="threat-actor", - type_description="A string identifying the threat actor", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="tlsh", - type_description="A checksum in the Trend Micro Locality Sensitive Hash format", - type_taxonomy="", - type_validation_regex=r"^t?[a-f0-9]{35,}", - type_validation_expect="at least 35 hexadecimal characters, optionally starting with t1 instead of hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="travel-details", - type_description="Travel details", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="twitter-id", - type_description="Twitter ID", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="uri", - type_description="Uniform Resource Identifier", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="url", type_description="url", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="user-agent", - type_description="The user-agent used by the malware in the HTTP request.", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="vhash", - type_description="A VirusTotal checksum", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="vulnerability", - type_description="A reference to the vulnerability used in the exploit", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="weakness", - type_description="A reference to the weakness used in the exploit", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="whois-creation-date", - type_description="The date of domain’s creation, obtained from the WHOIS information.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="whois-registrant-email", - type_description="The e-mail of a domain’s registrant, obtained from the WHOIS information.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="whois-registrant-name", - type_description="The name of a domain’s registrant, obtained from the WHOIS information.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="whois-registrant-org", - type_description="The org of a domain’s registrant, obtained from the WHOIS information.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="whois-registrant-phone", - type_description="The phone number of a domain’s registrant, obtained from the WHOIS information.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="whois-registrar", - type_description="The registrar of the domain, obtained from the WHOIS information.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="windows-scheduled-task", - type_description="A scheduled task in windows", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="windows-service-displayname", - type_description="A windows service’s displayname, not to be confused with the windows-service-name. This is the name that applications will generally display as the service’s name in applications.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="windows-service-name", - type_description="A windows service name. This is the name used internally by windows. Not to be confused with the windows-service-displayname.", - type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="x509-fingerprint-md5", - type_description="X509 fingerprint in MD5 format", type_taxonomy="", - type_validation_regex=r"[a-f0-9]{32}", type_validation_expect="32 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="x509-fingerprint-sha1", - type_description="X509 fingerprint in SHA-1 format", type_taxonomy="", - type_validation_regex=r"[a-f0-9]{40}", type_validation_expect="40 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="x509-fingerprint-sha256", - type_description="X509 fingerprint in SHA-256 format", type_taxonomy="", - type_validation_regex=r"[a-f0-9]{64}", type_validation_expect="64 hexadecimal characters") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="xmr", - type_description="Monero Address", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="yara", - type_description="Yara signature", type_taxonomy="") - create_safe_limited(db.session, IocType, ["type_name", "type_description"], type_name="zeek", - type_description="An NIDS rule in the Zeek rule-format", - type_taxonomy="") + for ioc_type in _IOC_TYPES: + create_safe(db.session, IocType, type_name=ioc_type['type_name'], type_description=ioc_type['type_description']) def create_safe_os_types(): From 2ba73b2ff12a20420cbdc4c7b646e4214adf035d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:51:11 +0100 Subject: [PATCH 195/286] Moved EvidenceTypes model in new model file evidences.py --- .../manage/manage_evidence_types_db.py | 2 +- source/app/models/evidences.py | 33 +++++++++++++++++++ source/app/models/models.py | 12 ------- source/app/post_init.py | 2 +- source/app/schema/marshables.py | 2 +- 5 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 source/app/models/evidences.py diff --git a/source/app/datamgmt/manage/manage_evidence_types_db.py b/source/app/datamgmt/manage/manage_evidence_types_db.py index 33c2b11a1..c02a17564 100644 --- a/source/app/datamgmt/manage/manage_evidence_types_db.py +++ b/source/app/datamgmt/manage/manage_evidence_types_db.py @@ -17,7 +17,7 @@ from sqlalchemy import func from typing import List -from app.models.models import EvidenceTypes +from app.models.evidences import EvidenceTypes from app.models.models import CaseReceivedFile diff --git a/source/app/models/evidences.py b/source/app/models/evidences.py new file mode 100644 index 000000000..960db6282 --- /dev/null +++ b/source/app/models/evidences.py @@ -0,0 +1,33 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 sqlalchemy import Column, Integer, Text, DateTime, func, ForeignKey +from sqlalchemy.orm import relationship + +from app import db + + +class EvidenceTypes(db.Model): + __tablename__ = 'evidence_type' + + id = Column(Integer, primary_key=True) + name = Column(Text) + description = Column(Text) + creation_date = Column(DateTime, server_default=func.now(), nullable=True) + created_by_id = Column(ForeignKey('user.id'), nullable=True) + + created_by = relationship('User') diff --git a/source/app/models/models.py b/source/app/models/models.py index 467b19a95..0048f62cf 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -47,18 +47,6 @@ metadata = Base.metadata -class EvidenceTypes(db.Model): - __tablename__ = 'evidence_type' - - id = Column(Integer, primary_key=True) - name = Column(Text) - description = Column(Text) - creation_date = Column(DateTime, server_default=func.now(), nullable=True) - created_by_id = Column(ForeignKey('user.id'), nullable=True) - - created_by = relationship('User') - - class CaseTemplate(db.Model): __tablename__ = 'case_template' diff --git a/source/app/post_init.py b/source/app/post_init.py index e6ce758b1..2385e115a 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -62,7 +62,7 @@ from app.models.cases import CaseState from app.models.customers import Client from app.models.models import ReviewStatus -from app.models.models import EvidenceTypes +from app.models.evidences import EvidenceTypes from app.models.models import EventCategory from app.models.models import IocType from app.models.models import IrisHook diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 71b015571..0cf6b942e 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -57,7 +57,7 @@ from app.models.models import IrisModuleHook from app.models.models import Tags from app.models.models import ReviewStatus -from app.models.models import EvidenceTypes +from app.models.evidences import EvidenceTypes from app.models.models import NoteDirectory from app.models.models import NoteRevisions from app.models.assets import AssetsType, CaseAssets, AnalysisStatus From f793d9e9658caf397b8765de728591f3aa63b33e Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:54:59 +0100 Subject: [PATCH 196/286] Moved CaseReceivedFile in evidences.py --- .../app/blueprints/rest/dim_tasks_routes.py | 2 +- .../rest/v2/evidences_routes/comments.py | 2 +- source/app/business/comments.py | 2 +- source/app/business/evidences.py | 2 +- source/app/datamgmt/case/case_rfiles_db.py | 2 +- source/app/datamgmt/datastore/datastore_db.py | 2 +- .../datamgmt/iris_engine/evidence_storage.py | 2 +- .../datamgmt/manage/manage_attribute_db.py | 2 +- source/app/datamgmt/manage/manage_cases_db.py | 2 +- .../manage/manage_evidence_types_db.py | 2 +- source/app/datamgmt/reporter/report_db.py | 2 +- source/app/models/evidences.py | 38 ++++++++++++++++++- source/app/models/models.py | 25 ------------ source/app/schema/marshables.py | 3 +- 14 files changed, 49 insertions(+), 39 deletions(-) diff --git a/source/app/blueprints/rest/dim_tasks_routes.py b/source/app/blueprints/rest/dim_tasks_routes.py index 4d275a844..91c5e6d8d 100644 --- a/source/app/blueprints/rest/dim_tasks_routes.py +++ b/source/app/blueprints/rest/dim_tasks_routes.py @@ -24,7 +24,7 @@ from app.models.models import IrisModule from app.models.models import IrisModuleHook from app.models.assets import CaseAssets -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases from app.models.cases import CasesEvent diff --git a/source/app/blueprints/rest/v2/evidences_routes/comments.py b/source/app/blueprints/rest/v2/evidences_routes/comments.py index 3db44e012..de3bf12d5 100644 --- a/source/app/blueprints/rest/v2/evidences_routes/comments.py +++ b/source/app/blueprints/rest/v2/evidences_routes/comments.py @@ -34,7 +34,7 @@ from app.business.comments import comments_create_for_evidence from app.business.comments import comments_get_for_evidence from app.business.comments import comments_delete_for_evidence -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile from app.business.evidences import evidences_get from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema diff --git a/source/app/business/comments.py b/source/app/business/comments.py index 7e4048fe9..5cdde471e 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -56,7 +56,7 @@ from app.iris_engine.utils.tracker import track_activity from app.models.comments import Comments from app.models.assets import CaseAssets -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile from app.models.iocs import Ioc from app.models.models import Notes from app.models.models import CaseTasks diff --git a/source/app/business/evidences.py b/source/app/business/evidences.py index 9a07f74fe..faf8196ce 100644 --- a/source/app/business/evidences.py +++ b/source/app/business/evidences.py @@ -23,7 +23,7 @@ from app.models.errors import ObjectNotFoundError from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile from app.models.pagination_parameters import PaginationParameters from app.datamgmt.case.case_rfiles_db import add_rfile from app.datamgmt.case.case_rfiles_db import delete_rfile diff --git a/source/app/datamgmt/case/case_rfiles_db.py b/source/app/datamgmt/case/case_rfiles_db.py index 5e116daa3..0402c4ae9 100644 --- a/source/app/datamgmt/case/case_rfiles_db.py +++ b/source/app/datamgmt/case/case_rfiles_db.py @@ -25,7 +25,7 @@ from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes from app.datamgmt.states import update_evidences_state -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile from app.models.comments import Comments from app.models.comments import EvidencesComments from app.models.authorization import User diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index e78e41551..0a80e6f52 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -28,7 +28,7 @@ from app.datamgmt.db_operations import db_create, db_delete from app.db import db from app.blueprints.iris_user import iris_current_user -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile from app.models.models import DataStoreFile from app.models.models import DataStorePath from app.models.iocs import Ioc diff --git a/source/app/datamgmt/iris_engine/evidence_storage.py b/source/app/datamgmt/iris_engine/evidence_storage.py index 21ce14d14..ca4c323a8 100644 --- a/source/app/datamgmt/iris_engine/evidence_storage.py +++ b/source/app/datamgmt/iris_engine/evidence_storage.py @@ -17,7 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from app.datamgmt.case.case_rfiles_db import add_rfile -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile class EvidenceStorage(object): diff --git a/source/app/datamgmt/manage/manage_attribute_db.py b/source/app/datamgmt/manage/manage_attribute_db.py index 74048eff9..689abfa43 100644 --- a/source/app/datamgmt/manage/manage_attribute_db.py +++ b/source/app/datamgmt/manage/manage_attribute_db.py @@ -23,7 +23,7 @@ from app.db import db from app import app from app.models.assets import CaseAssets -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases from app.models.cases import CasesEvent diff --git a/source/app/datamgmt/manage/manage_cases_db.py b/source/app/datamgmt/manage/manage_cases_db.py index 6b43bf1a4..91840f886 100644 --- a/source/app/datamgmt/manage/manage_cases_db.py +++ b/source/app/datamgmt/manage/manage_cases_db.py @@ -40,7 +40,7 @@ from app.models.models import CaseEventCategory from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases, CaseStatus, CaseClassification from app.models.cases import CasesEvent diff --git a/source/app/datamgmt/manage/manage_evidence_types_db.py b/source/app/datamgmt/manage/manage_evidence_types_db.py index c02a17564..24e028ee7 100644 --- a/source/app/datamgmt/manage/manage_evidence_types_db.py +++ b/source/app/datamgmt/manage/manage_evidence_types_db.py @@ -18,7 +18,7 @@ from typing import List from app.models.evidences import EvidenceTypes -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile def get_evidence_types_list() -> List[dict]: diff --git a/source/app/datamgmt/reporter/report_db.py b/source/app/datamgmt/reporter/report_db.py index 1a94b19da..79dcd4e23 100644 --- a/source/app/datamgmt/reporter/report_db.py +++ b/source/app/datamgmt/reporter/report_db.py @@ -26,7 +26,7 @@ from app.models.models import TaskAssignee from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc -from app.models.models import CaseReceivedFile +from app.models.evidences import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases from app.models.cases import CasesEvent diff --git a/source/app/models/evidences.py b/source/app/models/evidences.py index 960db6282..c056ecf76 100644 --- a/source/app/models/evidences.py +++ b/source/app/models/evidences.py @@ -15,7 +15,18 @@ # 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 sqlalchemy import Column, Integer, Text, DateTime, func, ForeignKey +import uuid + +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import Text +from sqlalchemy import DateTime +from sqlalchemy import func +from sqlalchemy import ForeignKey +from sqlalchemy import BigInteger +from sqlalchemy import UUID +from sqlalchemy import text +from sqlalchemy.dialects.postgresql import JSON from sqlalchemy.orm import relationship from app import db @@ -31,3 +42,28 @@ class EvidenceTypes(db.Model): created_by_id = Column(ForeignKey('user.id'), nullable=True) created_by = relationship('User') + + +class CaseReceivedFile(db.Model): + __tablename__ = 'case_received_file' + + id = Column(BigInteger, primary_key=True) + file_uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, server_default=text("gen_random_uuid()"), nullable=False) + filename = Column(Text) + date_added = Column(DateTime) + acquisition_date = Column(DateTime) + file_hash = Column(Text) + file_description = Column(Text) + file_size = Column(BigInteger) + start_date = Column(DateTime) + end_date = Column(DateTime) + case_id = Column(ForeignKey('cases.case_id')) + user_id = Column(ForeignKey('user.id')) + type_id = Column(ForeignKey('evidence_type.id')) + custom_attributes = Column(JSON) + chain_of_custody = Column(JSON) + modification_history = Column(JSON) + + case = relationship('Cases') + user = relationship('User') + type = relationship('EvidenceTypes') diff --git a/source/app/models/models.py b/source/app/models/models.py index 0048f62cf..66cd380ef 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -383,31 +383,6 @@ class CaseKanban(db.Model): case = relationship('Cases') -class CaseReceivedFile(db.Model): - __tablename__ = 'case_received_file' - - id = Column(BigInteger, primary_key=True) - file_uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, server_default=text("gen_random_uuid()"), nullable=False) - filename = Column(Text) - date_added = Column(DateTime) - acquisition_date = Column(DateTime) - file_hash = Column(Text) - file_description = Column(Text) - file_size = Column(BigInteger) - start_date = Column(DateTime) - end_date = Column(DateTime) - case_id = Column(ForeignKey('cases.case_id')) - user_id = Column(ForeignKey('user.id')) - type_id = Column(ForeignKey('evidence_type.id')) - custom_attributes = Column(JSON) - chain_of_custody = Column(JSON) - modification_history = Column(JSON) - - case = relationship('Cases') - user = relationship('User') - type = relationship('EvidenceTypes') - - class TaskStatus(db.Model): __tablename__ = 'task_status' diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 0cf6b942e..afe85c505 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -57,11 +57,10 @@ from app.models.models import IrisModuleHook from app.models.models import Tags from app.models.models import ReviewStatus -from app.models.evidences import EvidenceTypes +from app.models.evidences import EvidenceTypes, CaseReceivedFile from app.models.models import NoteDirectory from app.models.models import NoteRevisions from app.models.assets import AssetsType, CaseAssets, AnalysisStatus -from app.models.models import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases, CaseStatus, CaseClassification from app.models.cases import CasesEvent From 76c1838337666ce499bc2a3024bb324c45f71a5d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:58:16 +0100 Subject: [PATCH 197/286] Do not import iris_current_user from app.datamgmt.case.case_iocs_db --- pyproject.toml | 2 +- source/app/blueprints/rest/case/case_ioc_routes.py | 2 +- source/app/blueprints/rest/v2/iocs_routes/comments.py | 2 +- source/app/business/comments.py | 4 ++-- source/app/datamgmt/case/case_iocs_db.py | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6e9f9be28..e90027bb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the persistence layer (iris_user)" type = "forbidden" -source_modules = ["app.datamgmt.case.case_assets_db", "app.datamgmt.case.case_events_db"] +source_modules = ["app.datamgmt.case.case_assets_db", "app.datamgmt.case.case_events_db", "app.datamgmt.case.case_iocs_db"] forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true diff --git a/source/app/blueprints/rest/case/case_ioc_routes.py b/source/app/blueprints/rest/case/case_ioc_routes.py index 663f6bccd..debe0afa7 100644 --- a/source/app/blueprints/rest/case/case_ioc_routes.py +++ b/source/app/blueprints/rest/case/case_ioc_routes.py @@ -338,7 +338,7 @@ def case_comment_ioc_edit(cur_id, com_id, caseid): @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_comment_ioc_delete(cur_id, com_id, caseid): - success, msg = delete_ioc_comment(cur_id, com_id) + success, msg = delete_ioc_comment(iris_current_user.id, cur_id, com_id) if not success: return response_error(msg) diff --git a/source/app/blueprints/rest/v2/iocs_routes/comments.py b/source/app/blueprints/rest/v2/iocs_routes/comments.py index e4088fce4..6cc08aaa2 100644 --- a/source/app/blueprints/rest/v2/iocs_routes/comments.py +++ b/source/app/blueprints/rest/v2/iocs_routes/comments.py @@ -102,7 +102,7 @@ def delete(self, ioc_identifier, identifier): if comment.comment_user_id != iris_current_user.id: return ac_api_return_access_denied() - comments_delete_for_ioc(ioc, comment) + comments_delete_for_ioc(iris_current_user, ioc, comment) return response_api_deleted() except ObjectNotFoundError: return response_api_not_found() diff --git a/source/app/business/comments.py b/source/app/business/comments.py index 5cdde471e..8be2b1403 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -316,8 +316,8 @@ def comments_delete_for_evidence(evidence: CaseReceivedFile, comment: Comments): track_activity(f'comment {comment.comment_id} on evidence {evidence.id} deleted', caseid=comment.comment_case_id) -def comments_delete_for_ioc(ioc: Ioc, comment: Comments): - delete_ioc_comment(ioc.ioc_id, comment.comment_id) +def comments_delete_for_ioc(user, ioc: Ioc, comment: Comments): + delete_ioc_comment(user.id, ioc.ioc_id, comment.comment_id) call_modules_hook('on_postload_ioc_comment_delete', comment.comment_id, caseid=comment.comment_case_id) track_activity(f'comment {comment.comment_id} on ioc {ioc.ioc_id} deleted', caseid=comment.comment_case_id) diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index 828736a4a..c0f28bc9a 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -18,9 +18,9 @@ from sqlalchemy import and_ -from app.datamgmt.db_operations import db_create, db_delete +from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_delete from app.db import db -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.models.alerts import Alert @@ -262,10 +262,10 @@ def get_case_ioc_comment(ioc_id, comment_id): .join(Comments.user).first()) -def delete_ioc_comment(ioc_id, comment_id): +def delete_ioc_comment(user_identifier, ioc_id, comment_id): comment = Comments.query.filter( Comments.comment_id == comment_id, - Comments.comment_user_id == iris_current_user.id + Comments.comment_user_id == user_identifier ).first() if not comment: return False, "You are not allowed to delete this comment" From 5648d7bc6333f5ed4e562295f76fcbf606dd28f6 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 09:02:50 +0100 Subject: [PATCH 198/286] Do not import iris_current_user from app.datamgmt.case.case_notes_db --- pyproject.toml | 2 +- source/app/blueprints/rest/case/case_notes_routes.py | 4 ++-- source/app/blueprints/rest/v2/case_routes/notes.py | 6 ++++-- .../app/blueprints/rest/v2/notes_routes/comments.py | 2 +- source/app/business/comments.py | 4 ++-- source/app/business/notes.py | 4 ++-- source/app/datamgmt/case/case_notes_db.py | 12 ++++++------ 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e90027bb9..8f0783606 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the persistence layer (iris_user)" type = "forbidden" -source_modules = ["app.datamgmt.case.case_assets_db", "app.datamgmt.case.case_events_db", "app.datamgmt.case.case_iocs_db"] +source_modules = ["app.datamgmt.case.case_assets_db", "app.datamgmt.case.case_events_db", "app.datamgmt.case.case_iocs_db", "app.datamgmt.case.case_notes_db"] forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true diff --git a/source/app/blueprints/rest/case/case_notes_routes.py b/source/app/blueprints/rest/case/case_notes_routes.py index e20b22852..4e008105e 100644 --- a/source/app/blueprints/rest/case/case_notes_routes.py +++ b/source/app/blueprints/rest/case/case_notes_routes.py @@ -142,7 +142,7 @@ def case_note_save(cur_id, caseid): request_data['note_id'] = note.note_id addnote_schema.load(request_data, partial=True, instance=note) - note = notes_update(note) + note = notes_update(iris_current_user, note) return response_success(f"Note ID {cur_id} saved", data=addnote_schema.dump(note)) @@ -458,7 +458,7 @@ def case_comment_note_edit(cur_id, com_id, caseid): @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_comment_note_delete(cur_id, com_id, caseid): - success, msg = delete_note_comment(cur_id, com_id) + success, msg = delete_note_comment(iris_current_user.id, cur_id, com_id) if not success: return response_error(msg) diff --git a/source/app/blueprints/rest/v2/case_routes/notes.py b/source/app/blueprints/rest/v2/case_routes/notes.py index 6936d4c18..51fb8a538 100644 --- a/source/app/blueprints/rest/v2/case_routes/notes.py +++ b/source/app/blueprints/rest/v2/case_routes/notes.py @@ -20,7 +20,8 @@ from flask import Blueprint from flask import request -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.access_controls import ac_api_return_access_denied from app.blueprints.rest.endpoints import response_api_created from app.blueprints.rest.endpoints import response_api_success @@ -38,6 +39,7 @@ from app.models.errors import BusinessProcessingError from app.models.errors import ObjectNotFoundError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook +from app.blueprints.iris_user import iris_current_user class NotesOperations: @@ -109,7 +111,7 @@ def update(self, case_identifier, identifier): note.note_case_id) request_data['note_id'] = note.note_id self._schema.load(request_data, partial=True, instance=note) - note = notes_update(note) + note = notes_update(iris_current_user, note) schema = CaseNoteSchema() result = schema.dump(note) diff --git a/source/app/blueprints/rest/v2/notes_routes/comments.py b/source/app/blueprints/rest/v2/notes_routes/comments.py index 534b8c0f0..eb537bbef 100644 --- a/source/app/blueprints/rest/v2/notes_routes/comments.py +++ b/source/app/blueprints/rest/v2/notes_routes/comments.py @@ -103,7 +103,7 @@ def delete(self, note_identifier, identifier): if comment.comment_user_id != iris_current_user.id: return ac_api_return_access_denied() - comments_delete_for_note(note, comment) + comments_delete_for_note(iris_current_user, note, comment) return response_api_deleted() except ObjectNotFoundError: return response_api_not_found() diff --git a/source/app/business/comments.py b/source/app/business/comments.py index 8be2b1403..1eec5032d 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -323,8 +323,8 @@ def comments_delete_for_ioc(user, ioc: Ioc, comment: Comments): track_activity(f'comment {comment.comment_id} on ioc {ioc.ioc_id} deleted', caseid=comment.comment_case_id) -def comments_delete_for_note(note: Notes, comment: Comments): - delete_note_comment(note.note_id, comment.comment_id) +def comments_delete_for_note(user, note: Notes, comment: Comments): + delete_note_comment(user.id, note.note_id, comment.comment_id) call_modules_hook('on_postload_note_comment_delete', comment.comment_id, caseid=comment.comment_case_id) track_activity(f'comment {comment.comment_id} on note {note.note_id} deleted', caseid=comment.comment_case_id) diff --git a/source/app/business/notes.py b/source/app/business/notes.py index 51cedca40..3f407c9ec 100644 --- a/source/app/business/notes.py +++ b/source/app/business/notes.py @@ -80,9 +80,9 @@ def notes_get(identifier) -> Notes: return note -def notes_update(note: Notes): +def notes_update(user, note: Notes): try: - if not update_note_revision(note): + if not update_note_revision(user.id, note): logger.debug(f'Note {note.note_id} has not changed, skipping versioning') note.update_date = datetime.utcnow() diff --git a/source/app/datamgmt/case/case_notes_db.py b/source/app/datamgmt/case/case_notes_db.py index a47ea433b..b23f0ce70 100644 --- a/source/app/datamgmt/case/case_notes_db.py +++ b/source/app/datamgmt/case/case_notes_db.py @@ -22,10 +22,10 @@ from datetime import datetime from flask_sqlalchemy.pagination import Pagination -from app.datamgmt.db_operations import db_create, db_delete +from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_delete from app.db import db from app.datamgmt.persistence_error import PersistenceError -from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes from app.datamgmt.states import update_notes_state from app.models.comments import Comments @@ -136,7 +136,7 @@ def update_note(note_content, note_title, update_date, user_id, note_id, caseid) return None -def update_note_revision(note: Notes) -> bool: +def update_note_revision(user_identifier, note: Notes) -> bool: try: latest_version = db.session.query( NoteRevisions @@ -156,7 +156,7 @@ def update_note_revision(note: Notes) -> bool: revision_number=revision_number, note_title=note.note_title, note_content=note.note_content, - note_user=iris_current_user.id, + note_user=user_identifier, revision_timestamp=datetime.utcnow() ) db_create(note_version) @@ -415,10 +415,10 @@ def get_case_note_comment(note_id, comment_id): ).first() -def delete_note_comment(note_id, comment_id): +def delete_note_comment(user_identifier, note_id, comment_id): comment = Comments.query.filter( Comments.comment_id == comment_id, - Comments.comment_user_id == iris_current_user.id + Comments.comment_user_id == user_identifier ).first() if not comment: return False, 'You are not allowed to delete this comment' From 3a9ae9fe81a776ce6b3df9ee757ca2c62dfe012b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:17:18 +0100 Subject: [PATCH 199/286] Do not import iris_current_user from app.datamgmt.case.case_rfiles_db --- pyproject.toml | 2 +- source/app/blueprints/rest/case/case_evidences_routes.py | 2 +- .../app/blueprints/rest/v2/evidences_routes/comments.py | 2 +- source/app/business/comments.py | 4 ++-- source/app/datamgmt/case/case_rfiles_db.py | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8f0783606..cfc819705 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the persistence layer (iris_user)" type = "forbidden" -source_modules = ["app.datamgmt.case.case_assets_db", "app.datamgmt.case.case_events_db", "app.datamgmt.case.case_iocs_db", "app.datamgmt.case.case_notes_db"] +source_modules = ["app.datamgmt.case.case_assets_db", "app.datamgmt.case.case_events_db", "app.datamgmt.case.case_iocs_db", "app.datamgmt.case.case_notes_db", "app.datamgmt.case.case_rfiles_db"] forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true diff --git a/source/app/blueprints/rest/case/case_evidences_routes.py b/source/app/blueprints/rest/case/case_evidences_routes.py index 2f3dadf6d..6cc19f38d 100644 --- a/source/app/blueprints/rest/case/case_evidences_routes.py +++ b/source/app/blueprints/rest/case/case_evidences_routes.py @@ -221,7 +221,7 @@ def case_comment_evidence_edit(cur_id, com_id, caseid): @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_comment_evidence_delete(cur_id, com_id, caseid): - success, msg = delete_evidence_comment(cur_id, com_id) + success, msg = delete_evidence_comment(iris_current_user.id, cur_id, com_id) if not success: return response_error(msg) diff --git a/source/app/blueprints/rest/v2/evidences_routes/comments.py b/source/app/blueprints/rest/v2/evidences_routes/comments.py index de3bf12d5..44aca6559 100644 --- a/source/app/blueprints/rest/v2/evidences_routes/comments.py +++ b/source/app/blueprints/rest/v2/evidences_routes/comments.py @@ -104,7 +104,7 @@ def delete(self, evidence_identifier, identifier): if comment.comment_user_id != iris_current_user.id: return ac_api_return_access_denied() - comments_delete_for_evidence(evidence, comment) + comments_delete_for_evidence(iris_current_user, evidence, comment) return response_api_deleted() except ObjectNotFoundError: return response_api_not_found() diff --git a/source/app/business/comments.py b/source/app/business/comments.py index 1eec5032d..d98497083 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -309,8 +309,8 @@ def comments_delete_for_asset(asset: CaseAssets, comment: Comments): track_activity(f'comment {comment.comment_id} on asset {asset.asset_id} deleted', caseid=comment.comment_case_id) -def comments_delete_for_evidence(evidence: CaseReceivedFile, comment: Comments): - delete_evidence_comment(evidence.id, comment.comment_id) +def comments_delete_for_evidence(user, evidence: CaseReceivedFile, comment: Comments): + delete_evidence_comment(user.id, evidence.id, comment.comment_id) call_modules_hook('on_postload_evidence_comment_delete', comment.comment_id, caseid=comment.comment_case_id) track_activity(f'comment {comment.comment_id} on evidence {evidence.id} deleted', caseid=comment.comment_case_id) diff --git a/source/app/datamgmt/case/case_rfiles_db.py b/source/app/datamgmt/case/case_rfiles_db.py index 0402c4ae9..b5b50ce0c 100644 --- a/source/app/datamgmt/case/case_rfiles_db.py +++ b/source/app/datamgmt/case/case_rfiles_db.py @@ -20,9 +20,9 @@ from sqlalchemy import desc from flask_sqlalchemy.pagination import Pagination -from app.datamgmt.db_operations import db_create, db_delete +from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_delete from app.db import db -from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes from app.datamgmt.states import update_evidences_state from app.models.evidences import CaseReceivedFile @@ -166,10 +166,10 @@ def get_case_evidence_comment(evidence_id, comment_id): ).first() -def delete_evidence_comment(evidence_id, comment_id): +def delete_evidence_comment(user_identifier, evidence_id, comment_id): comment = Comments.query.filter( Comments.comment_id == comment_id, - Comments.comment_user_id == iris_current_user.id + Comments.comment_user_id == user_identifier ).first() if not comment: return False, "You are not allowed to delete this comment" From fbd20948a359e8064139eb1b5748b09467fd55bf Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:19:06 +0100 Subject: [PATCH 200/286] Do not import iris_current_user from app.datamgmt.case --- pyproject.toml | 2 +- source/app/blueprints/rest/case/case_tasks_routes.py | 2 +- source/app/blueprints/rest/v2/tasks_routes/comments.py | 2 +- source/app/business/comments.py | 4 ++-- source/app/datamgmt/case/case_tasks_db.py | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cfc819705..f4d51b933 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the persistence layer (iris_user)" type = "forbidden" -source_modules = ["app.datamgmt.case.case_assets_db", "app.datamgmt.case.case_events_db", "app.datamgmt.case.case_iocs_db", "app.datamgmt.case.case_notes_db", "app.datamgmt.case.case_rfiles_db"] +source_modules = "app.datamgmt.case" forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true diff --git a/source/app/blueprints/rest/case/case_tasks_routes.py b/source/app/blueprints/rest/case/case_tasks_routes.py index cefb7d8f2..350c2e334 100644 --- a/source/app/blueprints/rest/case/case_tasks_routes.py +++ b/source/app/blueprints/rest/case/case_tasks_routes.py @@ -266,7 +266,7 @@ def case_comment_task_edit(cur_id: int, com_id: int, caseid: int): @ac_api_requires() def case_comment_task_delete(cur_id: int, com_id: int, caseid: int): - success, msg = delete_task_comment(task_id=cur_id, comment_id=com_id) + success, msg = delete_task_comment(iris_current_user.id, cur_id, com_id) if not success: return response_error(msg) diff --git a/source/app/blueprints/rest/v2/tasks_routes/comments.py b/source/app/blueprints/rest/v2/tasks_routes/comments.py index 1e2db5909..8ffebac01 100644 --- a/source/app/blueprints/rest/v2/tasks_routes/comments.py +++ b/source/app/blueprints/rest/v2/tasks_routes/comments.py @@ -103,7 +103,7 @@ def delete(self, task_identifier, identifier): if comment.comment_user_id != iris_current_user.id: return ac_api_return_access_denied() - comments_delete_for_task(task, comment) + comments_delete_for_task(iris_current_user, task, comment) return response_api_deleted() except ObjectNotFoundError: return response_api_not_found() diff --git a/source/app/business/comments.py b/source/app/business/comments.py index d98497083..de06bd162 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -330,8 +330,8 @@ def comments_delete_for_note(user, note: Notes, comment: Comments): track_activity(f'comment {comment.comment_id} on note {note.note_id} deleted', caseid=comment.comment_case_id) -def comments_delete_for_task(task: CaseTasks, comment: Comments): - delete_task_comment(task.id, comment.comment_id) +def comments_delete_for_task(user, task: CaseTasks, comment: Comments): + delete_task_comment(user.id, task.id, comment.comment_id) call_modules_hook('on_postload_task_comment_delete', comment.comment_id, caseid=comment.comment_case_id) track_activity(f'comment {comment.comment_id} on task {task.id} deleted', caseid=comment.comment_case_id) diff --git a/source/app/datamgmt/case/case_tasks_db.py b/source/app/datamgmt/case/case_tasks_db.py index 0d65f1c95..7592d3df6 100644 --- a/source/app/datamgmt/case/case_tasks_db.py +++ b/source/app/datamgmt/case/case_tasks_db.py @@ -22,9 +22,9 @@ from sqlalchemy import desc from sqlalchemy import and_ -from app.datamgmt.db_operations import db_create, db_delete +from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_delete from app.db import db -from app.blueprints.iris_user import iris_current_user from app.datamgmt.conversions import convert_sort_direction from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes from app.datamgmt.manage.manage_users_db import get_users_list_restricted_from_case @@ -300,10 +300,10 @@ def delete_task(task_id): ).delete() -def delete_task_comment(task_id, comment_id): +def delete_task_comment(user_identifier, task_id, comment_id): comment = Comments.query.filter( Comments.comment_id == comment_id, - Comments.comment_user_id == iris_current_user.id + Comments.comment_user_id == user_identifier ).first() if not comment: return False, "You are not allowed to delete this comment" From 2c289a8b48c54ca06493fc3977e164f2cb0d5f49 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:20:53 +0100 Subject: [PATCH 201/286] Deprecated POST /global/tasks/update/ --- source/app/blueprints/rest/dashboard_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index b2b135729..a8c30792a 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -236,6 +236,7 @@ def add_gtask(caseid): @dashboard_rest_blueprint.route('/global/tasks/update/', methods=['POST']) +@endpoint_deprecated('PUT', '/api/v2/global-tasks/{identifier}') @ac_api_requires() @ac_requires_case_identifier() def edit_gtask(cur_id, caseid): From 1a92dd75acf656eed837ffc620ec18aed66380a7 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:31:18 +0100 Subject: [PATCH 202/286] Added tests for field task_close_date of global tasks --- tests/tests_rest_global_tasks.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py index 21a40eb6c..83d74dea3 100644 --- a/tests/tests_rest_global_tasks.py +++ b/tests/tests_rest_global_tasks.py @@ -34,6 +34,13 @@ def test_create_global_task_should_return_201(self): response = self._subject.create('/api/v2/global-tasks', body) self.assertEqual(201, response.status_code) + def test_create_global_task_should_set_field_task_close_date(self): + closing_date = '2025-11-19T12:28:01' + body = {'task_title': 'new title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER, + 'task_close_date': closing_date} + response = self._subject.create('/api/v2/global-tasks', body).json() + self.assertEqual(closing_date, response['task_close_date']) + def test_get_global_task_should_return_200(self): body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} response = self._subject.create('/api/v2/global-tasks', body).json() @@ -70,3 +77,13 @@ def test_update_global_task_should_return_200(self): body = {'task_title': 'new title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} response = self._subject.update(f'/api/v2/global-tasks/{identifier}', body) self.assertEqual(200, response.status_code) + + def test_update_global_task_should_update_field_task_close_date(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + closing_date = '2025-11-19T12:28:01' + body = {'task_title': 'new title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER, + 'task_close_date': closing_date} + response = self._subject.update(f'/api/v2/global-tasks/{identifier}', body).json() + self.assertEqual(closing_date, response['task_close_date']) From ac76eb1556a29745247f886ad0b013ce8c554f30 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:29:22 +0100 Subject: [PATCH 203/286] Removed unnecessary else --- source/app/blueprints/responses.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/source/app/blueprints/responses.py b/source/app/blueprints/responses.py index ed8676c4e..3068cf2b5 100644 --- a/source/app/blueprints/responses.py +++ b/source/app/blueprints/responses.py @@ -89,11 +89,10 @@ def default(self, obj): if isinstance(obj, uuid.UUID): return str(obj) - else: - if obj.__class__ == bytes: - try: - return pickle.load(obj) - except Exception: - return str(obj) + if obj.__class__ == bytes: + try: + return pickle.load(obj) + except Exception: + return str(obj) return json.JSONEncoder.default(self, obj) From 952011cbfbcaabfd68e217a18cdbb4135c1d6251 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:31:16 +0100 Subject: [PATCH 204/286] Removed unnecessary else --- source/app/blueprints/access_controls.py | 22 +++++++++---------- .../app/blueprints/graphql/graphql_route.py | 2 +- .../blueprints/pages/login/login_routes.py | 10 ++++----- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index 98a565e1b..485fd6beb 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -404,18 +404,17 @@ def wrap(*args, **kwargs): if not is_user_authenticated(request): return redirect(not_authenticated_redirection_url(request.full_path)) + chan_id = args[0].get('channel') + if chan_id: + case_id = int(chan_id.replace('case-', '').split('-')[0]) else: - chan_id = args[0].get('channel') - if chan_id: - case_id = int(chan_id.replace('case-', '').split('-')[0]) - else: - return _ac_return_access_denied(caseid=0) + return _ac_return_access_denied(caseid=0) - access = ac_fast_check_user_has_case_access(iris_current_user.id, case_id, access_level) - if not access: - return _ac_return_access_denied(caseid=case_id) + access = ac_fast_check_user_has_case_access(iris_current_user.id, case_id, access_level) + if not access: + return _ac_return_access_denied(caseid=case_id) - return f(*args, **kwargs) + return f(*args, **kwargs) return wrap return inner_wrap @@ -491,9 +490,8 @@ def _oidc_proxy_authentication_process(incoming_request: Request): user_email = response_json.get("sub") return _authenticate_with_email(user_email=user_email) - else: - log.info("USER IS NOT AUTHENTICATED") - return False + log.info("USER IS NOT AUTHENTICATED") + return False elif app.config.get("AUTHENTICATION_TOKEN_VERIFY_MODE") == 'signature': # Use the JWKS urls provided by the OIDC discovery to fetch the signing keys diff --git a/source/app/blueprints/graphql/graphql_route.py b/source/app/blueprints/graphql/graphql_route.py index 626120ecd..bfa7b9e9d 100644 --- a/source/app/blueprints/graphql/graphql_route.py +++ b/source/app/blueprints/graphql/graphql_route.py @@ -107,7 +107,7 @@ def wrap(*args, **kwargs): form = FlaskForm() if not form.validate(): return response_error('Invalid CSRF token') - elif request.is_json: + if request.is_json: request.json.pop('csrf_token') if not is_user_authenticated(request): diff --git a/source/app/blueprints/pages/login/login_routes.py b/source/app/blueprints/pages/login/login_routes.py index 45991a093..b5d21eafc 100644 --- a/source/app/blueprints/pages/login/login_routes.py +++ b/source/app/blueprints/pages/login/login_routes.py @@ -304,9 +304,8 @@ def mfa_setup(): session["mfa_verified"] = False track_activity(f'MFA setup successful for user {user.user}', ctx_less=True, display_in_ui=False) return wrap_login_user(user) - else: - track_activity(f'Failed MFA setup for user {user.user}. Invalid token.', ctx_less=True, display_in_ui=False) - flash('Invalid token or password. Please try again.', 'danger') + track_activity(f'Failed MFA setup for user {user.user}. Invalid token.', ctx_less=True, display_in_ui=False) + flash('Invalid token or password. Please try again.', 'danger') temp_otp_secret = pyotp.random_base32() otp_uri = pyotp.TOTP(temp_otp_secret).provisioning_uri(user.email, issuer_name="IRIS") @@ -347,8 +346,7 @@ def mfa_verify(): session['mfa_verified'] = True track_activity(f'MFA verification successful for user {user.user}', ctx_less=True, display_in_ui=False) return wrap_login_user(user) - else: - track_activity(f'Failed MFA verification for user {user.user}. Invalid token.', ctx_less=True, display_in_ui=False) - flash('Invalid token. Please try again.', 'danger') + track_activity(f'Failed MFA verification for user {user.user}. Invalid token.', ctx_less=True, display_in_ui=False) + flash('Invalid token. Please try again.', 'danger') return render_template('mfa_verify.html', form=form) From b34d05c4bd6a499da0a3726f0aff93b80e7bdaa6 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:32:33 +0100 Subject: [PATCH 205/286] Added ruff rule RET505 --- pyproject.toml | 2 +- source/app/business/asynchronous_tasks.py | 3 +- source/app/datamgmt/case/case_comments.py | 9 ++- source/app/datamgmt/case/case_notes_db.py | 6 +- source/app/datamgmt/conversions.py | 3 +- source/app/datamgmt/db_operations.py | 9 ++- source/app/datamgmt/filtering.py | 37 +++++------- source/app/datamgmt/iris_engine/modules_db.py | 3 +- .../datamgmt/manage/manage_attribute_db.py | 30 +++++----- .../datamgmt/manage/manage_srv_settings_db.py | 3 +- source/app/datamgmt/reporter/report_db.py | 3 +- source/app/datamgmt/states.py | 3 +- source/app/flask_dropzone/utils.py | 3 +- .../module_handler/module_handler.py | 3 +- source/app/iris_engine/tasker/tasks.py | 9 ++- source/app/iris_engine/updater/updater.py | 7 +-- source/app/iris_engine/utils/common.py | 60 +++++++++---------- 17 files changed, 85 insertions(+), 108 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f4d51b933..30cadd94d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E20", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] +select = ["ARG003", "ARG005", "B00", "E101", "E20", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] diff --git a/source/app/business/asynchronous_tasks.py b/source/app/business/asynchronous_tasks.py index e868e9282..d8238d5bd 100644 --- a/source/app/business/asynchronous_tasks.py +++ b/source/app/business/asynchronous_tasks.py @@ -33,8 +33,7 @@ def _get_engine_name(task): def _get_success(task_result: IIStatus): if task_result.is_success(): return 'Success' - else: - return 'Failure' + return 'Failure' def _dim_tasks_is_legacy(task): diff --git a/source/app/datamgmt/case/case_comments.py b/source/app/datamgmt/case/case_comments.py index fa95fdeda..1a955f5d8 100644 --- a/source/app/datamgmt/case/case_comments.py +++ b/source/app/datamgmt/case/case_comments.py @@ -25,8 +25,7 @@ def get_case_comment(comment_id, caseid) -> Optional[Comments]: return Comments.query.filter( Comments.comment_id == comment_id ).first() - else: - return Comments.query.filter( - Comments.comment_id == comment_id, - Comments.comment_case_id == caseid - ).first() + return Comments.query.filter( + Comments.comment_id == comment_id, + Comments.comment_case_id == caseid + ).first() diff --git a/source/app/datamgmt/case/case_notes_db.py b/source/app/datamgmt/case/case_notes_db.py index b23f0ce70..7807929e0 100644 --- a/source/app/datamgmt/case/case_notes_db.py +++ b/source/app/datamgmt/case/case_notes_db.py @@ -132,8 +132,7 @@ def update_note(note_content, note_title, update_date, user_id, note_id, caseid) db.session.commit() return note - else: - return None + return None def update_note_revision(user_identifier, note: Notes) -> bool: @@ -345,8 +344,7 @@ def update_note_group(group_title, group_id, caseid): db.session.commit() return ng - else: - return None + return None def find_pattern_in_notes(pattern, caseid): diff --git a/source/app/datamgmt/conversions.py b/source/app/datamgmt/conversions.py index 20e90ae18..e4c4a72fb 100644 --- a/source/app/datamgmt/conversions.py +++ b/source/app/datamgmt/conversions.py @@ -23,5 +23,4 @@ def convert_sort_direction(sort_direction): if sort_direction == 'desc': return desc - else: - return asc + return asc diff --git a/source/app/datamgmt/db_operations.py b/source/app/datamgmt/db_operations.py index 2d17f4f41..1461c90c5 100644 --- a/source/app/datamgmt/db_operations.py +++ b/source/app/datamgmt/db_operations.py @@ -28,11 +28,10 @@ def create_safe(session, model, **kwargs): instance = session.query(model).filter_by(**kwargs).first() if instance: return False - else: - instance = model(**kwargs) - session.add(instance) - session.commit() - return True + instance = model(**kwargs) + session.add(instance) + session.commit() + return True def db_delete(element): diff --git a/source/app/datamgmt/filtering.py b/source/app/datamgmt/filtering.py index 66ff125e6..81fbf4b64 100644 --- a/source/app/datamgmt/filtering.py +++ b/source/app/datamgmt/filtering.py @@ -63,28 +63,24 @@ def build_condition(column, operator, value): fk_col = fk_cols[0] if operator == 'in': return fk_col.in_(value) - else: - return ~fk_col.in_(value) - else: - raise NotImplementedError( - "in_() on a relationship with multiple FK columns not supported. Specify a direct column." - ) - else: - raise ValueError( - "Non-in operators on relationships require specifying a related model column, e.g., owner.id or assets.asset_name." + return ~fk_col.in_(value) + raise NotImplementedError( + "in_() on a relationship with multiple FK columns not supported. Specify a direct column." ) + raise ValueError( + "Non-in operators on relationships require specifying a related model column, e.g., owner.id or assets.asset_name." + ) if operator == 'not': return column != value - elif operator == 'in': + if operator == 'in': return column.in_(value) - elif operator == 'not_in': + if operator == 'not_in': return ~column.in_(value) - elif operator == 'eq': + if operator == 'eq': return column == value - elif operator == 'like': + if operator == 'like': return column.ilike(f"%{value}%") - else: - raise ValueError(f"Unsupported operator: {operator}") + raise ValueError(f"Unsupported operator: {operator}") def combine_conditions(conditions, logical_operator): @@ -95,14 +91,13 @@ def combine_conditions(conditions, logical_operator): if len(conditions) > 1: if logical_operator == 'or': return or_(*conditions) - elif logical_operator == 'not': + if logical_operator == 'not': return not_(and_(*conditions)) - else: # Default to 'and' - return and_(*conditions) - elif conditions: + # Default to 'and' + return and_(*conditions) + if conditions: return conditions[0] - else: - return None + return None def apply_custom_conditions(query, model, custom_conditions, relationship_model_map=None): diff --git a/source/app/datamgmt/iris_engine/modules_db.py b/source/app/datamgmt/iris_engine/modules_db.py index f11d37851..4e3c11297 100644 --- a/source/app/datamgmt/iris_engine/modules_db.py +++ b/source/app/datamgmt/iris_engine/modules_db.py @@ -176,8 +176,7 @@ def get_module_config_from_hname(module_name): if data: return data[0] - else: - return None + return None def get_pipelines_args_from_name(module_name): diff --git a/source/app/datamgmt/manage/manage_attribute_db.py b/source/app/datamgmt/manage/manage_attribute_db.py index 689abfa43..c8a5cc659 100644 --- a/source/app/datamgmt/manage/manage_attribute_db.py +++ b/source/app/datamgmt/manage/manage_attribute_db.py @@ -128,10 +128,9 @@ def add_tab_attribute(obj, tab_name): if tab_name in attribute: return True - else: - attribute[tab_name] = {} - flag_modified(obj, "custom_attributes") - db.session.commit() + attribute[tab_name] = {} + flag_modified(obj, "custom_attributes") + db.session.commit() return True @@ -214,21 +213,20 @@ def merge_custom_attributes(data, obj_id, object_type, overwrite=False): db.session.commit() return obj.custom_attributes - else: - default_attr = get_default_custom_attributes(object_type) - for tab in data: - if default_attr.get(tab) is None: - app.logger.info(f'Missing tab {tab} in {object_type} default attribute') - continue + default_attr = get_default_custom_attributes(object_type) + for tab in data: + if default_attr.get(tab) is None: + app.logger.info(f'Missing tab {tab} in {object_type} default attribute') + continue - for field in data[tab]: - if field not in default_attr[tab]: - app.logger.info(f'Missing field {field} in {object_type} default attribute') + for field in data[tab]: + if field not in default_attr[tab]: + app.logger.info(f'Missing field {field} in {object_type} default attribute') - else: - default_attr[tab][field]['value'] = data[tab][field] + else: + default_attr[tab][field]['value'] = data[tab][field] - return default_attr + return default_attr def validate_attribute(attribute): diff --git a/source/app/datamgmt/manage/manage_srv_settings_db.py b/source/app/datamgmt/manage/manage_srv_settings_db.py index f76626e34..65f790194 100644 --- a/source/app/datamgmt/manage/manage_srv_settings_db.py +++ b/source/app/datamgmt/manage/manage_srv_settings_db.py @@ -16,8 +16,7 @@ def get_server_settings_as_dict(): sc = ServerSettingsSchema() return sc.dump(srv_settings) - else: - return {} + return {} def get_alembic_revision(): diff --git a/source/app/datamgmt/reporter/report_db.py b/source/app/datamgmt/reporter/report_db.py index 79dcd4e23..5ea6897c8 100644 --- a/source/app/datamgmt/reporter/report_db.py +++ b/source/app/datamgmt/reporter/report_db.py @@ -176,8 +176,7 @@ def export_case_evidences_json(case_id): return [row._asdict() for row in evidences] - else: - return [] + return [] def export_case_notes_json(case_id): diff --git a/source/app/datamgmt/states.py b/source/app/datamgmt/states.py index c22a3e030..ecec79fa8 100644 --- a/source/app/datamgmt/states.py +++ b/source/app/datamgmt/states.py @@ -73,8 +73,7 @@ def get_object_state(object_name, caseid): if os: return os._asdict() - else: - return None + return None def delete_case_states(caseid): diff --git a/source/app/flask_dropzone/utils.py b/source/app/flask_dropzone/utils.py index cc3b9dc45..a03139d2c 100644 --- a/source/app/flask_dropzone/utils.py +++ b/source/app/flask_dropzone/utils.py @@ -27,8 +27,7 @@ def get_url(endpoint_or_url): return if endpoint_or_url.startswith(('https://', 'http://', '/')): return endpoint_or_url - else: - return url_for(endpoint_or_url) + return url_for(endpoint_or_url) def random_filename(old_filename): diff --git a/source/app/iris_engine/module_handler/module_handler.py b/source/app/iris_engine/module_handler/module_handler.py index 8ac12cf7c..d47f78051 100644 --- a/source/app/iris_engine/module_handler/module_handler.py +++ b/source/app/iris_engine/module_handler/module_handler.py @@ -385,8 +385,7 @@ def register_hook(module_id: int, iris_hook_name: str, manual_hook_name: str = N return True, [f"Hook {iris_hook_name} registered"] - else: - return True, [f"Hook {iris_hook_name} already registered"] + return True, [f"Hook {iris_hook_name} already registered"] def deregister_from_hook(module_id: int, iris_hook_name: str): diff --git a/source/app/iris_engine/tasker/tasks.py b/source/app/iris_engine/tasker/tasks.py index ddde1506f..181a5f267 100644 --- a/source/app/iris_engine/tasker/tasks.py +++ b/source/app/iris_engine/tasker/tasks.py @@ -82,11 +82,10 @@ def task_case_update(module, pipeline, pipeline_args, caseid): return IStatus.I2UnexpectedResult("Unable to build path") - else: - # The user do not have any context so we cannot update - # Return an error - errors.append('Current user does not have a valid case in context') - return IStatus.I2UnexpectedResult("Invalid context") + # The user do not have any context so we cannot update + # Return an error + errors.append('Current user does not have a valid case in context') + return IStatus.I2UnexpectedResult("Invalid context") def chunks(lst, n): diff --git a/source/app/iris_engine/updater/updater.py b/source/app/iris_engine/updater/updater.py index 59e8d5cd8..36bbbae18 100644 --- a/source/app/iris_engine/updater/updater.py +++ b/source/app/iris_engine/updater/updater.py @@ -149,10 +149,9 @@ def is_updates_available(): db.session.commit() return True, f'# New version {release_version} available\n\n{release.get("body")}', release - else: - srv_settings.has_updates_available = False - db.session.commit() - return False, f'**Current server is up-to-date with {release_version}**', None + srv_settings.has_updates_available = False + db.session.commit() + return False, f'**Current server is up-to-date with {release_version}**', None def init_server_update(release_config): diff --git a/source/app/iris_engine/utils/common.py b/source/app/iris_engine/utils/common.py index 13082fb93..09fb6ce06 100644 --- a/source/app/iris_engine/utils/common.py +++ b/source/app/iris_engine/utils/common.py @@ -63,53 +63,51 @@ def parse_bf_date_format(input_str): date = datetime.fromtimestamp(int(date_value)) return date - elif len(date_value) == 13 and '-' not in date_value and '.' not in date_value and '/' not in date_value: + if len(date_value) == 13 and '-' not in date_value and '.' not in date_value and '/' not in date_value: # Assume microsecond timestamp date = datetime.fromtimestamp(int(date_value) / 1000) return date - else: + # brute force formats + for fmt in ('%Y-%m-%d', '%Y-%m-%d %H:%M', '%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M:%S.%f', + '%Y-%m-%d %H:%M%z', '%Y-%m-%d %H:%M:%S%z', '%Y-%m-%d %H:%M:%S.%f%z', + '%Y-%m-%d %H:%M %Z', '%Y-%m-%d %H:%M:%S %Z', '%Y-%m-%d %H:%M:%S.%f %Z', + '%Y-%m-%d - %H:%M:%S.%f%z', - # brute force formats - for fmt in ('%Y-%m-%d', '%Y-%m-%d %H:%M', '%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M:%S.%f', - '%Y-%m-%d %H:%M%z', '%Y-%m-%d %H:%M:%S%z', '%Y-%m-%d %H:%M:%S.%f%z', - '%Y-%m-%d %H:%M %Z', '%Y-%m-%d %H:%M:%S %Z', '%Y-%m-%d %H:%M:%S.%f %Z', - '%Y-%m-%d - %H:%M:%S.%f%z', + '%b %d %H:%M:%S', '%Y %b %d %H:%M:%S', '%b %d %H:%M:%S %Y', '%b %d %Y %H:%M:%S', + '%y %b %d %H:%M:%S', '%b %d %H:%M:%S %y', '%b %d %y %H:%M:%S', - '%b %d %H:%M:%S', '%Y %b %d %H:%M:%S', '%b %d %H:%M:%S %Y', '%b %d %Y %H:%M:%S', - '%y %b %d %H:%M:%S', '%b %d %H:%M:%S %y', '%b %d %y %H:%M:%S', + '%Y-%m-%d', '%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S', '%Y-%m-%dT%H:%M:%S.%f', + '%Y-%m-%dT%H:%M%z', '%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S.%f%z', + '%Y-%m-%dT%H:%M %Z', '%Y-%m-%dT%H:%M:%S %Z', '%Y-%m-%dT%H:%M:%S.%f %Z', - '%Y-%m-%d', '%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S', '%Y-%m-%dT%H:%M:%S.%f', - '%Y-%m-%dT%H:%M%z', '%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S.%f%z', - '%Y-%m-%dT%H:%M %Z', '%Y-%m-%dT%H:%M:%S %Z', '%Y-%m-%dT%H:%M:%S.%f %Z', + '%Y-%d-%m', '%Y-%d-%m %H:%M', '%Y-%d-%m %H:%M:%S', '%Y-%d-%m %H:%M:%S.%f', + '%Y-%d-%m %H:%M%z', '%Y-%d-%m %H:%M:%S%z', '%Y-%d-%m %H:%M:%S.%f%z', + '%Y-%d-%m %H:%M %Z', '%Y-%d-%m %H:%M:%S %Z', '%Y-%d-%m %H:%M:%S.%f %Z', - '%Y-%d-%m', '%Y-%d-%m %H:%M', '%Y-%d-%m %H:%M:%S', '%Y-%d-%m %H:%M:%S.%f', - '%Y-%d-%m %H:%M%z', '%Y-%d-%m %H:%M:%S%z', '%Y-%d-%m %H:%M:%S.%f%z', - '%Y-%d-%m %H:%M %Z', '%Y-%d-%m %H:%M:%S %Z', '%Y-%d-%m %H:%M:%S.%f %Z', + '%d/%m/%Y %H:%M', '%d/%m/%Y %H:%M:%S', '%d/%m/%Y %H:%M:%S.%f', + '%d.%m.%Y %H:%M', '%d.%m.%Y %H:%M:%S', '%d.%m.%Y %H:%M:%S.%f', + '%d-%m-%Y %H:%M', '%d-%m-%Y %H:%M:%S', '%d-%m-%Y %H:%M:%S.%f', - '%d/%m/%Y %H:%M', '%d/%m/%Y %H:%M:%S', '%d/%m/%Y %H:%M:%S.%f', - '%d.%m.%Y %H:%M', '%d.%m.%Y %H:%M:%S', '%d.%m.%Y %H:%M:%S.%f', - '%d-%m-%Y %H:%M', '%d-%m-%Y %H:%M:%S', '%d-%m-%Y %H:%M:%S.%f', + '%b %d %Y %H:%M', '%b %d %Y %H:%M:%S', '%b %d %Y %H:%M:%S', - '%b %d %Y %H:%M', '%b %d %Y %H:%M:%S', '%b %d %Y %H:%M:%S', + '%a, %d %b %Y %H:%M:%S', '%a, %d %b %Y %H:%M:%S %Z', '%a, %d %b %Y %H:%M:%S.%f', + '%a, %d %b %y %H:%M:%S', '%a, %d %b %y %H:%M:%S %Z', '%a, %d %b %y %H:%M:%S.%f', - '%a, %d %b %Y %H:%M:%S', '%a, %d %b %Y %H:%M:%S %Z', '%a, %d %b %Y %H:%M:%S.%f', - '%a, %d %b %y %H:%M:%S', '%a, %d %b %y %H:%M:%S %Z', '%a, %d %b %y %H:%M:%S.%f', + '%d %b %Y %H:%M', '%d %b %Y %H:%M:%S', '%d %b %Y %H:%M:%S.%f', + '%d %b %y %H:%M', '%d %b %y %H:%M:%S', '%d %b %y %H:%M:%S.%f', - '%d %b %Y %H:%M', '%d %b %Y %H:%M:%S', '%d %b %Y %H:%M:%S.%f', - '%d %b %y %H:%M', '%d %b %y %H:%M:%S', '%d %b %y %H:%M:%S.%f', + '%Y-%m-%d', '%d.%m.%Y', '%d/%m/%Y', "%A, %B %d, %Y", "%A %B %d, %Y", "%A %B %d %Y", + '%d %B %Y'): - '%Y-%m-%d', '%d.%m.%Y', '%d/%m/%Y', "%A, %B %d, %Y", "%A %B %d, %Y", "%A %B %d %Y", - '%d %B %Y'): + try: - try: + date = datetime.strptime(date_value, fmt) + return date - date = datetime.strptime(date_value, fmt) - return date - - except ValueError: - pass + except ValueError: + pass return None From b1ffb6257fcd779f3958d648b510049522b971f3 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:35:22 +0100 Subject: [PATCH 206/286] Added ruff rule RET501 --- pyproject.toml | 2 +- source/app/datamgmt/datastore/datastore_db.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 30cadd94d..9257ee875 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E20", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] +select = ["ARG003", "ARG005", "B00", "E101", "E20", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET501", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 0a80e6f52..2328a64d1 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -251,8 +251,6 @@ def datastore_iter_deletion(dsp, cid): db_delete(dsp) - return None - def datastore_delete_files_of_path(node_id, cid): dsf_list = DataStoreFile.query.filter( From 45a738f33492a1acd50fe41f9d3cf32fffa5a9a1 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:37:34 +0100 Subject: [PATCH 207/286] Removed spurious blank line --- source/app/iris_engine/updater/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/app/iris_engine/updater/__init__.py b/source/app/iris_engine/updater/__init__.py index 8b1378917..e69de29bb 100644 --- a/source/app/iris_engine/updater/__init__.py +++ b/source/app/iris_engine/updater/__init__.py @@ -1 +0,0 @@ - From ab79f5fb67705e6bae30d96687398e7d08442404 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:42:29 +0100 Subject: [PATCH 208/286] Fixing some ruff RET502 warnings --- source/app/configuration.py | 2 +- source/app/flask_dropzone/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/configuration.py b/source/app/configuration.py index 1330e2a5a..69e6f2ad8 100644 --- a/source/app/configuration.py +++ b/source/app/configuration.py @@ -82,7 +82,7 @@ def load(self, section, option, fallback=None): def _load_azure_key_vault(self, section, option): if not (hasattr(self, 'key_vault_name') and self.key_vault_name): - return + return None key = f"{section}-{option}".replace('_', '-') diff --git a/source/app/flask_dropzone/utils.py b/source/app/flask_dropzone/utils.py index a03139d2c..0b008911f 100644 --- a/source/app/flask_dropzone/utils.py +++ b/source/app/flask_dropzone/utils.py @@ -24,7 +24,7 @@ def get_url(endpoint_or_url): if endpoint_or_url == '': - return + return None if endpoint_or_url.startswith(('https://', 'http://', '/')): return endpoint_or_url return url_for(endpoint_or_url) From 65d0db972eee12439826a01fb53588dd62fff420 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 08:32:29 +0100 Subject: [PATCH 209/286] Started implementation of GET /api/v2/global-tasks --- source/app/blueprints/rest/v2/global_tasks.py | 14 ++++++++++++++ .../blueprints/rest/v2/manage_routes/customers.py | 2 +- source/app/business/global_tasks.py | 8 ++++++++ source/app/datamgmt/client/client_db.py | 2 +- source/app/datamgmt/global_tasks.py | 9 +++++++++ tests/tests_rest_global_tasks.py | 4 ++++ 6 files changed, 37 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/global_tasks.py b/source/app/blueprints/rest/v2/global_tasks.py index 89c59e066..a2802f61a 100644 --- a/source/app/blueprints/rest/v2/global_tasks.py +++ b/source/app/blueprints/rest/v2/global_tasks.py @@ -27,8 +27,11 @@ from app.blueprints.rest.endpoints import response_api_error 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_paginated from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import response_api_deleted +from app.blueprints.rest.parsing import parse_pagination_parameters +from app.business.global_tasks import global_tasks_filter from app.business.global_tasks import global_tasks_create from app.business.global_tasks import global_tasks_get from app.business.global_tasks import global_tasks_update @@ -41,6 +44,11 @@ class GlobalTasksOperations: def __init__(self): self._schema = GlobalTasksSchema() + def search(self): + pagination_parameters = parse_pagination_parameters(request) + global_tasks = global_tasks_filter(pagination_parameters) + return response_api_paginated(self._schema, global_tasks) + def create(self): request_data = call_deprecated_on_preload_modules_hook('global_task_create', request.get_json()) try: @@ -90,6 +98,12 @@ def delete(identifier): global_tasks_operations = GlobalTasksOperations() +@global_tasks_blueprint.get('') +@ac_api_requires() +def search_global_task(): + return global_tasks_operations.search() + + @global_tasks_blueprint.post('') @ac_api_requires() def create_global_task(): diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index e8b6cadbc..d969a5b40 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -104,7 +104,7 @@ def delete(identifier): @customers_blueprint.get('') @ac_api_requires(Permissions.customers_read) -def search_evidences(): +def search_customers(): return customers_operations.search() diff --git a/source/app/business/global_tasks.py b/source/app/business/global_tasks.py index 927a05b2b..15a1eb4d1 100644 --- a/source/app/business/global_tasks.py +++ b/source/app/business/global_tasks.py @@ -16,15 +16,23 @@ # 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_sqlalchemy.pagination import Pagination + from app.models.errors import ObjectNotFoundError from app.models.models import GlobalTasks from datetime import datetime from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.datamgmt.db_operations import db_create +from app.datamgmt.global_tasks import filter_global_tasks from app.datamgmt.global_tasks import get_global_task_by_identifier from app.datamgmt.global_tasks import update_global_task from app.datamgmt.global_tasks import delete_global_task +from app.models.pagination_parameters import PaginationParameters + + +def global_tasks_filter(pagination_parameters: PaginationParameters) -> Pagination: + return filter_global_tasks(pagination_parameters) def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index 8851eb07d..ab8202f50 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -76,7 +76,7 @@ def get_paginated_customers(pagination_parameters: PaginationParameters, current query = query.filter( Client.client_id == UserClient.client_id, UserClient.user_id == current_user_identifier - ) + ) return paginate(Client, pagination_parameters, query) diff --git a/source/app/datamgmt/global_tasks.py b/source/app/datamgmt/global_tasks.py index 729f3736a..939339c9a 100644 --- a/source/app/datamgmt/global_tasks.py +++ b/source/app/datamgmt/global_tasks.py @@ -17,11 +17,14 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import desc +from flask_sqlalchemy.pagination import Pagination from app.db import db from app.models.authorization import User from app.models.models import GlobalTasks from app.models.models import TaskStatus +from app.models.pagination_parameters import PaginationParameters +from app.datamgmt.filtering import paginate def delete_global_task(task: GlobalTasks): @@ -33,6 +36,12 @@ def update_global_task(): db.session.commit() +def filter_global_tasks(pagination_parameters: PaginationParameters) -> Pagination: + query = GlobalTasks.query + + return paginate(GlobalTasks, pagination_parameters, query) + + def list_global_tasks(): ct = GlobalTasks.query.with_entities( GlobalTasks.id.label("task_id"), diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py index 83d74dea3..dee6567a3 100644 --- a/tests/tests_rest_global_tasks.py +++ b/tests/tests_rest_global_tasks.py @@ -87,3 +87,7 @@ def test_update_global_task_should_update_field_task_close_date(self): 'task_close_date': closing_date} response = self._subject.update(f'/api/v2/global-tasks/{identifier}', body).json() self.assertEqual(closing_date, response['task_close_date']) + + def test_search_global_tasks_should_return_200(self): + response = self._subject.get('/api/v2/global-tasks') + self.assertEqual(200, response.status_code) From 58900f8fa92646cfa7a1a36e4680b23dad7b67ca Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:11:19 +0100 Subject: [PATCH 210/286] Added test for field total of GET /api/v2/global-tasks --- tests/tests_rest_global_tasks.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py index dee6567a3..e0c076f93 100644 --- a/tests/tests_rest_global_tasks.py +++ b/tests/tests_rest_global_tasks.py @@ -91,3 +91,9 @@ def test_update_global_task_should_update_field_task_close_date(self): def test_search_global_tasks_should_return_200(self): response = self._subject.get('/api/v2/global-tasks') self.assertEqual(200, response.status_code) + + def test_search_global_tasks_should_return_total(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + response = self._subject.get('/api/v2/global-tasks').json() + self.assertEqual(1, response['total']) From e44bd0a0bf6859c6732b8d9ba06abc666b3a8073 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:31:45 +0100 Subject: [PATCH 211/286] Deprecated GET /global/tasks/list --- source/app/blueprints/rest/dashboard_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index a8c30792a..1eafa68ad 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -135,6 +135,7 @@ def get_cases_charts(): @dashboard_rest_blueprint.route('/global/tasks/list', methods=['GET']) +@endpoint_deprecated('GET', '/api/v2/global-tasks') @ac_api_requires() def get_gtasks(): tasks_list = list_global_tasks() From 106365d7cebd5192e5ffda7b3fc9bea5a648f744 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:42:52 +0100 Subject: [PATCH 212/286] Removed one use of iris_current_user out of the persistence layer --- source/app/blueprints/rest/alerts_routes.py | 2 +- source/app/datamgmt/alerts/alerts_db.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 7d7276828..f16042b0d 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -959,7 +959,7 @@ def alert_comment_delete(alert_id, com_id): if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) - success, msg = delete_alert_comment(comment_id=com_id, alert_id=alert_id) + success, msg = delete_alert_comment(iris_current_user.id, comment_id=com_id, alert_id=alert_id) if not success: return response_error(msg) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 1e91ce048..fe2ac2ed8 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1267,7 +1267,7 @@ def get_alert_comment(alert_id: int, comment_id: int) -> Optional[Comments]: ).first() -def delete_alert_comment(comment_id: int, alert_id: int) -> Tuple[bool, str]: +def delete_alert_comment(user_identifier, comment_id: int, alert_id: int) -> Tuple[bool, str]: """ Delete a comment of an alert @@ -1276,7 +1276,7 @@ def delete_alert_comment(comment_id: int, alert_id: int) -> Tuple[bool, str]: """ comment = Comments.query.filter( Comments.comment_id == comment_id, - Comments.comment_user_id == iris_current_user.id, + Comments.comment_user_id == user_identifier, Comments.comment_alert_id == alert_id ).first() if not comment: From 4f4872580e55b64416cb41fdf0af034d31ccbfeb Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:47:21 +0100 Subject: [PATCH 213/286] Extracted method --- source/app/datamgmt/alerts/alerts_db.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index fe2ac2ed8..968cd9626 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -994,6 +994,13 @@ def get_related_alerts(customer_id, assets, iocs, details=False): return similarities +# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! +def _get_font(in_dark_mode) -> str: + if in_dark_mode: + return '12px verdana white' + return '' + + def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_alerts, open_cases, closed_cases, days_back=30, number_of_results=200): """ @@ -1013,6 +1020,7 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al returns: dict: The details of the related alerts with matched assets and/or IOCs """ + in_dark_mode = iris_current_user.in_dark_mode if not assets and not iocs: return { 'nodes': [], @@ -1096,9 +1104,9 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al 'face': 'FontAwesome', 'code': '\uf0f3', 'color': alert_color, - 'weight': "bold" + 'weight': 'bold' }, - 'font': "12px verdana white" if iris_current_user.in_dark_mode else '' + 'font': _get_font(in_dark_mode) }) for asset_info in alert_info['assets']: @@ -1111,7 +1119,7 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al 'group': 'asset', 'shape': 'image', 'image': '/static/assets/img/graph/' + asset_info['icon'], - 'font': "12px verdana white" if iris_current_user.in_dark_mode else '' + 'font': _get_font(in_dark_mode) }) added_assets.add(asset_id) @@ -1130,10 +1138,10 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al 'icon': { 'face': 'FontAwesome', 'code': '\ue4a8', - 'color': 'white' if iris_current_user.in_dark_mode else '', + 'color': 'white' if in_dark_mode else '', 'weight': "bold" }, - 'font': "12px verdana white" if iris_current_user.in_dark_mode else '' + 'font': _get_font(in_dark_mode) }) added_iocs.add(ioc_value) @@ -1202,6 +1210,7 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al for case_id in cases_data: if case_id not in added_cases: + is_closed = cases_data[case_id].get('close_date') nodes.append({ 'id': f'case_{case_id}', 'label': f'[Closed] Case #{case_id}' if cases_data[case_id].get('close_date') else f'Case #{case_id}', @@ -1211,9 +1220,9 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al 'icon': { 'face': 'FontAwesome', 'code': '\uf0b1', - 'color': '#c95029' if cases_data[case_id].get('close_date') else '#4cba4f' + 'color': '#c95029' if is_closed else '#4cba4f' }, - 'font': "12px verdana white" if iris_current_user.in_dark_mode else '' + 'font': _get_font(in_dark_mode) }) added_cases.add(case_id) From 9f5894ba2ff3b3de3dbd073c0a176042c43cac90 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:50:13 +0100 Subject: [PATCH 214/286] Extracted method --- source/app/datamgmt/alerts/alerts_db.py | 49 ++++++++++++++++--------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 968cd9626..55b153ad9 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1001,6 +1001,35 @@ def _get_font(in_dark_mode) -> str: return '' +# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! +def _get_icon_case(is_closed) -> dict[str, str]: + return { + 'face': 'FontAwesome', + 'code': '\uf0b1', + 'color': '#c95029' if is_closed else '#4cba4f' + } + + +# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! +def _get_icon_ioc(in_dark_mode) -> dict[str, str]: + return { + 'face': 'FontAwesome', + 'code': '\ue4a8', + 'color': 'white' if in_dark_mode else '', + 'weight': "bold" + } + + +# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! +def _get_icon_alert(alert_color) -> dict[str, str]: + return { + 'face': 'FontAwesome', + 'code': '\uf0f3', + 'color': alert_color, + 'weight': 'bold' + } + + def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_alerts, open_cases, closed_cases, days_back=30, number_of_results=200): """ @@ -1100,12 +1129,7 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al 'title': f'{alert_info["alert"].alert_description}', 'group': 'alert', 'shape': 'icon', - 'icon': { - 'face': 'FontAwesome', - 'code': '\uf0f3', - 'color': alert_color, - 'weight': 'bold' - }, + 'icon': _get_icon_alert(alert_color), 'font': _get_font(in_dark_mode) }) @@ -1135,12 +1159,7 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al 'label': ioc_value, 'group': 'ioc', 'shape': 'icon', - 'icon': { - 'face': 'FontAwesome', - 'code': '\ue4a8', - 'color': 'white' if in_dark_mode else '', - 'weight': "bold" - }, + 'icon': _get_icon_ioc(in_dark_mode), 'font': _get_font(in_dark_mode) }) added_iocs.add(ioc_value) @@ -1217,11 +1236,7 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al 'title': cases_data[case_id].get("description"), 'group': 'case', 'shape': 'icon', - 'icon': { - 'face': 'FontAwesome', - 'code': '\uf0b1', - 'color': '#c95029' if is_closed else '#4cba4f' - }, + 'icon': _get_icon_case(is_closed), 'font': _get_font(in_dark_mode) }) added_cases.add(case_id) From 6d720ffdf7f8714c929afdd1ef1da58c6f24d126 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:52:47 +0100 Subject: [PATCH 215/286] Moved up an occurence of iris_current_user --- source/app/blueprints/rest/alerts_routes.py | 3 ++- source/app/blueprints/rest/v2/alerts.py | 4 ++-- source/app/business/alerts.py | 5 +++-- source/app/datamgmt/alerts/alerts_db.py | 3 +-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index f16042b0d..2ff0104ac 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -297,7 +297,8 @@ def alerts_similarities_route(alert_id) -> Response: days_back = 180 # Get similar alerts - similar_alerts = get_related_alerts_details(alert.alert_customer_id, alert.assets, alert.iocs, + similar_alerts = get_related_alerts_details(iris_current_user.in_dark_mode, + alert.alert_customer_id, alert.assets, alert.iocs, open_alerts=open_alerts, open_cases=open_cases, closed_cases=closed_cases, closed_alerts=closed_alerts, days_back=days_back, number_of_results=number_of_results) diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index 3c8223cc6..2ecc3c8c7 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -189,8 +189,8 @@ def get_related_alerts(self, identifier): if days_back < 0: days_back = 180 - similar_alerts = related_alerts_get(alert, open_alerts, closed_alerts, open_cases, closed_cases, - days_back, number_of_results) + similar_alerts = related_alerts_get(iris_current_user, alert, open_alerts, closed_alerts, open_cases, + closed_cases, days_back, number_of_results) return response_api_success(similar_alerts) except ObjectNotFoundError: diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 7acc44454..14704d253 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -112,9 +112,10 @@ def alerts_get(user, permissions, identifier) -> Alert: return alert -def related_alerts_get(alert, open_alerts, closed_alerts, open_cases, closed_cases, +def related_alerts_get(user, alert, open_alerts, closed_alerts, open_cases, closed_cases, days_back, number_of_results): - return get_related_alerts_details(alert.alert_customer_id, alert.assets, alert.iocs, + in_dark_mode = user.in_dark_mode + return get_related_alerts_details(in_dark_mode, alert.alert_customer_id, alert.assets, alert.iocs, open_alerts, closed_alerts, open_cases, closed_cases, days_back, number_of_results) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 55b153ad9..ab05697f7 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1030,7 +1030,7 @@ def _get_icon_alert(alert_color) -> dict[str, str]: } -def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_alerts, open_cases, closed_cases, +def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_alerts, closed_alerts, open_cases, closed_cases, days_back=30, number_of_results=200): """ Get the details of the related alerts @@ -1049,7 +1049,6 @@ def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_al returns: dict: The details of the related alerts with matched assets and/or IOCs """ - in_dark_mode = iris_current_user.in_dark_mode if not assets and not iocs: return { 'nodes': [], From f570e27dcceb0e3209a8c192fb0676be23e8704e Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:02:52 +0100 Subject: [PATCH 216/286] Extracted methods --- source/app/datamgmt/alerts/alerts_db.py | 28 ++++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index ab05697f7..d988e08ff 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1062,16 +1062,10 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale alert_status_filter = [] if open_alerts: - open_alert_status_ids = AlertStatus.query.with_entities( - AlertStatus.status_id - ).filter(AlertStatus.status_name.in_(['New', 'Assigned', 'In progress', 'Pending', 'Unspecified'])).all() - alert_status_filter += [status_id[0] for status_id in open_alert_status_ids] + alert_status_filter += _get_open_alerts_status_identifiers() if closed_alerts: - closed_alert_status_ids = AlertStatus.query.with_entities( - AlertStatus.status_id - ).filter(AlertStatus.status_name.in_(['Closed', 'Merged', 'Escalated'])).all() - alert_status_filter += [status_id[0] for status_id in closed_alert_status_ids] + alert_status_filter += _get_closed_alerts_status_identifiers() conditions = and_( SimilarAlertsCache.customer_id == customer_id, @@ -1260,6 +1254,24 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale } +def _get_closed_alerts_status_identifiers(): + return _get_alerts_status_identifiers(['Closed', 'Merged', 'Escalated']) + + +def _get_open_alerts_status_identifiers(): + return _get_alerts_status_identifiers(['New', 'Assigned', 'In progress', 'Pending', 'Unspecified']) + + +def _get_alerts_status_identifiers(status_names): + open_alert_status_ids = AlertStatus.query.with_entities( + AlertStatus.status_id + ).filter(AlertStatus.status_name.in_(status_names)).all() + result = [] + for status_id in open_alert_status_ids: + result.append(status_id) + return result + + def get_alert_comments(alert_id: int) -> List[Comments]: """ Get the comments of an alert From 14b5814593375fde90640f5b5a9e737178d67ddb Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:05:15 +0100 Subject: [PATCH 217/286] No need for optional parameter --- source/app/blueprints/rest/alerts_routes.py | 6 +++--- source/app/datamgmt/alerts/alerts_db.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 2ff0104ac..acbf59514 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -299,9 +299,9 @@ def alerts_similarities_route(alert_id) -> Response: # Get similar alerts similar_alerts = get_related_alerts_details(iris_current_user.in_dark_mode, alert.alert_customer_id, alert.assets, alert.iocs, - open_alerts=open_alerts, open_cases=open_cases, - closed_cases=closed_cases, closed_alerts=closed_alerts, - days_back=days_back, number_of_results=number_of_results) + open_alerts, closed_alerts, + open_cases, closed_cases, + days_back, number_of_results) return response_success(data=similar_alerts) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index d988e08ff..40af2e3f9 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1031,7 +1031,7 @@ def _get_icon_alert(alert_color) -> dict[str, str]: def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_alerts, closed_alerts, open_cases, closed_cases, - days_back=30, number_of_results=200): + days_back, number_of_results): """ Get the details of the related alerts From ee34d5d972ad940cbf9bd0165b54a39ea949c974 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:06:02 +0100 Subject: [PATCH 218/286] Renamed method --- source/app/blueprints/rest/v2/alerts.py | 4 ++-- source/app/business/alerts.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index 2ecc3c8c7..e77f1c45b 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -43,7 +43,7 @@ from app.business.alerts import alerts_get from app.business.alerts import alerts_update from app.business.alerts import alerts_delete -from app.business.alerts import related_alerts_get +from app.business.alerts import alerts_get_related from app.models.errors import BusinessProcessingError from app.models.errors import ObjectNotFoundError @@ -189,7 +189,7 @@ def get_related_alerts(self, identifier): if days_back < 0: days_back = 180 - similar_alerts = related_alerts_get(iris_current_user, alert, open_alerts, closed_alerts, open_cases, + similar_alerts = alerts_get_related(iris_current_user, alert, open_alerts, closed_alerts, open_cases, closed_cases, days_back, number_of_results) return response_api_success(similar_alerts) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 14704d253..fbe96d5d8 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -112,8 +112,8 @@ def alerts_get(user, permissions, identifier) -> Alert: return alert -def related_alerts_get(user, alert, open_alerts, closed_alerts, open_cases, closed_cases, - days_back, number_of_results): +def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, closed_cases, + days_back, number_of_results): in_dark_mode = user.in_dark_mode return get_related_alerts_details(in_dark_mode, alert.alert_customer_id, alert.assets, alert.iocs, open_alerts, closed_alerts, open_cases, closed_cases, From 33d04ddcc7ee489e389e53d1a2da7045a68b7289 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:41:32 +0100 Subject: [PATCH 219/286] Use method from business --- source/app/blueprints/rest/alerts_routes.py | 9 +++------ source/app/business/alerts.py | 3 +-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index acbf59514..7fb4699ea 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -36,7 +36,6 @@ from app.datamgmt.alerts.alerts_db import merge_alert_in_case from app.datamgmt.alerts.alerts_db import unmerge_alert_from_case from app.datamgmt.alerts.alerts_db import get_related_alerts -from app.datamgmt.alerts.alerts_db import get_related_alerts_details from app.datamgmt.alerts.alerts_db import get_alert_comments from app.datamgmt.alerts.alerts_db import delete_alert_comment from app.datamgmt.alerts.alerts_db import get_alert_comment @@ -63,6 +62,7 @@ from app.blueprints.responses import response_success from app.models.errors import BusinessProcessingError from app.business.alerts import alerts_create +from app.business.alerts import alerts_get_related alerts_rest_blueprint = Blueprint('alerts_rest', __name__) @@ -297,11 +297,8 @@ def alerts_similarities_route(alert_id) -> Response: days_back = 180 # Get similar alerts - similar_alerts = get_related_alerts_details(iris_current_user.in_dark_mode, - alert.alert_customer_id, alert.assets, alert.iocs, - open_alerts, closed_alerts, - open_cases, closed_cases, - days_back, number_of_results) + similar_alerts = alerts_get_related(iris_current_user, alert, open_alerts, closed_alerts, open_cases, closed_cases, + days_back, number_of_results) return response_success(data=similar_alerts) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index fbe96d5d8..21a1c2871 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -112,8 +112,7 @@ def alerts_get(user, permissions, identifier) -> Alert: return alert -def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, closed_cases, - days_back, number_of_results): +def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, closed_cases, days_back, number_of_results): in_dark_mode = user.in_dark_mode return get_related_alerts_details(in_dark_mode, alert.alert_customer_id, alert.assets, alert.iocs, open_alerts, closed_alerts, open_cases, closed_cases, From 7e99ab09ab35cbd71db59c6fdd07024ea22bd145 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:50:12 +0100 Subject: [PATCH 220/286] Moved up corner case in the business layer (does not require database access) --- source/app/business/alerts.py | 10 +++++++++- source/app/datamgmt/alerts/alerts_db.py | 23 +++++++---------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 21a1c2871..7bb671c10 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -113,8 +113,16 @@ def alerts_get(user, permissions, identifier) -> Alert: def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, closed_cases, days_back, number_of_results): + assets = alert.assets + iocs = alert.iocs + if not assets and not iocs: + return { + 'nodes': [], + 'edges': [] + } + in_dark_mode = user.in_dark_mode - return get_related_alerts_details(in_dark_mode, alert.alert_customer_id, alert.assets, alert.iocs, + return get_related_alerts_details(in_dark_mode, alert.alert_customer_id, assets, iocs, open_alerts, closed_alerts, open_cases, closed_cases, days_back, number_of_results) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 40af2e3f9..eb79b4b57 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1049,12 +1049,6 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale returns: dict: The details of the related alerts with matched assets and/or IOCs """ - if not assets and not iocs: - return { - 'nodes': [], - 'edges': [] - } - asset_names = [(asset.asset_name, asset.asset_type_id) for asset in assets] ioc_values = [(ioc.ioc_value, ioc.ioc_type_id) for ioc in iocs] @@ -1080,16 +1074,13 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale if alert_status_filter: conditions = and_(conditions, Alert.alert_status_id.in_(alert_status_filter)) - related_alerts = ( - db.session.query(Alert, SimilarAlertsCache.asset_name, SimilarAlertsCache.ioc_value, - asset_type_alias.asset_icon_not_compromised) - .join(SimilarAlertsCache, Alert.alert_id == SimilarAlertsCache.alert_id) - .outerjoin(Alert.resolution_status) - .outerjoin(asset_type_alias, SimilarAlertsCache.asset_type_id == asset_type_alias.asset_id) - .filter(conditions) - .limit(number_of_results) - .all() - ) + related_alerts = db.session.query( + Alert, SimilarAlertsCache.asset_name, SimilarAlertsCache.ioc_value, asset_type_alias.asset_icon_not_compromised + ).join( + SimilarAlertsCache, Alert.alert_id == SimilarAlertsCache.alert_id + ).outerjoin(Alert.resolution_status).outerjoin( + asset_type_alias, SimilarAlertsCache.asset_type_id == asset_type_alias.asset_id + ).filter(conditions).limit(number_of_results).all() alerts_dict = {} From b03ecd3eb2dbdb2b2e1ecd2f51860793b0312f03 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:17:11 +0100 Subject: [PATCH 221/286] Extracted method --- source/app/datamgmt/alerts/alerts_db.py | 98 +++++++++++++++---------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index eb79b4b57..fbf95ace2 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1095,40 +1095,25 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale if any(value == ioc_value for value, _ in ioc_values): alerts_dict[alert.alert_id]['iocs'].append(ioc_value) + return _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, customer_id, in_dark_mode) + + +def _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, customer_id, in_dark_mode): nodes = [] edges = [] added_assets = set() added_iocs = set() - added_cases = set() for alert_id, alert_info in alerts_dict.items(): - alert_color = '#c95029' if alert_info['alert'].status.status_name in ['Closed', 'Merged', 'Escalated'] else '' - - alert_resolution_title = f'[{alert_info["alert"].resolution_status.resolution_status_name}]\n' if alert_info["alert"].resolution_status else "" - - nodes.append({ - 'id': f'alert_{alert_id}', - 'label': f'[Closed]{alert_resolution_title} {alert_info["alert"].alert_title}' if alert_color != '' else f'{alert_resolution_title}{alert_info["alert"].alert_title}', - 'title': f'{alert_info["alert"].alert_description}', - 'group': 'alert', - 'shape': 'icon', - 'icon': _get_icon_alert(alert_color), - 'font': _get_font(in_dark_mode) - }) + alert_node = _create_alert_node(alert_id, alert_info, in_dark_mode) + nodes.append(alert_node) for asset_info in alert_info['assets']: asset_id = asset_info['asset_name'] if asset_id not in added_assets: - nodes.append({ - 'id': f'asset_{asset_id}', - 'label': asset_id, - 'group': 'asset', - 'shape': 'image', - 'image': '/static/assets/img/graph/' + asset_info['icon'], - 'font': _get_font(in_dark_mode) - }) + nodes.append(_create_asset_node(asset_id, asset_info, in_dark_mode)) added_assets.add(asset_id) edges.append({ @@ -1138,14 +1123,7 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale for ioc_value in alert_info['iocs']: if ioc_value not in added_iocs: - nodes.append({ - 'id': f'ioc_{ioc_value}', - 'label': ioc_value, - 'group': 'ioc', - 'shape': 'icon', - 'icon': _get_icon_ioc(in_dark_mode), - 'font': _get_font(in_dark_mode) - }) + nodes.append(_create_ioc_node(in_dark_mode, ioc_value)) added_iocs.add(ioc_value) edges.append({ @@ -1155,13 +1133,7 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale }) if open_cases or closed_cases: - close_condition = None - if open_cases and not closed_cases: - close_condition = Cases.close_date.is_(None) - if closed_cases and not open_cases: - close_condition = Cases.close_date.isnot(None) - if open_cases and closed_cases: - close_condition = Cases.close_date.isnot(None) | Cases.close_date.is_(None) + close_condition = _build_cases_closed_filter(open_cases, closed_cases) matching_ioc_cases = ( db.session.query(Ioc) @@ -1211,6 +1183,7 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale 'close_date': close_date, 'description': case_desc} cases_data[case_id]['matching_assets'].append(asset_name) + added_cases = set() for case_id in cases_data: if case_id not in added_cases: is_closed = cases_data[case_id].get('close_date') @@ -1245,6 +1218,57 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale } +def _build_cases_closed_filter(open_cases, closed_cases): + # OPEN + if open_cases and not closed_cases: + return Cases.close_date.is_(None) + # CLOSED + if closed_cases and not open_cases: + return Cases.close_date.isnot(None) + # ANY + if open_cases and closed_cases: + return Cases.close_date.isnot(None) | Cases.close_date.is_(None) + return None + + +def _create_ioc_node(in_dark_mode, ioc_value): + return { + 'id': f'ioc_{ioc_value}', + 'label': ioc_value, + 'group': 'ioc', + 'shape': 'icon', + 'icon': _get_icon_ioc(in_dark_mode), + 'font': _get_font(in_dark_mode) + } + + +def _create_asset_node(asset_id, asset_info, in_dark_mode): + return { + 'id': f'asset_{asset_id}', + 'label': asset_id, + 'group': 'asset', + 'shape': 'image', + 'image': '/static/assets/img/graph/' + asset_info['icon'], + 'font': _get_font(in_dark_mode) + } + + +def _create_alert_node(alert_id, alert_info, in_dark_mode): + alert_color = '#c95029' if alert_info['alert'].status.status_name in ['Closed', 'Merged', 'Escalated'] else '' + alert_resolution_title = f'[{alert_info["alert"].resolution_status.resolution_status_name}]\n' if alert_info[ + "alert"].resolution_status else "" + alert_node = { + 'id': f'alert_{alert_id}', + 'label': f'[Closed]{alert_resolution_title} {alert_info["alert"].alert_title}' if alert_color != '' else f'{alert_resolution_title}{alert_info["alert"].alert_title}', + 'title': f'{alert_info["alert"].alert_description}', + 'group': 'alert', + 'shape': 'icon', + 'icon': _get_icon_alert(alert_color), + 'font': _get_font(in_dark_mode) + } + return alert_node + + def _get_closed_alerts_status_identifiers(): return _get_alerts_status_identifiers(['Closed', 'Merged', 'Escalated']) From f99e77e5541db7c69450b469d0fa925802d68176 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:33:36 +0100 Subject: [PATCH 222/286] Extracted method --- source/app/datamgmt/alerts/alerts_db.py | 80 ++++++++++++++----------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index fbf95ace2..4859baf5c 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1133,50 +1133,17 @@ def _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, customer_ }) if open_cases or closed_cases: - close_condition = _build_cases_closed_filter(open_cases, closed_cases) - - matching_ioc_cases = ( - db.session.query(Ioc) - .with_entities(Ioc.case_id, Ioc.ioc_value, Cases.name, Cases.close_date, Cases.description) - .join(Ioc.case) - .filter( - and_( - and_( - Ioc.ioc_value.in_(added_iocs), - close_condition, - ), - Cases.client_id == customer_id - ) - ) - .distinct() - .all() - ) - - matching_asset_cases = ( - db.session.query(CaseAssets) - .with_entities(CaseAssets.case_id, CaseAssets.asset_name, Cases.name, Cases.close_date, Cases.description) - .join(CaseAssets.case) - .filter( - and_( - and_( - CaseAssets.asset_name.in_(added_assets), - close_condition - ), - Cases.client_id == customer_id - ) - ) - .distinct(CaseAssets.case_id) - .all() - ) cases_data = {} + matching_ioc_cases = get_iocs_with_cases(added_iocs, customer_id, open_cases, closed_cases) for case_id, ioc_value, case_name, close_date, case_desc in matching_ioc_cases: if case_id not in cases_data: cases_data[case_id] = {'name': case_name, 'matching_ioc': [], 'matching_assets': [], 'close_date': close_date, 'description': case_desc} cases_data[case_id]['matching_ioc'].append(ioc_value) + matching_asset_cases = get_assets_with_cases(added_assets, customer_id, open_cases, closed_cases) for case_id, asset_name, case_name, close_date, case_desc in matching_asset_cases: if case_id not in cases_data: cases_data[case_id] = {'name': case_name, 'matching_ioc': [], 'matching_assets': [], @@ -1218,6 +1185,49 @@ def _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, customer_ } +def get_assets_with_cases(added_assets, customer_id, open_cases, closed_cases): + close_condition = _build_cases_closed_filter(open_cases, closed_cases) + matching_asset_cases = ( + db.session.query(CaseAssets) + .with_entities(CaseAssets.case_id, CaseAssets.asset_name, Cases.name, Cases.close_date, Cases.description) + .join(CaseAssets.case) + .filter( + and_( + and_( + CaseAssets.asset_name.in_(added_assets), + close_condition + ), + Cases.client_id == customer_id + ) + ) + .distinct(CaseAssets.case_id) + .all() + ) + return matching_asset_cases + + +def get_iocs_with_cases(added_iocs, customer_id, open_cases, closed_cases): + close_condition = _build_cases_closed_filter(open_cases, closed_cases) + + matching_ioc_cases = ( + db.session.query(Ioc) + .with_entities(Ioc.case_id, Ioc.ioc_value, Cases.name, Cases.close_date, Cases.description) + .join(Ioc.case) + .filter( + and_( + and_( + Ioc.ioc_value.in_(added_iocs), + close_condition, + ), + Cases.client_id == customer_id + ) + ) + .distinct() + .all() + ) + return matching_ioc_cases + + def _build_cases_closed_filter(open_cases, closed_cases): # OPEN if open_cases and not closed_cases: From e589c165c0c7dc3e4c23fb199b85f2b25a7a9b25 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:40:36 +0100 Subject: [PATCH 223/286] Moved code up into the business layer --- source/app/business/alerts.py | 167 +++++++++++++++++++++++- source/app/datamgmt/alerts/alerts_db.py | 163 +---------------------- 2 files changed, 167 insertions(+), 163 deletions(-) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 7bb671c10..9a0b7cdc3 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -33,6 +33,8 @@ from app.datamgmt.alerts.alerts_db import delete_alert from app.datamgmt.alerts.alerts_db import get_filtered_alerts from app.datamgmt.alerts.alerts_db import get_related_alerts_details +from app.datamgmt.alerts.alerts_db import get_assets_with_cases +from app.datamgmt.alerts.alerts_db import get_iocs_with_cases from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.util import add_obj_history_entry @@ -112,6 +114,167 @@ def alerts_get(user, permissions, identifier) -> Alert: return alert +# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! +def _get_font(in_dark_mode) -> str: + if in_dark_mode: + return '12px verdana white' + return '' + + +# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! +def _get_icon_alert(alert_color) -> dict[str, str]: + return { + 'face': 'FontAwesome', + 'code': '\uf0f3', + 'color': alert_color, + 'weight': 'bold' + } + + +# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! +def _get_icon_ioc(in_dark_mode) -> dict[str, str]: + return { + 'face': 'FontAwesome', + 'code': '\ue4a8', + 'color': 'white' if in_dark_mode else '', + 'weight': "bold" + } + + +# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! +def _get_icon_case(is_closed) -> dict[str, str]: + return { + 'face': 'FontAwesome', + 'code': '\uf0b1', + 'color': '#c95029' if is_closed else '#4cba4f' + } + + +def _create_alert_node(alert_id, alert_info, in_dark_mode): + alert_color = '#c95029' if alert_info['alert'].status.status_name in ['Closed', 'Merged', 'Escalated'] else '' + alert_resolution_title = f'[{alert_info["alert"].resolution_status.resolution_status_name}]\n' if alert_info[ + "alert"].resolution_status else "" + alert_node = { + 'id': f'alert_{alert_id}', + 'label': f'[Closed]{alert_resolution_title} {alert_info["alert"].alert_title}' if alert_color != '' else f'{alert_resolution_title}{alert_info["alert"].alert_title}', + 'title': f'{alert_info["alert"].alert_description}', + 'group': 'alert', + 'shape': 'icon', + 'icon': _get_icon_alert(alert_color), + 'font': _get_font(in_dark_mode) + } + return alert_node + + +def _create_ioc_node(in_dark_mode, ioc_value): + return { + 'id': f'ioc_{ioc_value}', + 'label': ioc_value, + 'group': 'ioc', + 'shape': 'icon', + 'icon': _get_icon_ioc(in_dark_mode), + 'font': _get_font(in_dark_mode) + } + + +def _create_asset_node(asset_id, asset_info, in_dark_mode): + return { + 'id': f'asset_{asset_id}', + 'label': asset_id, + 'group': 'asset', + 'shape': 'image', + 'image': '/static/assets/img/graph/' + asset_info['icon'], + 'font': _get_font(in_dark_mode) + } + + +def _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, customer_id, in_dark_mode): + nodes = [] + edges = [] + + added_assets = set() + added_iocs = set() + + for alert_id, alert_info in alerts_dict.items(): + alert_node = _create_alert_node(alert_id, alert_info, in_dark_mode) + nodes.append(alert_node) + + for asset_info in alert_info['assets']: + asset_id = asset_info['asset_name'] + + if asset_id not in added_assets: + nodes.append(_create_asset_node(asset_id, asset_info, in_dark_mode)) + added_assets.add(asset_id) + + edges.append({ + 'from': f'alert_{alert_id}', + 'to': f'asset_{asset_id}' + }) + + for ioc_value in alert_info['iocs']: + if ioc_value not in added_iocs: + nodes.append(_create_ioc_node(in_dark_mode, ioc_value)) + added_iocs.add(ioc_value) + + edges.append({ + 'from': f'alert_{alert_id}', + 'to': f'ioc_{ioc_value}', + 'dashes': True + }) + + if open_cases or closed_cases: + + cases_data = {} + + matching_ioc_cases = get_iocs_with_cases(added_iocs, customer_id, open_cases, closed_cases) + for case_id, ioc_value, case_name, close_date, case_desc in matching_ioc_cases: + if case_id not in cases_data: + cases_data[case_id] = {'name': case_name, 'matching_ioc': [], 'matching_assets': [], + 'close_date': close_date, 'description': case_desc} + cases_data[case_id]['matching_ioc'].append(ioc_value) + + matching_asset_cases = get_assets_with_cases(added_assets, customer_id, open_cases, closed_cases) + for case_id, asset_name, case_name, close_date, case_desc in matching_asset_cases: + if case_id not in cases_data: + cases_data[case_id] = {'name': case_name, 'matching_ioc': [], 'matching_assets': [], + 'close_date': close_date, 'description': case_desc} + cases_data[case_id]['matching_assets'].append(asset_name) + + added_cases = set() + for case_id in cases_data: + if case_id not in added_cases: + is_closed = cases_data[case_id].get('close_date') + nodes.append({ + 'id': f'case_{case_id}', + 'label': f'[Closed] Case #{case_id}' if cases_data[case_id].get('close_date') else f'Case #{case_id}', + 'title': cases_data[case_id].get("description"), + 'group': 'case', + 'shape': 'icon', + 'icon': _get_icon_case(is_closed), + 'font': _get_font(in_dark_mode) + }) + added_cases.add(case_id) + + for ioc_value in cases_data[case_id]['matching_ioc']: + edges.append({ + 'from': f'ioc_{ioc_value}', + 'to': f'case_{case_id}', + 'dashes': True + }) + + for asset_name in cases_data[case_id]['matching_assets']: + edges.append({ + 'from': f'asset_{asset_name}', + 'to': f'case_{case_id}', + 'dashes': True + }) + + return { + 'nodes': nodes, + 'edges': edges + } + + def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, closed_cases, days_back, number_of_results): assets = alert.assets iocs = alert.iocs @@ -122,9 +285,11 @@ def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, clos } in_dark_mode = user.in_dark_mode - return get_related_alerts_details(in_dark_mode, alert.alert_customer_id, assets, iocs, + alerts_dict = get_related_alerts_details(in_dark_mode, alert.alert_customer_id, assets, iocs, open_alerts, closed_alerts, open_cases, closed_cases, days_back, number_of_results) + return _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, alert.alert_customer_id, in_dark_mode) + def alerts_exists(user, permissions, identifier) -> bool: diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 4859baf5c..31fa96c18 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -994,42 +994,6 @@ def get_related_alerts(customer_id, assets, iocs, details=False): return similarities -# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! -def _get_font(in_dark_mode) -> str: - if in_dark_mode: - return '12px verdana white' - return '' - - -# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! -def _get_icon_case(is_closed) -> dict[str, str]: - return { - 'face': 'FontAwesome', - 'code': '\uf0b1', - 'color': '#c95029' if is_closed else '#4cba4f' - } - - -# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! -def _get_icon_ioc(in_dark_mode) -> dict[str, str]: - return { - 'face': 'FontAwesome', - 'code': '\ue4a8', - 'color': 'white' if in_dark_mode else '', - 'weight': "bold" - } - - -# TODO this is presentation code, should be in the presentation layer (frontend) rather than in the persistence layer! -def _get_icon_alert(alert_color) -> dict[str, str]: - return { - 'face': 'FontAwesome', - 'code': '\uf0f3', - 'color': alert_color, - 'weight': 'bold' - } - - def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_alerts, closed_alerts, open_cases, closed_cases, days_back, number_of_results): """ @@ -1095,94 +1059,7 @@ def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_ale if any(value == ioc_value for value, _ in ioc_values): alerts_dict[alert.alert_id]['iocs'].append(ioc_value) - return _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, customer_id, in_dark_mode) - - -def _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, customer_id, in_dark_mode): - nodes = [] - edges = [] - - added_assets = set() - added_iocs = set() - - for alert_id, alert_info in alerts_dict.items(): - alert_node = _create_alert_node(alert_id, alert_info, in_dark_mode) - nodes.append(alert_node) - - for asset_info in alert_info['assets']: - asset_id = asset_info['asset_name'] - - if asset_id not in added_assets: - nodes.append(_create_asset_node(asset_id, asset_info, in_dark_mode)) - added_assets.add(asset_id) - - edges.append({ - 'from': f'alert_{alert_id}', - 'to': f'asset_{asset_id}' - }) - - for ioc_value in alert_info['iocs']: - if ioc_value not in added_iocs: - nodes.append(_create_ioc_node(in_dark_mode, ioc_value)) - added_iocs.add(ioc_value) - - edges.append({ - 'from': f'alert_{alert_id}', - 'to': f'ioc_{ioc_value}', - 'dashes': True - }) - - if open_cases or closed_cases: - - cases_data = {} - - matching_ioc_cases = get_iocs_with_cases(added_iocs, customer_id, open_cases, closed_cases) - for case_id, ioc_value, case_name, close_date, case_desc in matching_ioc_cases: - if case_id not in cases_data: - cases_data[case_id] = {'name': case_name, 'matching_ioc': [], 'matching_assets': [], - 'close_date': close_date, 'description': case_desc} - cases_data[case_id]['matching_ioc'].append(ioc_value) - - matching_asset_cases = get_assets_with_cases(added_assets, customer_id, open_cases, closed_cases) - for case_id, asset_name, case_name, close_date, case_desc in matching_asset_cases: - if case_id not in cases_data: - cases_data[case_id] = {'name': case_name, 'matching_ioc': [], 'matching_assets': [], - 'close_date': close_date, 'description': case_desc} - cases_data[case_id]['matching_assets'].append(asset_name) - - added_cases = set() - for case_id in cases_data: - if case_id not in added_cases: - is_closed = cases_data[case_id].get('close_date') - nodes.append({ - 'id': f'case_{case_id}', - 'label': f'[Closed] Case #{case_id}' if cases_data[case_id].get('close_date') else f'Case #{case_id}', - 'title': cases_data[case_id].get("description"), - 'group': 'case', - 'shape': 'icon', - 'icon': _get_icon_case(is_closed), - 'font': _get_font(in_dark_mode) - }) - added_cases.add(case_id) - - for ioc_value in cases_data[case_id]['matching_ioc']: - edges.append({ - 'from': f'ioc_{ioc_value}', - 'to': f'case_{case_id}', - 'dashes': True - }) - - for asset_name in cases_data[case_id]['matching_assets']: - edges.append({ - 'from': f'asset_{asset_name}', - 'to': f'case_{case_id}', - 'dashes': True - }) - - return { - 'nodes': nodes, - 'edges': edges - } + return alerts_dict def get_assets_with_cases(added_assets, customer_id, open_cases, closed_cases): @@ -1241,44 +1118,6 @@ def _build_cases_closed_filter(open_cases, closed_cases): return None -def _create_ioc_node(in_dark_mode, ioc_value): - return { - 'id': f'ioc_{ioc_value}', - 'label': ioc_value, - 'group': 'ioc', - 'shape': 'icon', - 'icon': _get_icon_ioc(in_dark_mode), - 'font': _get_font(in_dark_mode) - } - - -def _create_asset_node(asset_id, asset_info, in_dark_mode): - return { - 'id': f'asset_{asset_id}', - 'label': asset_id, - 'group': 'asset', - 'shape': 'image', - 'image': '/static/assets/img/graph/' + asset_info['icon'], - 'font': _get_font(in_dark_mode) - } - - -def _create_alert_node(alert_id, alert_info, in_dark_mode): - alert_color = '#c95029' if alert_info['alert'].status.status_name in ['Closed', 'Merged', 'Escalated'] else '' - alert_resolution_title = f'[{alert_info["alert"].resolution_status.resolution_status_name}]\n' if alert_info[ - "alert"].resolution_status else "" - alert_node = { - 'id': f'alert_{alert_id}', - 'label': f'[Closed]{alert_resolution_title} {alert_info["alert"].alert_title}' if alert_color != '' else f'{alert_resolution_title}{alert_info["alert"].alert_title}', - 'title': f'{alert_info["alert"].alert_description}', - 'group': 'alert', - 'shape': 'icon', - 'icon': _get_icon_alert(alert_color), - 'font': _get_font(in_dark_mode) - } - return alert_node - - def _get_closed_alerts_status_identifiers(): return _get_alerts_status_identifiers(['Closed', 'Merged', 'Escalated']) From 22e04964fe14441751869d815a962e709d15f923 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:45:26 +0100 Subject: [PATCH 224/286] Code cleanup --- source/app/business/alerts.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 9a0b7cdc3..59f0de55e 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -188,6 +188,18 @@ def _create_asset_node(asset_id, asset_info, in_dark_mode): } +def _create_case_node(case_id, description, close_date, in_dark_mode): + return { + 'id': f'case_{case_id}', + 'label': f'[Closed] Case #{case_id}' if close_date else f'Case #{case_id}', + 'title': description, + 'group': 'case', + 'shape': 'icon', + 'icon': _get_icon_case(close_date), + 'font': _get_font(in_dark_mode) + } + + def _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, customer_id, in_dark_mode): nodes = [] edges = [] @@ -242,27 +254,21 @@ def _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, customer_ added_cases = set() for case_id in cases_data: + case_data = cases_data[case_id] if case_id not in added_cases: - is_closed = cases_data[case_id].get('close_date') - nodes.append({ - 'id': f'case_{case_id}', - 'label': f'[Closed] Case #{case_id}' if cases_data[case_id].get('close_date') else f'Case #{case_id}', - 'title': cases_data[case_id].get("description"), - 'group': 'case', - 'shape': 'icon', - 'icon': _get_icon_case(is_closed), - 'font': _get_font(in_dark_mode) - }) + close_date = case_data.get('close_date') + description = case_data.get('description') + nodes.append(_create_case_node(case_id, description, close_date, in_dark_mode)) added_cases.add(case_id) - for ioc_value in cases_data[case_id]['matching_ioc']: + for ioc_value in case_data['matching_ioc']: edges.append({ 'from': f'ioc_{ioc_value}', 'to': f'case_{case_id}', 'dashes': True }) - for asset_name in cases_data[case_id]['matching_assets']: + for asset_name in case_data['matching_assets']: edges.append({ 'from': f'asset_{asset_name}', 'to': f'case_{case_id}', From 3307565461e1951e6b00c554847b32480cb06b76 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:46:49 +0100 Subject: [PATCH 225/286] Flattened and_ --- source/app/datamgmt/alerts/alerts_db.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 31fa96c18..70328b1b2 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1070,10 +1070,8 @@ def get_assets_with_cases(added_assets, customer_id, open_cases, closed_cases): .join(CaseAssets.case) .filter( and_( - and_( - CaseAssets.asset_name.in_(added_assets), - close_condition - ), + CaseAssets.asset_name.in_(added_assets), + close_condition, Cases.client_id == customer_id ) ) @@ -1092,10 +1090,8 @@ def get_iocs_with_cases(added_iocs, customer_id, open_cases, closed_cases): .join(Ioc.case) .filter( and_( - and_( - Ioc.ioc_value.in_(added_iocs), - close_condition, - ), + Ioc.ioc_value.in_(added_iocs), + close_condition, Cases.client_id == customer_id ) ) From e557cb71a20691b660943308cbe2c9863cd38606 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:47:29 +0100 Subject: [PATCH 226/286] Fixed ruff warning --- source/app/business/alerts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 59f0de55e..34d6c3ce7 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -297,7 +297,6 @@ def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, clos return _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, alert.alert_customer_id, in_dark_mode) - def alerts_exists(user, permissions, identifier) -> bool: alert = _get(user, permissions, identifier) From 0f05312511e83f061367304c9bc0481ecd1efb05 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:49:05 +0100 Subject: [PATCH 227/286] Removed unused parameters --- source/app/business/alerts.py | 5 ++--- source/app/datamgmt/alerts/alerts_db.py | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 34d6c3ce7..2bea4fc3c 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -291,9 +291,8 @@ def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, clos } in_dark_mode = user.in_dark_mode - alerts_dict = get_related_alerts_details(in_dark_mode, alert.alert_customer_id, assets, iocs, - open_alerts, closed_alerts, open_cases, closed_cases, - days_back, number_of_results) + alerts_dict = get_related_alerts_details(alert.alert_customer_id, assets, iocs, open_alerts, closed_alerts, + days_back, number_of_results) return _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, alert.alert_customer_id, in_dark_mode) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 70328b1b2..fa011e913 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -994,8 +994,7 @@ def get_related_alerts(customer_id, assets, iocs, details=False): return similarities -def get_related_alerts_details(in_dark_mode, customer_id, assets, iocs, open_alerts, closed_alerts, open_cases, closed_cases, - days_back, number_of_results): +def get_related_alerts_details(customer_id, assets, iocs, open_alerts, closed_alerts, days_back, number_of_results): """ Get the details of the related alerts From a9a91c27975830d00447aa04c3a109b128187be7 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:52:16 +0100 Subject: [PATCH 228/286] Moved up iris_current_user --- source/app/blueprints/rest/datastore_routes.py | 4 ++-- source/app/datamgmt/datastore/datastore_db.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/app/blueprints/rest/datastore_routes.py b/source/app/blueprints/rest/datastore_routes.py index b6128d865..744663c36 100644 --- a/source/app/blueprints/rest/datastore_routes.py +++ b/source/app/blueprints/rest/datastore_routes.py @@ -141,7 +141,7 @@ def datastore_update_file(cur_id: int, caseid: int): msg_added_as += 'and added in IOC' if dsf.file_is_evidence: - datastore_add_file_as_evidence(dsf, caseid) + datastore_add_file_as_evidence(iris_current_user.id, dsf, caseid) msg_added_as += ' and evidence' if len(msg_added_as) > 0 else 'and added in evidence' track_activity(f'File \"{dsf.file_original_name}\" updated in DS', caseid=caseid) @@ -266,7 +266,7 @@ def datastore_add_file(cur_id: int, caseid: int): msg_added_as += 'and added in IOC' if dsf_sc.file_is_evidence: - datastore_add_file_as_evidence(dsf_sc, caseid) + datastore_add_file_as_evidence(iris_current_user.id, dsf_sc, caseid) msg_added_as += ' and evidence' if len(msg_added_as) > 0 else 'and added in evidence' track_activity(f"File \"{dsf_sc.file_original_name}\" added to DS", caseid=caseid) diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 2328a64d1..6f318352c 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -366,7 +366,7 @@ def datastore_add_file_as_ioc(dsf): db_create(ioc) -def datastore_add_file_as_evidence(dsf, caseid): +def datastore_add_file_as_evidence(user_identifier, dsf, caseid): crf = CaseReceivedFile.query.filter( CaseReceivedFile.file_hash == dsf.file_sha256 ).first() @@ -379,7 +379,7 @@ def datastore_add_file_as_evidence(dsf, caseid): crf.date_added = datetime.datetime.now() crf.filename = dsf.file_original_name crf.file_size = dsf.file_size - crf.user_id = iris_current_user.id + crf.user_id = user_identifier db_create(crf) From 4880f736118c6665fedfb512d000bc2ddbbb8762 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:56:01 +0100 Subject: [PATCH 229/286] Single quotes --- source/app/datamgmt/datastore/datastore_db.py | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 6f318352c..7389d8050 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -71,18 +71,18 @@ def ds_list_tree(cid): ).all() dsp_root = dsp_root - droot_id = f"d-{dsp_root.path_id}" + droot_id = f'd-{dsp_root.path_id}' path_tree = { droot_id: { - "name": dsp_root.path_name, - "type": "directory", - "is_root": True, - "children": {} + 'name': dsp_root.path_name, + 'type': 'directory', + 'is_root': True, + 'children': {} } } - droot_children = path_tree[droot_id]["children"] + droot_children = path_tree[droot_id]['children'] files_nodes = {} @@ -90,9 +90,9 @@ def ds_list_tree(cid): dfnode = dfile.__dict__ dfnode.pop('_sa_instance_state') dfnode.pop('file_local_name') - dfnode['type'] = "file" - dnode_id = f"f-{dfile.file_id}" - dnode_parent_id = f"d-{dfile.file_parent_id}" + dfnode['type'] = 'file' + dnode_id = f'f-{dfile.file_id}' + dnode_parent_id = f'd-{dfile.file_parent_id}' if dnode_parent_id == droot_id: droot_children.update({ @@ -108,19 +108,19 @@ def ds_list_tree(cid): files_nodes[dnode_parent_id] = {dnode_id: dfnode} for dpath in dsp: - dpath_id = f"d-{dpath.path_id}" - dpath_parent_id = f"d-{dpath.path_parent_id}" + dpath_id = f'd-{dpath.path_id}' + dpath_parent_id = f'd-{dpath.path_parent_id}' path_node = { dpath_id: { - "name": dpath.path_name, - "type": "directory", - "children": {} + 'name': dpath.path_name, + 'type': 'directory', + 'children': {} } } path_node_files = files_nodes.get(dpath_id) if path_node_files: - path_node[dpath_id]["children"].update(path_node_files) + path_node[dpath_id]['children'].update(path_node_files) if dpath_parent_id == droot_id: droot_children.update(path_node) @@ -164,7 +164,7 @@ def init_ds_tree(cid): def datastore_iter_tree(path_parent_id, path_node, tree): for parent_id, node in tree.items(): if parent_id == path_parent_id: - node["children"].update(path_node) + node['children'].update(path_node) return tree if isinstance(node, dict): @@ -306,12 +306,12 @@ def datastore_get_standard_path(datastore_file, cid): else: target_path = root_path / 'Regulars' - target_path = target_path / f"case-{cid}" + target_path = target_path / f'case-{cid}' if not target_path.is_dir(): target_path.mkdir(parents=True, exist_ok=True) - return target_path / f"dsf-{datastore_file.file_uuid}" + return target_path / f'dsf-{datastore_file.file_uuid}' def datastore_get_file(file_id, cid): @@ -357,10 +357,10 @@ def datastore_add_file_as_ioc(dsf): if ioc is None: ioc = Ioc() ioc.ioc_value = dsf.file_sha256 - ioc.ioc_description = f"SHA256 of {dsf.file_original_name}. Imported from datastore." + ioc.ioc_description = f'SHA256 of {dsf.file_original_name}. Imported from datastore.' ioc.ioc_type_id = ioc_type_id.type_id ioc.ioc_tlp_id = ioc_tlp_id.tlp_id - ioc.ioc_tags = "datastore" + ioc.ioc_tags = 'datastore' ioc.user_id = iris_current_user.id db_create(ioc) @@ -374,7 +374,7 @@ def datastore_add_file_as_evidence(user_identifier, dsf, caseid): if crf is None: crf = CaseReceivedFile() crf.file_hash = dsf.file_sha256 - crf.file_description = f"Imported from datastore. {dsf.file_description}" + crf.file_description = f'Imported from datastore. {dsf.file_description}' crf.case_id = caseid crf.date_added = datetime.datetime.now() crf.filename = dsf.file_original_name @@ -458,7 +458,7 @@ def datastore_filter_tree(filter_d, caseid): if has_password is not None: condition = and_(condition, - (DataStoreFile.file_password != "")) + (DataStoreFile.file_password != '')) dsp_root = DataStorePath.query.filter( and_(DataStorePath.path_case_id == caseid, @@ -492,18 +492,18 @@ def datastore_filter_tree(filter_d, caseid): return None, str(e) dsp_root = dsp_root - droot_id = f"d-{dsp_root.path_id}" + droot_id = f'd-{dsp_root.path_id}' path_tree = { droot_id: { - "name": dsp_root.path_name, - "type": "directory", - "is_root": True, - "children": {} + 'name': dsp_root.path_name, + 'type': 'directory', + 'is_root': True, + 'children': {} } } - droot_children = path_tree[droot_id]["children"] + droot_children = path_tree[droot_id]['children'] files_nodes = {} @@ -511,9 +511,9 @@ def datastore_filter_tree(filter_d, caseid): dfnode = dfile.__dict__ dfnode.pop('_sa_instance_state') dfnode.pop('file_local_name') - dfnode['type'] = "file" - dnode_id = f"f-{dfile.file_id}" - dnode_parent_id = f"d-{dfile.file_parent_id}" + dfnode['type'] = 'file' + dnode_id = f'f-{dfile.file_id}' + dnode_parent_id = f'd-{dfile.file_parent_id}' if dnode_parent_id == droot_id: droot_children.update({ @@ -529,19 +529,19 @@ def datastore_filter_tree(filter_d, caseid): files_nodes[dnode_parent_id] = {dnode_id: dfnode} for dpath in dsp: - dpath_id = f"d-{dpath.path_id}" - dpath_parent_id = f"d-{dpath.path_parent_id}" + dpath_id = f'd-{dpath.path_id}' + dpath_parent_id = f'd-{dpath.path_parent_id}' path_node = { dpath_id: { - "name": dpath.path_name, - "type": "directory", - "children": {} + 'name': dpath.path_name, + 'type': 'directory', + 'children': {} } } path_node_files = files_nodes.get(dpath_id) if path_node_files: - path_node[dpath_id]["children"].update(path_node_files) + path_node[dpath_id]['children'].update(path_node_files) if dpath_parent_id == droot_id: droot_children.update(path_node) From 3fb4eda51a9db1864f833e0dee3251fb0ca0177a Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:57:18 +0100 Subject: [PATCH 230/286] Extended existing import constraint --- pyproject.toml | 2 +- source/app/blueprints/rest/datastore_routes.py | 4 ++-- source/app/datamgmt/datastore/datastore_db.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9257ee875..75354885e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the persistence layer (iris_user)" type = "forbidden" -source_modules = "app.datamgmt.case" +source_modules = ["app.datamgmt.case", "app.datamgmt.datastore"] forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true diff --git a/source/app/blueprints/rest/datastore_routes.py b/source/app/blueprints/rest/datastore_routes.py index 744663c36..ff7e8a92d 100644 --- a/source/app/blueprints/rest/datastore_routes.py +++ b/source/app/blueprints/rest/datastore_routes.py @@ -137,7 +137,7 @@ def datastore_update_file(cur_id: int, caseid: int): msg_added_as = '' if dsf.file_is_ioc: - datastore_add_file_as_ioc(dsf) + datastore_add_file_as_ioc(iris_current_user.id, dsf) msg_added_as += 'and added in IOC' if dsf.file_is_evidence: @@ -262,7 +262,7 @@ def datastore_add_file(cur_id: int, caseid: int): msg_added_as = '' if dsf_sc.file_is_ioc: - datastore_add_file_as_ioc(dsf_sc) + datastore_add_file_as_ioc(iris_current_user.id, dsf_sc) msg_added_as += 'and added in IOC' if dsf_sc.file_is_evidence: diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 7389d8050..9f25cbf28 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -25,9 +25,9 @@ from sqlalchemy import func from app import app -from app.datamgmt.db_operations import db_create, db_delete +from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_delete from app.db import db -from app.blueprints.iris_user import iris_current_user from app.models.evidences import CaseReceivedFile from app.models.models import DataStoreFile from app.models.models import DataStorePath @@ -341,7 +341,7 @@ def datastore_delete_file(cur_id, cid): return False, f'File {cur_id} deleted' -def datastore_add_file_as_ioc(dsf): +def datastore_add_file_as_ioc(user_identifier, dsf): ioc = Ioc.query.filter( Ioc.ioc_value == dsf.file_sha256 ).first() @@ -361,7 +361,7 @@ def datastore_add_file_as_ioc(dsf): ioc.ioc_type_id = ioc_type_id.type_id ioc.ioc_tlp_id = ioc_tlp_id.tlp_id ioc.ioc_tags = 'datastore' - ioc.user_id = iris_current_user.id + ioc.user_id = user_identifier db_create(ioc) From 055dc6598163b45852922a3b3d7c46e18a854016 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:28:09 +0100 Subject: [PATCH 231/286] Following calling conventions --- source/app/datamgmt/states.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/app/datamgmt/states.py b/source/app/datamgmt/states.py index ecec79fa8..2a6e59e47 100644 --- a/source/app/datamgmt/states.py +++ b/source/app/datamgmt/states.py @@ -24,7 +24,7 @@ from app.models.models import ObjectState -def _update_object_state(object_name, caseid, userid=None) -> ObjectState: +def _update_object_state(object_name, caseid, userid) -> ObjectState: """ Expects a db commit soon after @@ -83,7 +83,7 @@ def delete_case_states(caseid): def update_timeline_state(caseid, userid=None): - return _update_object_state('timeline', caseid=caseid, userid=userid) + return _update_object_state('timeline', caseid, userid) def get_timeline_state(caseid): @@ -91,7 +91,7 @@ def get_timeline_state(caseid): def update_tasks_state(caseid, userid=None): - return _update_object_state('tasks', caseid=caseid, userid=userid) + return _update_object_state('tasks', caseid, userid) def get_tasks_state(caseid): @@ -99,7 +99,7 @@ def get_tasks_state(caseid): def update_evidences_state(caseid, userid=None): - return _update_object_state('evidences', caseid=caseid, userid=userid) + return _update_object_state('evidences', caseid, userid) def get_evidences_state(caseid): @@ -107,7 +107,7 @@ def get_evidences_state(caseid): def update_ioc_state(caseid, userid=None): - return _update_object_state('ioc', caseid, userid=userid) + return _update_object_state('ioc', caseid, userid) def get_ioc_state(caseid): @@ -115,7 +115,7 @@ def get_ioc_state(caseid): def update_assets_state(caseid, userid=None): - return _update_object_state('assets', caseid=caseid, userid=userid) + return _update_object_state('assets', caseid, userid) def get_assets_state(caseid): @@ -123,7 +123,7 @@ def get_assets_state(caseid): def update_notes_state(caseid, userid=None): - return _update_object_state('notes', caseid=caseid, userid=userid) + return _update_object_state('notes', caseid, userid) def get_notes_state(caseid): From e40674d99eda15d465a8d6697be3ef8c8104ec90 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:33:10 +0100 Subject: [PATCH 232/286] Moved up some imports to iris_current_user --- .../blueprints/rest/case/case_timeline_routes.py | 6 +++--- .../app/blueprints/rest/v2/case_routes/events.py | 3 ++- source/app/business/events.py | 6 +++--- source/app/datamgmt/alerts/alerts_db.py | 6 +++--- source/app/datamgmt/case/case_db.py | 12 ++++++------ source/app/datamgmt/case/case_events_db.py | 4 ++-- source/app/datamgmt/states.py | 15 ++++++++++++--- 7 files changed, 31 insertions(+), 21 deletions(-) diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index 09462dc77..2c8a83cbc 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -631,7 +631,7 @@ def case_delete_event(cur_id, caseid): if not event: return response_error('Not a valid event ID for this case') - events_delete(event) + events_delete(iris_current_user, event) return response_success(f'Event ID {cur_id} deleted') @@ -773,7 +773,7 @@ def case_duplicate_event(cur_id, caseid): event.event_title = f"[DUPLICATED] - {event.event_title}" db.session.add(event) - update_timeline_state(caseid=caseid) + update_timeline_state(caseid) db.session.commit() # Update category @@ -959,7 +959,7 @@ def case_events_upload_csv(caseid): add_obj_history_entry(event, 'created') db.session.add(event) - update_timeline_state(caseid=caseid) + update_timeline_state(caseid) save_event_category(event.event_id, request_data.get('event_category_id')) diff --git a/source/app/blueprints/rest/v2/case_routes/events.py b/source/app/blueprints/rest/v2/case_routes/events.py index f47fc4a15..c23c6bac0 100644 --- a/source/app/blueprints/rest/v2/case_routes/events.py +++ b/source/app/blueprints/rest/v2/case_routes/events.py @@ -40,6 +40,7 @@ from app.iris_engine.utils.collab import notify from app.models.authorization import CaseAccessLevel from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook +from app.blueprints.iris_user import iris_current_user class Events: @@ -142,7 +143,7 @@ def delete(self, case_identifier, identifier): return ac_api_return_access_denied(caseid=event.case_id) self._check_event_and_case_identifier_match(event, case_identifier) - events_delete(event) + events_delete(iris_current_user, event) return response_api_deleted() except ObjectNotFoundError: diff --git a/source/app/business/events.py b/source/app/business/events.py index 8f6702ae8..573c12179 100644 --- a/source/app/business/events.py +++ b/source/app/business/events.py @@ -77,7 +77,7 @@ def events_get(identifier) -> CasesEvent: def events_update(event: CasesEvent, event_category_id, event_assets, event_iocs, event_sync_iocs_assets) -> CasesEvent: add_obj_history_entry(event, 'updated') - update_timeline_state(caseid=event.case_id) + update_timeline_state(event.case_id) db.session.commit() save_event_category(event.event_id, event_category_id) @@ -98,8 +98,8 @@ def events_update(event: CasesEvent, event_category_id, event_assets, event_iocs return event -def events_delete(event: CasesEvent): - delete_event(event) +def events_delete(user, event: CasesEvent): + delete_event(user.id, event) call_modules_hook('on_postload_event_delete', event.event_id, caseid=event.case_id) collab_notify(event.case_id, 'events', 'deletion', event.event_id) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index fa011e913..f224b85db 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -412,7 +412,7 @@ def create_case_from_alerts(alerts: List[Alert], iocs_list: List[str], assets_li add_obj_history_entry(event, 'created') db.session.add(event) - update_timeline_state(caseid=case.case_id) + update_timeline_state(case.case_id) event.category = [unspecified_cat] @@ -580,7 +580,7 @@ def create_case_from_alert(alert: Alert, iocs_list: List[str], assets_list: List add_obj_history_entry(event, 'created') db.session.add(event) - update_timeline_state(caseid=case.case_id) + update_timeline_state(case.case_id) event.category = [unspecified_cat] @@ -702,7 +702,7 @@ def merge_alert_in_case(alert: Alert, case: Cases, iocs_list: List[str], add_obj_history_entry(event, 'created') db.session.add(event) - update_timeline_state(caseid=case.case_id) + update_timeline_state(case.case_id) event.category = [unspecified_cat] diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index 1a2e26aa8..1582fd842 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -239,12 +239,12 @@ def case_db_save(case: Cases): case.name = f'#{case.case_id} - {case.name}' # Create the states - update_timeline_state(caseid=case.case_id, userid=case.user_id) - update_tasks_state(caseid=case.case_id, userid=case.user_id) - update_evidences_state(caseid=case.case_id, userid=case.user_id) - update_ioc_state(caseid=case.case_id, userid=case.user_id) - update_assets_state(caseid=case.case_id, userid=case.user_id) - update_notes_state(caseid=case.case_id, userid=case.user_id) + update_timeline_state(case.case_id, case.user_id) + update_tasks_state(case.case_id, case.user_id) + update_evidences_state(case.case_id, case.user_id) + update_ioc_state(case.case_id, case.user_id) + update_assets_state(case.case_id, case.user_id) + update_notes_state(case.case_id, case.user_id) db.session.commit() diff --git a/source/app/datamgmt/case/case_events_db.py b/source/app/datamgmt/case/case_events_db.py index b23bf4840..44b5c2182 100644 --- a/source/app/datamgmt/case/case_events_db.py +++ b/source/app/datamgmt/case/case_events_db.py @@ -365,7 +365,7 @@ def get_case_iocs_for_tm(caseid): return iocs -def delete_event(event): +def delete_event(user_identifier, event): case_identifier = event.case_id delete_event_category(event.event_id) @@ -393,7 +393,7 @@ def delete_event(event): db.session.commit() db.session.delete(event) - update_timeline_state(caseid=case_identifier) + update_timeline_state(case_identifier, user_identifier) db.session.commit() diff --git a/source/app/datamgmt/states.py b/source/app/datamgmt/states.py index 2a6e59e47..6cbf09f17 100644 --- a/source/app/datamgmt/states.py +++ b/source/app/datamgmt/states.py @@ -36,9 +36,6 @@ def _update_object_state(object_name, caseid, userid) -> ObjectState: Returns: ObjectState object """ - if not userid: - userid = iris_current_user.id - os = ObjectState.query.filter(and_( ObjectState.object_name == object_name, ObjectState.object_case_id == caseid @@ -83,6 +80,8 @@ def delete_case_states(caseid): def update_timeline_state(caseid, userid=None): + if not userid: + userid = iris_current_user.id return _update_object_state('timeline', caseid, userid) @@ -91,6 +90,8 @@ def get_timeline_state(caseid): def update_tasks_state(caseid, userid=None): + if not userid: + userid = iris_current_user.id return _update_object_state('tasks', caseid, userid) @@ -99,6 +100,8 @@ def get_tasks_state(caseid): def update_evidences_state(caseid, userid=None): + if not userid: + userid = iris_current_user.id return _update_object_state('evidences', caseid, userid) @@ -107,6 +110,8 @@ def get_evidences_state(caseid): def update_ioc_state(caseid, userid=None): + if not userid: + userid = iris_current_user.id return _update_object_state('ioc', caseid, userid) @@ -115,6 +120,8 @@ def get_ioc_state(caseid): def update_assets_state(caseid, userid=None): + if not userid: + userid = iris_current_user.id return _update_object_state('assets', caseid, userid) @@ -123,6 +130,8 @@ def get_assets_state(caseid): def update_notes_state(caseid, userid=None): + if not userid: + userid = iris_current_user.id return _update_object_state('notes', caseid, userid) From d3721f8c00cac0aa084fe23ff9cb01397189e0ce Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:35:17 +0100 Subject: [PATCH 233/286] Added missing preamble --- source/app/datamgmt/filters/filters_db.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/source/app/datamgmt/filters/filters_db.py b/source/app/datamgmt/filters/filters_db.py index b87f5dbcf..d36c977b2 100644 --- a/source/app/datamgmt/filters/filters_db.py +++ b/source/app/datamgmt/filters/filters_db.py @@ -1,3 +1,21 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 sqlalchemy import and_ from app.blueprints.iris_user import iris_current_user From 043cac8f9abc56195405c858b4fd89133a8ebf65 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:36:15 +0100 Subject: [PATCH 234/286] Moved up some imports to iris_current_user --- source/app/blueprints/rest/filters_routes.py | 2 +- source/app/datamgmt/filters/filters_db.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/filters_routes.py b/source/app/blueprints/rest/filters_routes.py index 51d09b7cc..a4fb9a80e 100644 --- a/source/app/blueprints/rest/filters_routes.py +++ b/source/app/blueprints/rest/filters_routes.py @@ -174,7 +174,7 @@ def filters_list_route(filter_type) -> Response: saved_filter_schema = SavedFilterSchema(many=True) try: - saved_filters = list_filters_by_type(str(filter_type).lower()) + saved_filters = list_filters_by_type(iris_current_user.id, str(filter_type).lower()) return response_success(data=saved_filter_schema.dump(saved_filters)) diff --git a/source/app/datamgmt/filters/filters_db.py b/source/app/datamgmt/filters/filters_db.py index d36c977b2..f0540b292 100644 --- a/source/app/datamgmt/filters/filters_db.py +++ b/source/app/datamgmt/filters/filters_db.py @@ -40,7 +40,7 @@ def get_filter_by_id(filter_id): return saved_filter -def list_filters_by_type(filter_type): +def list_filters_by_type(user_identifier, filter_type): """ List filters by type @@ -58,7 +58,7 @@ def list_filters_by_type(filter_type): private_filters_for_user = SavedFilter.query.filter( and_( SavedFilter.filter_is_private == True, - SavedFilter.created_by == iris_current_user.id, + SavedFilter.created_by == user_identifier, SavedFilter.filter_type == filter_type ) ) From 0c1c654ba70d08f09866e07fdf345653d4bbc75b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:38:15 +0100 Subject: [PATCH 235/286] Enforced one more constraint to avoid importing iris_current_user from the persistence layer --- pyproject.toml | 2 +- source/app/blueprints/rest/filters_routes.py | 6 +++--- source/app/blueprints/rest/v2/alerts_filters.py | 6 +++--- source/app/business/alerts_filters.py | 4 ++-- source/app/datamgmt/filters/filters_db.py | 5 ++--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 75354885e..b84ec333d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the persistence layer (iris_user)" type = "forbidden" -source_modules = ["app.datamgmt.case", "app.datamgmt.datastore"] +source_modules = ["app.datamgmt.case", "app.datamgmt.datastore", "app.datamgmt.filters"] forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true diff --git a/source/app/blueprints/rest/filters_routes.py b/source/app/blueprints/rest/filters_routes.py index a4fb9a80e..ba527879b 100644 --- a/source/app/blueprints/rest/filters_routes.py +++ b/source/app/blueprints/rest/filters_routes.py @@ -85,7 +85,7 @@ def filters_update_route(filter_id) -> Response: try: data = request.get_json() - saved_filter = get_filter_by_id(filter_id) + saved_filter = get_filter_by_id(iris_current_user.id, filter_id) if not saved_filter: return response_error('Filter not found') @@ -115,7 +115,7 @@ def filters_delete_route(filter_id) -> Response: Response object """ try: - saved_filter = get_filter_by_id(filter_id) + saved_filter = get_filter_by_id(iris_current_user.id, filter_id) if not saved_filter: return response_error('Filter not found') @@ -147,7 +147,7 @@ def filters_get_route(filter_id) -> Response: saved_filter_schema = SavedFilterSchema() try: - saved_filter = get_filter_by_id(filter_id) + saved_filter = get_filter_by_id(iris_current_user.id, filter_id) if not saved_filter: return response_error('Filter not found') diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index f2ef77796..1053e3eed 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -63,7 +63,7 @@ def create(self): def get(self, identifier): try: - saved_filter = alert_filter_get(identifier) + saved_filter = alert_filter_get(iris_current_user, identifier) return response_api_success(self._schema.dump(saved_filter)) except ObjectNotFoundError: @@ -76,7 +76,7 @@ def put(self, identifier): request_data = request.get_json() try: - saved_filter = alert_filter_get(identifier) + saved_filter = alert_filter_get(iris_current_user, 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)) @@ -93,7 +93,7 @@ def put(self, identifier): @staticmethod def delete(identifier): try: - saved_filter = alert_filter_get(identifier) + saved_filter = alert_filter_get(iris_current_user, identifier) alert_filter_delete(saved_filter) return response_api_deleted() diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index 09ee796a8..6d1b5c245 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -26,8 +26,8 @@ def alert_filter_add(new_saved_filter): db.session.commit() -def alert_filter_get(identifier): - alert_filter = get_filter_by_id(identifier) +def alert_filter_get(user, identifier): + alert_filter = get_filter_by_id(user.id, identifier) if not alert_filter: raise ObjectNotFoundError() return alert_filter diff --git a/source/app/datamgmt/filters/filters_db.py b/source/app/datamgmt/filters/filters_db.py index f0540b292..4b5ef82b1 100644 --- a/source/app/datamgmt/filters/filters_db.py +++ b/source/app/datamgmt/filters/filters_db.py @@ -18,11 +18,10 @@ from sqlalchemy import and_ -from app.blueprints.iris_user import iris_current_user from app.models.models import SavedFilter -def get_filter_by_id(filter_id): +def get_filter_by_id(user_identifier, filter_id): """ Get a filter by its ID @@ -34,7 +33,7 @@ def get_filter_by_id(filter_id): """ saved_filter = SavedFilter.query.filter(SavedFilter.filter_id == filter_id).first() if saved_filter: - if saved_filter.filter_is_private and saved_filter.created_by != iris_current_user.id: + if saved_filter.filter_is_private and saved_filter.created_by != user_identifier: return None return saved_filter From 44df5bdd2f3ac4bb60c3c29f841054cbed622b00 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:49:43 +0100 Subject: [PATCH 236/286] Added ruff rule E211 --- pyproject.toml | 2 +- source/app/blueprints/rest/v2/alerts_filters.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b84ec333d..8dcd853c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E20", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET501", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] +select = ["ARG003", "ARG005", "B00", "E101", "E20", "E211", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET501", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 1053e3eed..fe8983440 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -48,7 +48,7 @@ def _load(self, request_data, **kwargs): def create(self): request_data = request.get_json() - request_data ['created_by'] = iris_current_user.id + request_data['created_by'] = iris_current_user.id try: new_saved_filter = self._load(request_data) From 530e4e4d6e56cecb351a981dbb679d4a5c6eaa28 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:52:43 +0100 Subject: [PATCH 237/286] Added ruff rule E226 --- pyproject.toml | 2 +- source/app/blueprints/rest/manage/manage_customers_routes.py | 4 ++-- source/app/iris_engine/access_control/ldap_handler.py | 4 ++-- source/app/iris_engine/demo_builder.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8dcd853c0..854bdbf8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E20", "E211", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET501", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] +select = ["ARG003", "ARG005", "B00", "E101", "E20", "E211", "E225", "E226", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET501", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index 1ef924d4b..e8a0de6f2 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -201,7 +201,7 @@ def get_customer_case_stats(client_id): st = cases_stats['cases_last_year'] et = cases_stats['cases_current_year'] - cases_stats['ratio_year'] = ((et - st)/(st)) * 100 + cases_stats['ratio_year'] = ((et - st) / (st)) * 100 if cases_stats['cases_last_month'] == 0: st = 1 @@ -210,7 +210,7 @@ def get_customer_case_stats(client_id): st = cases_stats['cases_last_month'] et = cases_stats['cases_current_month'] - cases_stats['ratio_month'] = ((et - st)/(st)) * 100 + cases_stats['ratio_month'] = ((et - st) / (st)) * 100 if (case.close_date is not None) and (case.open_date is not None): cases_stats['average_case_duration'] += (case.close_date - case.open_date).days diff --git a/source/app/iris_engine/access_control/ldap_handler.py b/source/app/iris_engine/access_control/ldap_handler.py index ea2f4cd4e..0498359ba 100644 --- a/source/app/iris_engine/access_control/ldap_handler.py +++ b/source/app/iris_engine/access_control/ldap_handler.py @@ -35,7 +35,7 @@ def _get_unique_identifier(user_login): if app.config.get('LDAP_AUTHENTICATION_TYPE').lower() == 'ntlm': - return user_login[user_login.find('\\')+1:] + return user_login[user_login.find('\\') + 1:] return user_login @@ -83,7 +83,7 @@ def ldap_authenticate(ldap_user_name, ldap_user_pwd): """ if app.config.get('LDAP_AUTHENTICATION_TYPE').lower() != 'ntlm': ldap_user_name = conv.escape_filter_chars(ldap_user_name) - ldap_user = f"{app.config.get('LDAP_USER_PREFIX')}{ldap_user_name.strip()}{ ','+app.config.get('LDAP_USER_SUFFIX') if app.config.get('LDAP_USER_SUFFIX') else ''}" + ldap_user = f"{app.config.get('LDAP_USER_PREFIX')}{ldap_user_name.strip()}{ ',' + app.config.get('LDAP_USER_SUFFIX') if app.config.get('LDAP_USER_SUFFIX') else ''}" else: ldap_user = f"{ldap_user_name.strip()}" diff --git a/source/app/iris_engine/demo_builder.py b/source/app/iris_engine/demo_builder.py index 51bb69297..4bca39317 100644 --- a/source/app/iris_engine/demo_builder.py +++ b/source/app/iris_engine/demo_builder.py @@ -192,7 +192,7 @@ def create_demo_cases(users_data: dict = None, cases_count: int = 0, clients_cou access_level=CaseAccessLevel.full_access.value) cases_list = [] - for case_index in range(0, int(cases_count/2)): + for case_index in range(0, int(cases_count / 2)): if demo_case_exists(f"Restricted Case {case_index}", f"SOC-RSTRCT-{case_index}") is not None: log.info(f'Restricted case {case_index} already exists') continue From 7a975d45f31f463dee1e6cd5e3031c9df483591d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:54:28 +0100 Subject: [PATCH 238/286] Added ruff rule E272 --- pyproject.toml | 2 +- source/app/datamgmt/manage/manage_users_db.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 854bdbf8b..185376300 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E20", "E211", "E225", "E226", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET501", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] +select = ["ARG003", "ARG005", "B00", "E101", "E20", "E211", "E225", "E226", "E272", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET501", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] diff --git a/source/app/datamgmt/manage/manage_users_db.py b/source/app/datamgmt/manage/manage_users_db.py index c8f2f1d3c..c138a4491 100644 --- a/source/app/datamgmt/manage/manage_users_db.py +++ b/source/app/datamgmt/manage/manage_users_db.py @@ -395,7 +395,7 @@ def get_user_cases_fast(user_id): UserCaseEffectiveAccess.access_level != CaseAccessLevel.deny_all.value ).all() - return [c.case_id for c in user_cases] + return [c.case_id for c in user_cases] def remove_cases_access_from_user(user_id, cases_list): From 100a73de0638fee3bf9c65a29aebbede17d3525f Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:56:25 +0100 Subject: [PATCH 239/286] Added ruff rule E221 --- pyproject.toml | 2 +- source/app/datamgmt/case/case_events_db.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 185376300..a3d6ecd2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff.lint] preview = true -select = ["ARG003", "ARG005", "B00", "E101", "E20", "E211", "E225", "E226", "E272", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET501", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] +select = ["ARG003", "ARG005", "B00", "E101", "E20", "E211", "E221", "E225", "E226", "E272", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET501", "RET505", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] ignore = ["E402", "E711", "E712", "E721", "E722"] [tool.vulture] diff --git a/source/app/datamgmt/case/case_events_db.py b/source/app/datamgmt/case/case_events_db.py index 44b5c2182..35c7d97eb 100644 --- a/source/app/datamgmt/case/case_events_db.py +++ b/source/app/datamgmt/case/case_events_db.py @@ -400,7 +400,7 @@ def delete_event(user_identifier, event): def get_category_by_name(cat_name): return EventCategory.query.filter( - EventCategory.name == cat_name, + EventCategory.name == cat_name, ).first() From 2d23cc9185e3e55e11be114149414edafc01b628 Mon Sep 17 00:00:00 2001 From: ldbsi Date: Wed, 26 Nov 2025 13:43:34 +0100 Subject: [PATCH 240/286] [#887][FIX] Fixed issue with notes being overwritten with stale content --- ui/src/pages/case.notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/pages/case.notes.js b/ui/src/pages/case.notes.js index 50e7b0db1..cfd02f1ee 100644 --- a/ui/src/pages/case.notes.js +++ b/ui/src/pages/case.notes.js @@ -522,7 +522,7 @@ function save_note() { let currentNoteTitle = $('#currentNoteTitle').text() ? $('#currentNoteTitle').text() : $('#currentNoteTitleInput').val(); data_sent['note_title'] = currentNoteTitle; data_sent['csrf_token'] = $('#csrf_token').val(); - data_sent['note_content'] = $('#note_content').val(); + data_sent['note_content'] = note_editor ? note_editor.getValue() : "not found"; let ret = get_custom_attributes_fields(); let has_error = ret[0].length > 0; let attributes = ret[1]; From 714ce17d349c6d76fb7660afdd57bb36fdaad94c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asier=20Carre=C3=B1o?= Date: Mon, 1 Dec 2025 10:57:13 +0100 Subject: [PATCH 241/286] [ADD] Add traefik support --- deploy/kubernetes/charts/Chart.yaml | 2 +- .../kubernetes/charts/templates/ingress.yaml | 2 +- .../charts/templates/ingress_route.yaml | 28 +++++++++++++++++++ deploy/kubernetes/charts/values.yaml | 3 ++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 deploy/kubernetes/charts/templates/ingress_route.yaml diff --git a/deploy/kubernetes/charts/Chart.yaml b/deploy/kubernetes/charts/Chart.yaml index 9ea9f1319..c35db24ef 100644 --- a/deploy/kubernetes/charts/Chart.yaml +++ b/deploy/kubernetes/charts/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.2 +version: 0.2.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/deploy/kubernetes/charts/templates/ingress.yaml b/deploy/kubernetes/charts/templates/ingress.yaml index 9fc3e1768..b8f6656e5 100644 --- a/deploy/kubernetes/charts/templates/ingress.yaml +++ b/deploy/kubernetes/charts/templates/ingress.yaml @@ -1,4 +1,4 @@ -{{- if .Values.ingress.enabled }} +{{- if and .Values.ingress.enabled (not .Values.ingress.traefik.enabled) }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: diff --git a/deploy/kubernetes/charts/templates/ingress_route.yaml b/deploy/kubernetes/charts/templates/ingress_route.yaml new file mode 100644 index 000000000..4dfd2881e --- /dev/null +++ b/deploy/kubernetes/charts/templates/ingress_route.yaml @@ -0,0 +1,28 @@ +{{- if .Values.ingress.traefik.enabled }} +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: {{ .Values.ingress.name }} + {{- with .Values.ingress.customAnnotations }} + annotations: + {{- range $k, $v := . }} + {{- if not (hasPrefix "nginx.ingress.kubernetes.io" $k) }} + {{ $k }}: {{ $v }} + {{- end }} + {{- end }} + {{- end }} +spec: + entryPoints: + - {{ .Values.ingress.traefik.entryPoint | default "web" }} + routes: + {{- range $host := .Values.ingress.hosts }} + {{- range $path := $host.paths }} + - kind: Rule + match: > + Host(`{{ $host.host }}`) && PathPrefix(`{{ $path.path }}`) + services: + - name: {{ $.Values.irisapp.name }} + port: {{ $.Values.irisapp.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/deploy/kubernetes/charts/values.yaml b/deploy/kubernetes/charts/values.yaml index a7f8a2eae..c746b5cbc 100644 --- a/deploy/kubernetes/charts/values.yaml +++ b/deploy/kubernetes/charts/values.yaml @@ -224,6 +224,9 @@ ingress: enabled: true name: iris-ingress className: nginx + traefik: + enabled: false + entryPoint: web enableTls: false sslRedirect: false customAnnotations: From fb7cbb80e67b5cfc5bff9f5b5844d6faa71c5bbe Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Sun, 28 Dec 2025 14:04:44 +0100 Subject: [PATCH 242/286] Fixed path to store icon when updating asset-type --- source/app/schema/marshables.py | 4 +--- tests/iris.py | 3 +++ tests/rest_api.py | 8 +++++++ tests/tests_rest_asset_types.py | 39 +++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 tests/tests_rest_asset_types.py diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 5644f6bd5..f74e8c840 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -159,9 +159,7 @@ def store_icon(file): try: store_fullpath = os.path.join(app.config['ASSET_STORE_PATH'], filename) - show_fullpath = os.path.join(app.config['APP_PATH'], 'app', - app.config['ASSET_SHOW_PATH'].strip(os.path.sep), - filename) + show_fullpath = os.path.join(app.config['APP_PATH'], app.config['ASSET_SHOW_PATH'].strip(os.path.sep), filename) file.save(store_fullpath) os.symlink(store_fullpath, show_fullpath) diff --git a/tests/iris.py b/tests/iris.py index ec435d54c..fbca2ec37 100644 --- a/tests/iris.py +++ b/tests/iris.py @@ -54,6 +54,9 @@ def delete(self, path): def post_multipart_encoded_file(self, path, data, file_path): return self._api.post_multipart_encoded_file(path, data, file_path) + def post_multipart_encoded_files(self, path, data, files): + return self._api.post_multipart_encoded_files(path, data, files) + def _create_user(self, user_name): body = { 'user_name': user_name, diff --git a/tests/rest_api.py b/tests/rest_api.py index d523d6ccd..68d8a5f6f 100644 --- a/tests/rest_api.py +++ b/tests/rest_api.py @@ -82,3 +82,11 @@ def post_multipart_encoded_file(self, path, data, file_path): response_as_string = self._convert_response_to_string(response) print(f'POST {url} {data} {file_path} => {response_as_string}') return response + + def post_multipart_encoded_files(self, path, data, files): + headers = {'Authorization': f'Bearer {self._api_key}'} + url = self._build_url(path) + response = requests.post(url, headers=headers, data=data, files=files) + response_as_string = self._convert_response_to_string(response) + print(f'POST {url} {data} {list(files)} => {response_as_string}') + return response diff --git a/tests/tests_rest_asset_types.py b/tests/tests_rest_asset_types.py new file mode 100644 index 000000000..57060d9fb --- /dev/null +++ b/tests/tests_rest_asset_types.py @@ -0,0 +1,39 @@ +# 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 + +_FIRST_ASSET_TYPE_IDENTIFIER = 1 + + +class TestsRestAssetTypes(TestCase): + + def setUp(self) -> None: + self._subject = Iris() + + def tearDown(self): + self._subject.clear_database() + + def test_update_asset_type_should_return_200(self): + url = f'/manage/asset-type/update/{_FIRST_ASSET_TYPE_IDENTIFIER}' + data = {'asset_name': 'Account', 'asset_description': 'Generic Account'} + with open('data/img/desktop.png', 'rb') as file_not_compromised: + files = {'asset_icon_not_compromised': file_not_compromised, 'asset_icon_compromised': ('', '')} + response = self._subject.post_multipart_encoded_files(url, data, files) + self.assertEqual(200, response.status_code) From c7105318d7d5222c281229e653764d0a87d2e805 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Sun, 28 Dec 2025 14:27:22 +0100 Subject: [PATCH 243/286] Forgot to commit image --- tests/data/img/desktop.png | Bin 0 -> 383 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/data/img/desktop.png diff --git a/tests/data/img/desktop.png b/tests/data/img/desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..1d3d03a77d2d1b5c4ee9efeb9df6c31bcb866f14 GIT binary patch literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoY)RhkE)4%caKYZ?lNlHoO*~y3 zLn`LHy?c7qUNvv@$1&^&`8}BU3ShYXqQdelnaIoKf6Cj1ZTdN)8_x8_Eg_!a){z7*9I!85Rzp`}LGK9Ht9lQ8oIv+4Z O89ZJ6T-G@yGywpN{D+kQ literal 0 HcmV?d00001 From 91af0dd6650db00215e0db70d26fd0c2e8ae9736 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Sun, 28 Dec 2025 14:50:22 +0100 Subject: [PATCH 244/286] Created directory in which icons are stored --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 496966f01..03c2538b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -168,6 +168,10 @@ jobs: # 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 + # This is needed to have the path where icons are store when the volumes are mounted in the docker-compose + # It will map to /iriswebapp/static/assets/img/graph/ + # Could alternatively build the full ui to be mounted in the development server. Maybe will rather do that in the future + mkdir -p ui/dist/assets/img/graph/ docker compose --file docker-compose.dev.yml up --detach - name: Run tests working-directory: tests From 3fb2b4e731018d0989039668d4e87668dfcdf259 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Sun, 28 Dec 2025 23:52:49 +0100 Subject: [PATCH 245/286] Build the ui before API tests, rather than just creating the necessary directory --- .github/workflows/ci.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03c2538b3..b171f3458 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,15 +163,21 @@ jobs: docker load --input ${{ runner.temp }}/iriswebapp_app.tar - name: Check out iris uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: ui/package-lock.json + - name: Build ui to be mounted in development docker + working-directory: ui + run: | + npm ci + npm run build - 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 - # This is needed to have the path where icons are store when the volumes are mounted in the docker-compose - # It will map to /iriswebapp/static/assets/img/graph/ - # Could alternatively build the full ui to be mounted in the development server. Maybe will rather do that in the future - mkdir -p ui/dist/assets/img/graph/ docker compose --file docker-compose.dev.yml up --detach - name: Run tests working-directory: tests From 8922cb0f154cad9e16392b3bf2aaed326852c1c3 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Mon, 29 Dec 2025 10:03:36 +0100 Subject: [PATCH 246/286] Factored test driver --- tests/iris.py | 8 ++++++-- tests/rest_api.py | 9 --------- tests/tests_rest_reports.py | 20 +++++--------------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/tests/iris.py b/tests/iris.py index f2448ab7b..cd316b92e 100644 --- a/tests/iris.py +++ b/tests/iris.py @@ -69,8 +69,12 @@ def update(self, path, body): def delete(self, path): return self._api.delete(path) - def post_multipart_encoded_file(self, path, data, file_path): - return self._api.post_multipart_encoded_file(path, data, file_path) + def create_report(self, data, template_name): + file_path = f'data/report_templates/{template_name}' + with open(file_path, 'rb') as file: + files = {'file': file} + response = self._api.post_multipart_encoded_files('/manage/templates/add', data, files).json() + return response['data']['report_id'] def post_multipart_encoded_files(self, path, data, files): return self._api.post_multipart_encoded_files(path, data, files) diff --git a/tests/rest_api.py b/tests/rest_api.py index 68d8a5f6f..18206f5e4 100644 --- a/tests/rest_api.py +++ b/tests/rest_api.py @@ -74,15 +74,6 @@ def is_ready(self): except ConnectionError: return False - def post_multipart_encoded_file(self, path, data, file_path): - headers = {'Authorization': f'Bearer {self._api_key}'} - with open(file_path, 'rb') as file: - url = self._build_url(path) - response = requests.post(url, headers=headers, data=data, files={'file': file}) - response_as_string = self._convert_response_to_string(response) - print(f'POST {url} {data} {file_path} => {response_as_string}') - return response - def post_multipart_encoded_files(self, path, data, files): headers = {'Authorization': f'Bearer {self._api_key}'} url = self._build_url(path) diff --git a/tests/tests_rest_reports.py b/tests/tests_rest_reports.py index 7167bdaae..80ae7cbd9 100644 --- a/tests/tests_rest_reports.py +++ b/tests/tests_rest_reports.py @@ -38,9 +38,7 @@ def tearDown(self): def test_generate_docx_report__in_safe_mode_should_return_200(self): data = {'report_name': 'name', 'report_type': 1, 'report_language': 1, 'report_description': 'description', 'report_name_format': 'report_name_format'} - response = self._subject.post_multipart_encoded_file('/manage/templates/add', data, - 'data/report_templates/empty.docx').json() - report_identifier = response['data']['report_id'] + report_identifier = self._subject.create_report(data, 'empty.docx') case_identifier = self._subject.create_dummy_case() response = self._subject.get(f'/case/report/generate-investigation/{report_identifier}', {'cid': case_identifier, 'safe': True}) @@ -49,9 +47,7 @@ def test_generate_docx_report__in_safe_mode_should_return_200(self): def test_generate_docx_report_should_render_variable_case_for_customer(self): data = {'report_name': 'name', 'report_type': 1, 'report_language': 1, 'report_description': 'description', 'report_name_format': 'report_name_format'} - response = self._subject.post_multipart_encoded_file('/manage/templates/add', data, - 'data/report_templates/variable_case_for_customer.docx').json() - report_identifier = response['data']['report_id'] + report_identifier = self._subject.create_report(data,'variable_case_for_customer.docx') case_identifier = self._subject.create_dummy_case() response = self._subject.get(f'/case/report/generate-investigation/{report_identifier}', {'cid': case_identifier, 'safe': True}) @@ -62,9 +58,7 @@ def test_generate_docx_report_should_render_variable_case_for_customer(self): def test_generate_md_report_should_render_variable_case_name(self): data = {'report_name': 'name', 'report_type': 1, 'report_language': 1, 'report_description': 'description', 'report_name_format': 'report_name_format'} - response = self._subject.post_multipart_encoded_file('/manage/templates/add', data, - 'data/report_templates/variable_case_name.md').json() - report_identifier = response['data']['report_id'] + report_identifier = self._subject.create_report(data,'variable_case_name.md') case_identifier = self._subject.create_dummy_case() response = self._subject.get(f'/case/report/generate-investigation/{report_identifier}', {'cid': case_identifier, 'safe': True}) @@ -73,9 +67,7 @@ def test_generate_md_report_should_render_variable_case_name(self): def test_generate_md_report_should_render_variable_case_for_customer(self): data = {'report_name': 'name', 'report_type': 1, 'report_language': 1, 'report_description': 'description', 'report_name_format': 'report_name_format'} - response = self._subject.post_multipart_encoded_file('/manage/templates/add', data, - 'data/report_templates/variable_case_for_customer.md').json() - report_identifier = response['data']['report_id'] + report_identifier = self._subject.create_report(data,'variable_case_for_customer.md') case_identifier = self._subject.create_dummy_case() response = self._subject.get(f'/case/report/generate-investigation/{report_identifier}', {'cid': case_identifier, 'safe': True}) @@ -84,9 +76,7 @@ def test_generate_md_report_should_render_variable_case_for_customer(self): def test_generate_md_activities_report_should_render_variable_case_for_customer_when(self): data = {'report_name': 'name', 'report_type': 2, 'report_language': 1, 'report_description': 'description', 'report_name_format': 'report_name_format'} - response = self._subject.post_multipart_encoded_file('/manage/templates/add', data, - 'data/report_templates/variable_case_for_customer.md').json() - report_identifier = response['data']['report_id'] + report_identifier = self._subject.create_report(data,'variable_case_for_customer.md') case_identifier = self._subject.create_dummy_case() response = self._subject.get(f'/case/report/generate-activities/{report_identifier}', {'cid': case_identifier, 'safe': True}) From fb03cca6a38041350e179c1c750756532c30193d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Mon, 29 Dec 2025 10:17:08 +0100 Subject: [PATCH 247/286] Fixed ruff warnings --- tests/tests_rest_reports.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/tests_rest_reports.py b/tests/tests_rest_reports.py index 80ae7cbd9..8b9e07794 100644 --- a/tests/tests_rest_reports.py +++ b/tests/tests_rest_reports.py @@ -47,7 +47,7 @@ def test_generate_docx_report__in_safe_mode_should_return_200(self): def test_generate_docx_report_should_render_variable_case_for_customer(self): data = {'report_name': 'name', 'report_type': 1, 'report_language': 1, 'report_description': 'description', 'report_name_format': 'report_name_format'} - report_identifier = self._subject.create_report(data,'variable_case_for_customer.docx') + report_identifier = self._subject.create_report(data, 'variable_case_for_customer.docx') case_identifier = self._subject.create_dummy_case() response = self._subject.get(f'/case/report/generate-investigation/{report_identifier}', {'cid': case_identifier, 'safe': True}) @@ -58,7 +58,7 @@ def test_generate_docx_report_should_render_variable_case_for_customer(self): def test_generate_md_report_should_render_variable_case_name(self): data = {'report_name': 'name', 'report_type': 1, 'report_language': 1, 'report_description': 'description', 'report_name_format': 'report_name_format'} - report_identifier = self._subject.create_report(data,'variable_case_name.md') + report_identifier = self._subject.create_report(data, 'variable_case_name.md') case_identifier = self._subject.create_dummy_case() response = self._subject.get(f'/case/report/generate-investigation/{report_identifier}', {'cid': case_identifier, 'safe': True}) @@ -67,7 +67,7 @@ def test_generate_md_report_should_render_variable_case_name(self): def test_generate_md_report_should_render_variable_case_for_customer(self): data = {'report_name': 'name', 'report_type': 1, 'report_language': 1, 'report_description': 'description', 'report_name_format': 'report_name_format'} - report_identifier = self._subject.create_report(data,'variable_case_for_customer.md') + report_identifier = self._subject.create_report(data, 'variable_case_for_customer.md') case_identifier = self._subject.create_dummy_case() response = self._subject.get(f'/case/report/generate-investigation/{report_identifier}', {'cid': case_identifier, 'safe': True}) @@ -76,7 +76,7 @@ def test_generate_md_report_should_render_variable_case_for_customer(self): def test_generate_md_activities_report_should_render_variable_case_for_customer_when(self): data = {'report_name': 'name', 'report_type': 2, 'report_language': 1, 'report_description': 'description', 'report_name_format': 'report_name_format'} - report_identifier = self._subject.create_report(data,'variable_case_for_customer.md') + report_identifier = self._subject.create_report(data, 'variable_case_for_customer.md') case_identifier = self._subject.create_dummy_case() response = self._subject.get(f'/case/report/generate-activities/{report_identifier}', {'cid': case_identifier, 'safe': True}) From 3c0e5812663c567527190c0d1b0aa2644e860209 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Mon, 29 Dec 2025 10:17:41 +0100 Subject: [PATCH 248/286] Removed most probably unused png files --- .../static/assets/css/img/network/downArrow.png | Bin 4460 -> 0 bytes .../static/assets/css/img/network/leftArrow.png | Bin 4531 -> 0 bytes .../app/static/assets/css/img/network/minus.png | Bin 4147 -> 0 bytes .../app/static/assets/css/img/network/plus.png | Bin 4341 -> 0 bytes .../static/assets/css/img/network/rightArrow.png | Bin 4514 -> 0 bytes .../static/assets/css/img/network/upArrow.png | Bin 4461 -> 0 bytes .../assets/css/img/network/zoomExtends.png | Bin 4464 -> 0 bytes 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 source/app/static/assets/css/img/network/downArrow.png delete mode 100644 source/app/static/assets/css/img/network/leftArrow.png delete mode 100644 source/app/static/assets/css/img/network/minus.png delete mode 100644 source/app/static/assets/css/img/network/plus.png delete mode 100644 source/app/static/assets/css/img/network/rightArrow.png delete mode 100644 source/app/static/assets/css/img/network/upArrow.png delete mode 100644 source/app/static/assets/css/img/network/zoomExtends.png diff --git a/source/app/static/assets/css/img/network/downArrow.png b/source/app/static/assets/css/img/network/downArrow.png deleted file mode 100644 index e77d5e6d4157b12a5b2fa08a9fc138f0ab9eb4e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4460 zcmV-y5tHtTP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000J;NklFiJeI@Lq_ZL zBmL0S$Rv}rA?cV&Fjh^|PKHr3HRI&NhS;c`jEONpgn&UnEnlJ_Am1#z!h)de+aHT0 z;;vxtzxUqfp68x(?z!iAiJ1%?j48rOmnIvi>lsvt1`y~H9SSnw0-F?A>H>~kT7T;_ z2z@$}yyt2jBz7^jxrDogfG!go#BG@q+l2yBCJhqU{@?P^y~u;>zKLUsunAxxl$+4v zAJ>@{w5l~Vcy)K2afu_uH*6FDdd4~pb&e|ISzAfSS=;G|E6#>6p}?iPK*;6`j}5*z zV`9sv@*1iCNeEb7BzuaQk zab>E(3fniP*Xy>gXm&P+Dkwzy#dN(8x4Xn@T4|e#J$UtRmUo(Vu5lCTyDGAKa&NDx zJ94U!PX)ZZXzz(lQGaaqL+c{2Rhr9uUbTH(fz-OdUCL>sUb0%QcQYj`Q{uNUuKe*#5q@>@~6 zyVF2SvD>0CML4zINKer$8oZ766oJ5UOb}+Y=9Os@14L z6QAACL5V(!OaO?Po!GhRp$*fPj5ftXf-@ps9+cG=<+q?yQN!v}(V0TZdI(_+%4(fS znYcavo=6iPs2LDXT((wHplOEnLNc8otU+1bGbR2Zk)j5H%#3(;O1FZ7P^D=gVn{)} zaf!p*hVSeprnCLUp~JU^d^O&gQ-S~mZ(n*f*-tZ$EelNRN)AZ1d9o8dJJCT32*hea z2_yv%O}xFHNS~{E`UyY5T2-HZq zXQ>|U-^%L0{#HMI%Gq&fFKlZj1T)(4xEXvag>L8B6gotKmZza3{WJb zZ>-ZW_d(0ZRNR>BrV=XnD#Q58@p~(#uD`e-e{$}Ph8LHEf*Y-##P+Fow*7Mb&o?@Sf8;zUJ3a)7nV6XuGu-Y5_d(kKuq zBLtNLWg(ux89|TsrkXPAb$wj^-t>C9H{t^=d(r{OvcQb`)WEDBPng=l3n6rihB6^} zTtHOXN+P@&eKp}9CFTdh>}?~>b3VED�EM>-Bgb&Rym2{we-b7Oawc(c4k4MzLw~<#m(JZ zB@~qQpIqE{{i3mPsMc88e=1BEyGQ-tL@*0+ivy;CK*#2o{spHM{iH| z9Gh3v@&0O|0;q5KVBKexcPNl+?>no)bBumM$tbrKM+f?v`xeex?C|i=cb@7#9^EtA z5$w2SHyX8op_k&`I`M#daNHB=p|*k_yW6PjI+$`i->cYLaVm1JK(;C>Y@EBf^f#6l z?()Qbv){M8za*3ZtU)Wg0(?wkQ!yX57bc$QK9cU1y5sxad*s-*Y2|I!*Oq)mpB)0i zYRYVQe9lv~R#WDTw&)JQGgg2>oc^DT$x>Wy(Xl7@WQ;F(atb;$0A5>*}wG2f&2c2 z$oSY~4e8jS)d-9lf|GzL(4{)wDWd$ieC77;J yvyc*~sDYFLfm~LiO6lU!`6KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000KwNkldg1pcz&L1duC-Js%T0xva#KOj<-U4vF6^DUSL1S;K9~U_5b@Tt^Ey`FJT68-Kj(K@lGiW+Yop&i{&M7B z>u{Hrrk>wENpk7_%3KsepyYr|SJ#$ZeaF5B4$H*~2y7$)ydJydzbissZp1xWoaYR) zr;}ILnXVIBCMk9DGIxE;E^Flt@IDA{CpyalSf*WF{QI!=4Y*GhtxV|d8|t&wSgQgB z@#5q~mcP{GKo$TH`b)e-ObgR|7SwN!-(5alu)>aGKKq)pwUy>GY7#vBzz>23#+sk( zJ;zvUt#WO-rdRj6C@uPrEF}aP6n=>80I*$`_r)CFMYnyuHoJ{qty-Q|YC083B)}jr z32aH&yFbx0#WWWDOxJ1kYv)%!4gjZSUt|u=!z=&@^N2Bw1>M@PA$m!1@{)2>exwsC z?CHK4)hRx6YXG3kRNyz>DJ6T)?hJ8_FbI;6elm2HM8p|jK<7PMe=CQhZJ+z^y}B*F zq4%3W0wcsNqIGS=+ePu7Gt6(*tky~Fglj|niU~e8y?&t4Uy__NBqB;706Op4?Ke*{ z4R$ErsD3WxD|10O5yMO2)B9A=^QBJ(KYQtVN)SVkgu{li~d8RGLYE00g;& zv^}R?R~YZ9GsSr%wI4C$07Nq-5W5uSC9G9PN?NbE~6U7qzKuUDT$sv5xo0#k$|N*;@SoK&?>QB$5C? zB@rFTJ`l4 zljms`pL-#EeTBPF4&5AMWBng>r#t==&m_Ra*HMD_dO;>)1h@pZIj*qNS#oT~%!2p= z6Qj20obua%-U`3Ly~!u{>%3+)*ddGK#)ApV${t;s)AV7;sKt}O4X7ih1u@{ra0rqR z?4oUZCS+|P09@{?Q?g^MK>~9Lfz!^KOEmK}KW)1K_Z@sce$S~d+w$BhSDSB1V_6st@$GYEnN=KKDn?02EBRgQP;HP&j)NW)MfG?~tW0%G~K;Q_HU z@agYqbckC-?_&=B_{ZrKbE)l|tPa>Hd)AUZH!Mb}V$H8^5F~x|_FT=odX5DxSE&e;TyDw_?H}&5C3&S8?*{s5b9T&u);;M=3>o&V|4PbRbK7PQ z0Q4VqAAN9zmn)N$T4y~MSZ}R%bC*2&Cr=9Qp9rwZQs(oq{+~&urqc~z)-T=uB#snXP0tAmX; zgTJF<5r}Bz&S?iexZ_`#EE!yqAM+Bi*rZ^^hz?8Okviy2zOm@nflB6pf3;3=5e*o9jafR{A Re)#|Z002ovPDHLkV1iMdkGuc? diff --git a/source/app/static/assets/css/img/network/minus.png b/source/app/static/assets/css/img/network/minus.png deleted file mode 100644 index 30698076b9ac59f0880752ddc13af3f6bf062403..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4147 zcmV-35X|q1P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000GENkl3{XTl>HW%Jt}hQ9w9=fGBB#D5Nr`HCj2%^s65j<=9N+ zV3PL3QpRpxvb3q0hEXfcvY5#6Hcpz3fEXGXVqCmn9+1P$0|x{;jGf(&eGX-rqQd)c z@3q$Vti9G=@AoZYFw()eB0S>OWFd7ug9@<#g59D+K|0*vB?Xqbfddy8+$;g1-z_BX z*qRTCpN!>h;btMAi)Vlsk?9#O6p%6ykU-vV<*qHrhx?wvaYe`hxC6=nT1O_@XN1nN zO&^`rk{CF}Y4Hhn0YGcN-M7YhHt>w2)N;mA5?S9<7cLaIbvFokzUuMr&jt;)elP1t z{Y@c|VVPf-J^q#IIhH5d2D2;0ZHGtiu@xkpynbwgP(bjlv-2Hm?h3XR$rg&9D+Hd6 zeyM!zq>n3cA6j3#Dy6Vt`*e^XWoyNwZOi}Ju5N*TCmTf5^Mt_4i5rftNO-3f_o+tPlk(wk|JQ7PsaChDZ|wr*!t*>~U3hS4JqxqV$E% zx2p@G3joCWA{PQ=jGkY&I`M<^7-HqduRT8_G^>^r*v(P703gso5pE`c?C53H7$!Y4 zHm6#n22D(xp@R~;AF}`;E+E-HXUxL3;R2_J&TfzKpI}$ikaawEmXNXlLZpSxwqcl+ z5;WaL3N+1kfktvB0i=b_Zt-?F?Kly#zGhWsLvMYU*A5jml_CkQ1At2&NK~mrprx(1T=Jm{mzAIrAH2@U~ zgbErp2o(r|1{$OSbwEKu6BA5gf)Wi)e>8-Ba{1j%Z`{~`WT$7s@tRPANx>M8_X5E0 zy%)ojm~eV-bHRfw?NeoGOIut`Vcuu6kJ+UeD;KO8Gmu*bC~5F+r$2d+&wP^G-b9 zDv0#6Hoq9NveX;5zNa?O_$B3n29Z;QWL9*XvdptQ-3$QJf-)Unr61on)W@m1yUOAz z7)}Yvg94($Q5rei=eVxBGEz~4(s@X#=G`dPNnGi@6n?Vph9^v@lE6Qq~QV|ZQYk7R8Ww-eCE52?9}Pl3IJyuB?%v0SeHE1(A~CzPxCMl|9iZ{W$7Wweh5A&IS?^l?APvDob)J#&IBsf!} xW+f$983jRyKrSm$Wps0YbZT1Zoq%5bH2}dmt(E>J5*z>k002ovPDHLkV1iae%XI(% diff --git a/source/app/static/assets/css/img/network/plus.png b/source/app/static/assets/css/img/network/plus.png deleted file mode 100644 index f7ab2a334e566b6deb9657d7d8370f591bfb81bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4341 zcmV!P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000IcNklZ0y)aS=B_F>$~A z_;!gITGr7pim8wdAki-d6r|q|UQ%GaA2{5x z_NzJ&Mktef;As^kcOJI-g}a1+A%P2GT4vz7P(aG4K?0SZH;(K^75onkoO1vh0oFj7 zfR5Na_mboluEkSVcIU+}7_dg9jsQT>`!hTn<*3!{LR@l-j_lG zI}XThicuj1p0xk8@#VR1oWr+}*V?wvukG5q7$iv9)3myG>!iSrf5{Hf#&!bOHfP7j zTO8Xu#~VE5J(2WkYh}rm!HYHpg{+ty&+~b^oP|kCz2kNFUHh}+jZ42>Wda#HPp$6R z9z0WHa;oq;74Yl1yN_&~@vAEndGgGe^`%z_J8X&y6b0Xxw_{sW$Bx){wV#&^+E+rT~oH-bXEW$I}#NTpm^$<_80R0aN)uH6DaD@ zcSSwseIlvIefh$Y{2K&!jLK2?>32+twlxPT5$YOh9`okFhI#Zgk=Db6PNE^F>PH)%II+(5)2FYk>urlGh$}C6*X2K&t4{^tc4JT$;(~g z(8Tok8z3zfs4##7#pWUfT1T$cB#Q{3FnM|R{gipFb$dzIP^T@}4uf8r&uUDLVHhZ_ zn^O(|40LIsLVC2_^NiyK=XVkcy#O#juAuuvcP&6J5fC|~V0Qe1fuOM+tn~1a-6H@&`zPfVo;IiKJj!p!Wj6 z)u9fX5(`eP@>GWk-_d0)lj{7izOcM|VQX*1Dv>CZXZGM4_xuB>=C(r*QGSPgb zfB-ZM^i#yzch?=67UdWQ0DEk%$2Hh#D?XN9uBbpNC@Kg{Dj6V-F8jP10MC?34jLk%K)fFEZ=xF z3To0yQ3AdSGNK)xr)O+)26a0J+v3GMyQm@3KuAebe}lEu`j`g*7A2H;|MAH2_a_S9 zqjmGj$EHQW0PtQ>W5{}F@ikim3qymD{6Ii7d7T+y<;|S2lXkKPNxSb{hN6Zt_@OkH zesR(uab@UI>LAsyYE-5Udms|Gd6;b|?em zK}-gOmXCYhpDk2Skjg4n`OWEc+q(w`41IpFr>%txT^Ed4>Zjph>>dv zdq&4%?Rwl3a5Dhxzp=OI;LW!)Co_^3W%rPR#~X^v%RXIN4ieL&vOJUN|EK40=8>L* zMT#0Ex74n$y*Fk9x$*Yk+4K$ZF+wSB^q$O4j7aI5HD%twcth#Y_TG-cmZUGfyb~*; zkQSBY*=(tgYQ^7RFxR00000NkvXXu0mjfp~Nvu diff --git a/source/app/static/assets/css/img/network/rightArrow.png b/source/app/static/assets/css/img/network/rightArrow.png deleted file mode 100644 index c3a209d8b0a58355305aae03b5520a1e070d8eab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4514 zcmV;T5nb+yP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000KfNkl=)-fx%60=o-~3&uscqkw>si!2o>5sjwfBtw!WZAfjL zc9OQKiI-lcwX`)=Nz$5(Gf5aF32KstNSJ7-6H+aRARr>aa1((n_uIm}@SqLUBtRuL;Pkc4Q|CdD zZRa&rFVRCI%7DCM00zmt_-&kPh1&g9CH14WKLtJJ8={ zMb}!7^`=IOOC{(w^emE`hb|3X@FP&%(lnK-BYU*q|G=!fr|rY{h1`=eNVfZIy_jW-uYfBhj?bbsL`=QA#Ai zRcI#v8unJ{p@`!rmpcb`n!oPTob1RpK3kRg;Mu->?>RP`iHIRdPMakniYEXu9*I4( zBQh7dAE?`t&^FfWHB$}+N%n(J42dMiH+z|k)WieYasZ;W3_>6gW2HLEG6OzX_v?hV@fI&)F$-m4FbPN` zfk}WsAn;8idVJ|K*N?{J=cIT&T%{CFw(8+aLC;=(B-K1Qpag&bw=fG4gF!(O6KjbW ziygHW09wbcc?njaJh?mbPLGdTsQ6}#U#Mq-cZcjR_EiOSvjaM_zT|)^Sb>?g8P%OXk>*Z6wmdmqhguIABtHG@ImMhVQN1Wu?NEY)w&KimxfYcvVw_u|fdGT$^F zZrT_5&+d~k>{yO3tyS90=2whuW35XBG5lRZ+P5xwvMRwNx$DNbT5FBoVHip!BKnkN z+FfZb_5rYt-`!&XH0^`b1>B@@(=fatoqcm~#ha=zrXg+AwP?@fC zyxm)nK5VnL6Wuxn?RL%qpmNP8?pf(4xAIPfpi=jtumykRZMIwGZzJDmAd{A*HxBp3q8@6xsizh@XOiJpJvSIx!SXb3w(a8 z)eN^PbAq3j_tK~qEGFd*X#j+>D{3-%eT3!m?UGp z^$SCTwZ_F&avQsE(Xz3H0IMzKp2tmZMwgijqwMw;FK4Z}8G1`YpIo@&83G*Lvif;AFp(X}sC3Zn#=sZ>?F@JJID$AlPj!Z9lR)@8}&r zVbW!AX;Ih<#NrhtD*@2#6*+T13r8IXX!bjScKO`D`tRwKe&KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000Jw01_u;C^6LsAgGY+3 z+aI!BFcKEsfA^ll$TcX#&?!Z8JuegHH55CK!9C-&=moy_XCGJ z77o^cpo|Aek6bMWPk9uZhHa~!>{;3;H9k# zJiEdsHh;n!h?L(j0ma7o?MqUBVViCIK~FfRQr+`;>|Xnh?7F@$GME?y`u*AY@^8l_ zHXq>a#7Y?x@QUfpru9=lY#Hm*=j&FV==wAs0OlHht3LgOR>iXMO4RcXE_~fL1lJe}8eq{$S+0um92f z;gyYZ14PP}Q}eplj;x6X&QxGC31HpS?MF)|ulgofDp#F*wd87l+mn{)JbPTklWxa= zJ+`CY7O^m4@%6F3Il2OuPBpQwvF{5rlT&Y|Fv9`p=Qm{yIGqX-pSb1AhCMrYgcW?v95o|2a8#hwDj7`NpI9M2}mSW zF4`CPSO$PJ4a&ekaqRr|UuSK*5K1_^u2tj`7|+GO&}z}na9gx9-1FmKXcZ)3kNwZN zq2kJ>tiMpGpVLOfa2Qg`0>lc4nYI#uC8ia&P{G?Sud{S`E+*>K`u;T;@0}TWzczE@ znHaU9|7w3*;-9W;w1kR(X;P_8A_)?!wn71M+95m(0O`>d$LzT0gZ?sJzn4#R9m*Cg zq13dpQLEAoj;uur?cl2^Z#GJ-!dE^2$@coa91PT+tusev-FhYYjfa%}a?(#bO_AxhT)tjixot&BD4ZN!ft`pUNt%UB zL`4LGRX?jMm~%tVaYLj0Ri=v7{#BV9>O+CPmA1Z~S>oObVld(aGqw~821eio2 zgOENU!@({%mU+&PerMsd^x%T z=QOP~=#j|2UX4To09=wF?qFi$r90=f!H5Iog>s&*PzHb_&iz&ut47zD0;&Ib+Pmk* z>s+08+q4XZ0$gS=HxL-jt_EW`ZBLL0@=Re3GB!`Ekpe?Q0~65^2BBH5O%5k`xX1qQ z$cVwjVG*}`T9O4x5DI<9t>5pjR*3M;y(g`5+s3$mRBvV8hT&?x%u(kvq?*|kxX3NBv+@!s13cD z6P|vkj3zx{>gl*^o7n%|ohW93#E4YqGE-S?oF-u)80nsGcRUvmA8*;6e`IsWVXx%N z)a?u|6C|)GacTX}%ucd1~pzNG7Sc z&2v7f-P0O1L5jXNDI(=wID{?kQ?Y-y|0TakJ~=(02W+XFacFhe1O42SS<49Ykstvg zhUtlPM~>EdeX1_EYjR}9ofx&j3lLnsUX6XAGy2k<7Gs;IHKp6@NFWdbV=XQJDEHvb z$G*apE8vD|%P)z=`)XDL!wSB?DF_my)obKEfz~AF<$h33<&S@b8M}cq_4y@4{6hj2 zNhFzxI3}P}b}{&>U{OzgI%LYX);@}G#Qr+~{b?&$J^k9A00000NkvXXu0mjfzMf>g diff --git a/source/app/static/assets/css/img/network/zoomExtends.png b/source/app/static/assets/css/img/network/zoomExtends.png deleted file mode 100644 index 74595c6358448fbdcfbd62629084841ca7e8b8a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4464 zcmV-$5s&VPP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000J?Nklmr?3xa&Z(!1P+L?CZ z)HFk8I!$dt<2X~bGfw*YAl1VaY`=w2guz-01!Jr_ZsL({y1u8G%uE2<* zWuJa{cDHB*w@LpqJI~ABdw%zvd(XM|VQFb8LMF#)^&|!!GbP|t${2X*$hc+8@H}HdkBLcy%ASf3Cx)=r^ zgbqMK03cbTB$MI*@It72nGPHhNKg+!0U(GVM8p7xVImUo7yxSBM|KjZh5{f!+M?A% zd5P}+yo8M-tK)OWm&K+`6Ts;7Q0#??uJ}{_=HyfU=G05q`%;8b9@K;7q&!97bws2` z>}z*JN#dyM@Bs++k$@w)sPD;#esO+d^0q6uhf)`=ypg(aipm6{jwG@M>|X{Sbsc_@3S~mzg*7iWKA-*jUVKG46AMQk(BiJO z2aczc%o9qSP#UrZYz8B9TSAE9194>k~Hik^&{#E)jve?>a3NMBG-~Dbj@PL{p#T6fmRN?|%eF>RX8ZQ-Uc24y zBk8TJt@RwZ`AlH!_OMAHAdR0>Y6Jn-lz+e{vh$GdjzS|0#!*j`?Odn`RJ zYsfcsF=a-dDG&l%AcQT!)jxaW;qcmQHedT=e-GF`e!|2MRH&d)gQ5ma7C=#fAgoQr zrqiyYfe;ko>wg!Uy`L0lvS5owVLn0F5?mv5g>UTku*vIu6(=71SLJ+RE|fK;yam&o zt2}<)NbS&H0dl=YFoP82EXf_8tAnBj359k(DX7STx|cE*uNepFr ztu68L&A}8!1%Winw&rJb0m}r$p9iXPX4mR4G&eUl>P^C8vG|-$r#A$Tyl)tNFUKgY zs?6Q_!=QxztN?&h{^sn%pB=D-!2rWX@kICt(`*yD(exBDwCXU99TX{1^O zNpRo3eGn0Ru;G=b1X86$Ls{nUyicrn(uenUNVoYHmaP< Date: Sun, 11 Jan 2026 17:02:53 +0100 Subject: [PATCH 249/286] Update source/app/blueprints/rest/v2/alerts_filters.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- source/app/blueprints/rest/v2/alerts_filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 683a81d83..ee98fe76d 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -39,7 +39,7 @@ def _load(self, request_data): def create(self): request_data = request.get_json() - request_data ['created_by'] = iris_current_user.id + request_data['created_by'] = iris_current_user.id try: new_saved_filter = self._load(request_data) From 2380d76f1c2cc22772ec2084a05bfd77ab0ae23f Mon Sep 17 00:00:00 2001 From: whitekernel <74464599+whikernel@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:03:13 +0100 Subject: [PATCH 250/286] Update source/app/datamgmt/case/case_assets_db.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- source/app/datamgmt/case/case_assets_db.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index 2fb112602..c321a46e9 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -109,9 +109,11 @@ def get_assets_by_case(case_identifier): return CaseAssets.query.with_entities( CaseEventsAssets.event_id, CaseAssets.asset_name + ).join( + CaseEventsAssets.asset ).filter( - CaseEventsAssets.case_id == case_identifier, - ).join(CaseEventsAssets.asset).all() + CaseEventsAssets.case_id == case_identifier + ).all() def filter_assets(case_identifier, pagination_parameters: PaginationParameters, request_parameters: dict) -> Pagination: From 71fea0713c290044e38723591f5421844000956e Mon Sep 17 00:00:00 2001 From: whitekernel <74464599+whikernel@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:18:11 +0100 Subject: [PATCH 251/286] Update source/app/datamgmt/case/case_db.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- source/app/datamgmt/case/case_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index 766ab7363..1d0d3d55f 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -40,7 +40,7 @@ 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]: +def get_first_case_with_customer(customer_identifier: int) -> Optional[Cases]: case = Cases.query.filter( Cases.client_id == customer_identifier ).first() From add47a679a61b3691baaea3a91b896dd3db9172d Mon Sep 17 00:00:00 2001 From: whitekernel <74464599+whikernel@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:19:14 +0100 Subject: [PATCH 252/286] Update tests/tests_rest_customers.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- tests/tests_rest_customers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index 1615fa36f..957a5c09a 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -86,7 +86,7 @@ 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): + def test_get_customer_should_return_403_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'] From 045d098f44a4d0e1cc4bfeeba31abe4cfa48e84d Mon Sep 17 00:00:00 2001 From: whikernel Date: Mon, 12 Jan 2026 13:29:42 +0100 Subject: [PATCH 253/286] [DEL] Removed alerts filters --- .../app/blueprints/rest/v2/alerts_filters.py | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index b72c794fc..46edc6e47 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -37,11 +37,8 @@ from app.business.alerts_filters import alert_filter_update from app.business.alerts_filters import alert_filter_delete -from app.business.alerts_filters import alerts_filters_add - class AlertsFiltersOperations: - def __init__(self): self._schema = SavedFilterSchema() @@ -50,7 +47,7 @@ def _load(self, request_data, **kwargs): def create(self): request_data = request.get_json() - request_data['created_by'] = iris_current_user.id + request_data["created_by"] = iris_current_user.id try: new_saved_filter = self._load(request_data) @@ -58,7 +55,7 @@ def create(self): return response_api_created(self._schema.dump(new_saved_filter)) except ValidationError as e: - return response_api_error('Data error', e.messages) + return response_api_error("Data error", e.messages) except BusinessProcessingError as e: return response_api_error(e.get_message(), data=e.get_data()) @@ -79,12 +76,14 @@ def put(self, identifier): try: saved_filter = alert_filter_get(iris_current_user, identifier) - new_saved_filter = self._load(request_data, instance=saved_filter, partial=True) + 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) + return response_api_error("Data error", data=e.messages) except ObjectNotFoundError: return response_api_not_found() @@ -106,29 +105,31 @@ def delete(identifier): 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_blueprint = Blueprint( + "alerts_filters_rest_v2", __name__, url_prefix="/alerts-filters" +) alerts_filters_operations = AlertsFiltersOperations() -@alerts_filters_blueprint.post('') +@alerts_filters_blueprint.post("") @ac_api_requires() def create_alert_filter(): return alerts_filters_operations.create() -@alerts_filters_blueprint.get('/') +@alerts_filters_blueprint.get("/") @ac_api_requires() def get_alert_filter(identifier): return alerts_filters_operations.get(identifier) -@alerts_filters_blueprint.put('/') +@alerts_filters_blueprint.put("/") @ac_api_requires() def update_alert_filter(identifier): return alerts_filters_operations.put(identifier) -@alerts_filters_blueprint.delete('/') +@alerts_filters_blueprint.delete("/") @ac_api_requires() def delete_alert_filter(identifier): return alerts_filters_operations.delete(identifier) From 1cc4d0d1bfec12991cc3b359f8c047fa5f1f762f Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Tue, 13 Jan 2026 09:05:44 +0100 Subject: [PATCH 254/286] Added support for calling from a new Svelte UI. --- docker-compose.dev.yml | 2 +- docker/nginx/nginx-newui.conf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index b234c9975..d2b1ee3d1 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -108,7 +108,7 @@ services: working_dir: /app environment: - IRIS_SVELTEKIT_FRONTEND_DIR=${IRIS_SVELTEKIT_FRONTEND_DIR:-../iris-frontend} - - PUBLIC_EXTERNAL_API_URL=${PUBLIC_EXTERNAL_API_URL:-https://127.0.0.1} + - PUBLIC_EXTERNAL_API_URL=${PUBLIC_EXTERNAL_API_URL:-http://127.0.0.1:8000} - PUBLIC_INTERNAL_API_URL=http://${IRIS_UPSTREAM_SERVER}:${IRIS_UPSTREAM_PORT} - PUBLIC_USE_MOCK_API_DATA=false - ORIGIN=https://127.0.0.1 diff --git a/docker/nginx/nginx-newui.conf b/docker/nginx/nginx-newui.conf index 63fb519db..1f881df40 100644 --- a/docker/nginx/nginx-newui.conf +++ b/docker/nginx/nginx-newui.conf @@ -26,7 +26,7 @@ events { http { map $request_uri $csp_header { - default "default-src 'self' https://analytics.dfir-iris.org https://127.0.0.1 http://app:8000; script-src 'self' 'unsafe-inline' https://analytics.dfir-iris.org; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"; + default "default-src 'self' https://analytics.dfir-iris.org https://127.0.0.1 http://app:8000; script-src 'self' 'unsafe-inline' https://analytics.dfir-iris.org; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://127.0.0.1:8000;"; } include /etc/nginx/mime.types; @@ -171,4 +171,4 @@ http { root /usr/share/nginx/html; } } -} \ No newline at end of file +} From bd209a312430ed262e54cfbd71110865d58261ce Mon Sep 17 00:00:00 2001 From: whitekernel <74464599+whikernel@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:15:22 +0100 Subject: [PATCH 255/286] Revert "Added support for calling from a new Svelte UI." --- docker-compose.dev.yml | 2 +- docker/nginx/nginx-newui.conf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index d2b1ee3d1..b234c9975 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -108,7 +108,7 @@ services: working_dir: /app environment: - IRIS_SVELTEKIT_FRONTEND_DIR=${IRIS_SVELTEKIT_FRONTEND_DIR:-../iris-frontend} - - PUBLIC_EXTERNAL_API_URL=${PUBLIC_EXTERNAL_API_URL:-http://127.0.0.1:8000} + - PUBLIC_EXTERNAL_API_URL=${PUBLIC_EXTERNAL_API_URL:-https://127.0.0.1} - PUBLIC_INTERNAL_API_URL=http://${IRIS_UPSTREAM_SERVER}:${IRIS_UPSTREAM_PORT} - PUBLIC_USE_MOCK_API_DATA=false - ORIGIN=https://127.0.0.1 diff --git a/docker/nginx/nginx-newui.conf b/docker/nginx/nginx-newui.conf index 1f881df40..63fb519db 100644 --- a/docker/nginx/nginx-newui.conf +++ b/docker/nginx/nginx-newui.conf @@ -26,7 +26,7 @@ events { http { map $request_uri $csp_header { - default "default-src 'self' https://analytics.dfir-iris.org https://127.0.0.1 http://app:8000; script-src 'self' 'unsafe-inline' https://analytics.dfir-iris.org; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://127.0.0.1:8000;"; + default "default-src 'self' https://analytics.dfir-iris.org https://127.0.0.1 http://app:8000; script-src 'self' 'unsafe-inline' https://analytics.dfir-iris.org; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"; } include /etc/nginx/mime.types; @@ -171,4 +171,4 @@ http { root /usr/share/nginx/html; } } -} +} \ No newline at end of file From 2fabaff34ff9abcaadf5936082668e273516b0e1 Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 15 Jan 2026 10:54:48 +0100 Subject: [PATCH 256/286] [FIX] oidc user group being mandatory --- .../blueprints/pages/login/login_routes.py | 272 +++++++++++------- 1 file changed, 176 insertions(+), 96 deletions(-) diff --git a/source/app/blueprints/pages/login/login_routes.py b/source/app/blueprints/pages/login/login_routes.py index b5d21eafc..65702b389 100644 --- a/source/app/blueprints/pages/login/login_routes.py +++ b/source/app/blueprints/pages/login/login_routes.py @@ -52,11 +52,7 @@ from app.business.auth import generate_auth_tokens from app.schema.marshables import UserSchema -login_blueprint = Blueprint( - 'login', - __name__, - template_folder='templates' -) +login_blueprint = Blueprint("login", __name__, template_folder="templates") log = app.logger @@ -65,13 +61,20 @@ def _render_template_login(form, msg): - organisation_name = app.config.get('ORGANISATION_NAME') - login_banner = app.config.get('LOGIN_BANNER_TEXT') - ptfm_contact = app.config.get('LOGIN_PTFM_CONTACT') - auth_type = app.config.get('AUTHENTICATION_TYPE') - - return render_template('login.html', form=form, msg=msg, organisation_name=organisation_name, - login_banner=login_banner, ptfm_contact=ptfm_contact, auth_type=auth_type) + organisation_name = app.config.get("ORGANISATION_NAME") + login_banner = app.config.get("LOGIN_BANNER_TEXT") + ptfm_contact = app.config.get("LOGIN_PTFM_CONTACT") + auth_type = app.config.get("AUTHENTICATION_TYPE") + + return render_template( + "login.html", + form=form, + msg=msg, + organisation_name=organisation_name, + login_banner=login_banner, + ptfm_contact=ptfm_contact, + auth_type=auth_type, + ) def _validate_local_login(username, password): @@ -82,7 +85,11 @@ def _validate_local_login(username, password): if bc.check_password_hash(user.password, password): return user - track_activity(f'wrong login password for user \'{username}\' using local auth', ctx_less=True, display_in_ui=False) + track_activity( + f"wrong login password for user '{username}' using local auth", + ctx_less=True, + display_in_ui=False, + ) return None @@ -90,65 +97,82 @@ def _authenticate_ldap(form, username, password, local_fallback=True): try: user = validate_ldap_login(username, password, local_fallback) if user is None: - return _render_template_login(form, 'Wrong credentials. Please try again.') + return _render_template_login(form, "Wrong credentials. Please try again.") - user_data = UserSchema(exclude=['user_password', 'mfa_secrets', 'webauthn_credentials']).dump(user) + user_data = UserSchema( + exclude=["user_password", "mfa_secrets", "webauthn_credentials"] + ).dump(user) # Generate auth tokens for API access tokens = generate_auth_tokens(user) - user_data.update({'tokens': tokens}) + user_data.update({"tokens": tokens}) return wrap_login_user(user_data) except Exception as e: log.error(e.__str__()) - return _render_template_login(form, 'LDAP authentication unavailable. Check server logs') + return _render_template_login( + form, "LDAP authentication unavailable. Check server logs" + ) def _authenticate_password(form, username, password): user = retrieve_user_by_username(username) if not user or user.is_service_account: - return _render_template_login(form, 'Wrong credentials. Please try again.') + return _render_template_login(form, "Wrong credentials. Please try again.") if bc.check_password_hash(user.password, password): return wrap_login_user(user) - track_activity(f'wrong login password for user \'{username}\' using local auth', ctx_less=True, - display_in_ui=False) - return _render_template_login(form, 'Wrong credentials. Please try again.') + track_activity( + f"wrong login password for user '{username}' using local auth", + ctx_less=True, + display_in_ui=False, + ) + return _render_template_login(form, "Wrong credentials. Please try again.") # Authenticate user if app.config.get("AUTHENTICATION_TYPE") in ["local", "ldap", "oidc"]: - @login_blueprint.route('/login', methods=['GET', 'POST']) + + @login_blueprint.route("/login", methods=["GET", "POST"]) def login(): if iris_current_user.is_authenticated: - return redirect(url_for('index.index')) + return redirect(url_for("index.index")) - if is_authentication_oidc() and app.config.get('AUTHENTICATION_LOCAL_FALLBACK') is False: - return redirect(url_for('login.oidc_login')) + if ( + is_authentication_oidc() + and app.config.get("AUTHENTICATION_LOCAL_FALLBACK") is False + ): + return redirect(url_for("login.oidc_login")) form = LoginForm(request.form) # check if both http method is POST and form is valid on submit if not form.is_submitted() and not form.validate(): - return _render_template_login(form, None) # assign form data to variables - username = request.form.get('username', '', type=str) - password = request.form.get('password', '', type=str) + username = request.form.get("username", "", type=str) + password = request.form.get("password", "", type=str) if is_authentication_ldap() is True: - return _authenticate_ldap(form, username, password, app.config.get('AUTHENTICATION_LOCAL_FALLBACK')) + return _authenticate_ldap( + form, + username, + password, + app.config.get("AUTHENTICATION_LOCAL_FALLBACK"), + ) return _authenticate_password(form, username, password) + if is_authentication_oidc(): - @login_blueprint.route('/oidc-login') + + @login_blueprint.route("/oidc-login") def oidc_login(): if iris_current_user.is_authenticated: - return redirect(url_for('index.index')) + return redirect(url_for("index.index")) session["oidc_state"] = rndstr() session["oidc_nonce"] = rndstr() @@ -159,7 +183,7 @@ def oidc_login(): "scope": app.config.get("OIDC_SCOPES"), "nonce": session["oidc_nonce"], "redirect_uri": url_for("login.oidc_authorise", _external=True), - "state": session["oidc_state"] + "state": session["oidc_state"], } auth_req = oidc_client.construct_AuthorizationRequest(request_args=args) @@ -167,12 +191,14 @@ def oidc_login(): return redirect(login_url) + if is_authentication_oidc(): - @login_blueprint.route('/oidc-authorize') + @login_blueprint.route("/oidc-authorize") def oidc_authorise(): - auth_resp = oidc_client.parse_response(AuthorizationResponse, info=request.args, - sformat="dict") + auth_resp = oidc_client.parse_response( + AuthorizationResponse, info=request.args, sformat="dict" + ) if auth_resp["state"] != session["oidc_state"]: track_activity( @@ -186,7 +212,9 @@ def oidc_authorise(): "code": auth_resp["code"], } - access_token_resp = oidc_client.do_access_token_request(state=auth_resp["state"], request_args=args) + access_token_resp = oidc_client.do_access_token_request( + state=auth_resp["state"], request_args=args + ) # not all providers set email by default, use preferred_username where it's missing # Use the mapping from the configuration or default to email or preferred_username if not set @@ -195,23 +223,34 @@ def oidc_authorise(): usergroup_field = app.config.get("OIDC_MAPPING_USERGROUP") userroles_mapping_field = app.config.get("OIDC_MAPPING_ROLES") try: - if 'id_token' not in access_token_resp: - log.error('OIDC authentication failed: \'id_token\' not found in access token response') + if "id_token" not in access_token_resp: + log.error( + "OIDC authentication failed: 'id_token' not found in access token response" + ) track_activity( - 'OIDC authentication failed: missing id_token in response', + "OIDC authentication failed: missing id_token in response", ctx_less=True, display_in_ui=False, ) return redirect(url_for("login.login")) - user_login = access_token_resp['id_token'].get(username_field) or access_token_resp['id_token'].get(email_field) - user_name = access_token_resp['id_token'].get(email_field) or access_token_resp['id_token'].get(username_field) - user_group = access_token_resp['id_token'].get(usergroup_field) + user_login = access_token_resp["id_token"].get( + username_field + ) or access_token_resp["id_token"].get(email_field) + user_name = access_token_resp["id_token"].get( + email_field + ) or access_token_resp["id_token"].get(username_field) + if usergroup_field is not None: + user_group = access_token_resp["id_token"].get(usergroup_field) + else: + user_group = None if not user_login: - log.error('OIDC authentication failed: username or email not found in id_token') + log.error( + "OIDC authentication failed: username or email not found in id_token" + ) track_activity( - 'OIDC authentication failed: username or email not found in id_token', + "OIDC authentication failed: username or email not found in id_token", ctx_less=True, display_in_ui=False, ) @@ -223,61 +262,72 @@ def oidc_authorise(): ctx_less=True, display_in_ui=False, ) - return redirect(url_for('login.login')) + return redirect(url_for("login.login")) - user = get_user(user_login, 'user') + user = get_user(user_login, "user") if not user: - log.warning(f'OIDC user {user_login} not found in database') - if app.config.get('AUTHENTICATION_CREATE_USER_IF_NOT_EXIST') is False: - log.warning('Authentication is set to not create user if not exists') + log.warning(f"OIDC user {user_login} not found in database") + if app.config.get("AUTHENTICATION_CREATE_USER_IF_NOT_EXIST") is False: + log.warning("Authentication is set to not create user if not exists") track_activity( - f'OIDC user {user_login} not found in database', + f"OIDC user {user_login} not found in database", ctx_less=True, display_in_ui=False, ) - return response_error('User not found in IRIS', 404) + return response_error("User not found in IRIS", 404) - log.info(f'Creating OIDC user {user_login} in database') + log.info(f"Creating OIDC user {user_login} in database") track_activity( - f'Creating OIDC user {user_login} in database', + f"Creating OIDC user {user_login} in database", ctx_less=True, display_in_ui=False, ) # generate random password - password = ''.join(random.choices(string.printable[:-6], k=16)) + password = "".join(random.choices(string.printable[:-6], k=16)) user = create_user( - user_name, - user_login, - bc.generate_password_hash(password.encode('utf8')).decode('utf8'), - user_login, - True, - user_is_service_account=False - ) + user_name, + user_login, + bc.generate_password_hash(password.encode("utf8")).decode("utf8"), + user_login, + True, + user_is_service_account=False, + ) + + if user and not user.active: + return response_error("User not active in IRIS", 403) + + if user and (not user_group) and userroles_mapping_field: + return response_error( + "Required user group information missing in OIDC response", 403 + ) - if user and not user.active or (user and not user_group): - return response_error("User not active or has no role in IRIS", 403) if user_group: if not userroles_mapping_field: groups_list = get_groups_list() - group_name_to_id = {group.group_name: group.group_id for group in groups_list} + group_name_to_id = { + group.group_name: group.group_id for group in groups_list + } else: group_name_to_id = json.loads(userroles_mapping_field) - new_user_group = [group_name_to_id[group_name] for group_name in user_group if group_name in group_name_to_id] + new_user_group = [ + group_name_to_id[group_name] + for group_name in user_group + if group_name in group_name_to_id + ] update_user_groups(user.id, new_user_group) return wrap_login_user(user, is_oidc=True) -@app.route('/auth/mfa-setup', methods=['GET', 'POST']) +@app.route("/auth/mfa-setup", methods=["GET", "POST"]) def mfa_setup(): - user = retrieve_user_by_username(username=session['username']) + user = retrieve_user_by_username(username=session["username"]) form = MFASetupForm() if form.submit() and form.validate(): - token = form.token.data mfa_secret = form.mfa_secret.data user_password = form.user_password.data @@ -286,67 +336,97 @@ def mfa_setup(): if totp.verify(token): has_valid_password = False if is_authentication_ldap() is True: - if validate_ldap_login(user.user, user_password, - local_fallback=app.config.get('AUTHENTICATION_LOCAL_FALLBACK')): + if validate_ldap_login( + user.user, + user_password, + local_fallback=app.config.get("AUTHENTICATION_LOCAL_FALLBACK"), + ): has_valid_password = True elif bc.check_password_hash(user.password, user_password): has_valid_password = True if not has_valid_password: - track_activity(f'Failed MFA setup for user {user.user}. Invalid password.', ctx_less=True, display_in_ui=False) - flash('Invalid password. Please try again.', 'danger') - return render_template('mfa_setup.html', form=form) + track_activity( + f"Failed MFA setup for user {user.user}. Invalid password.", + ctx_less=True, + display_in_ui=False, + ) + flash("Invalid password. Please try again.", "danger") + return render_template("mfa_setup.html", form=form) user.mfa_secrets = mfa_secret user.mfa_setup_complete = True db.session.commit() session["mfa_verified"] = False - track_activity(f'MFA setup successful for user {user.user}', ctx_less=True, display_in_ui=False) + track_activity( + f"MFA setup successful for user {user.user}", + ctx_less=True, + display_in_ui=False, + ) return wrap_login_user(user) - track_activity(f'Failed MFA setup for user {user.user}. Invalid token.', ctx_less=True, display_in_ui=False) - flash('Invalid token or password. Please try again.', 'danger') + track_activity( + f"Failed MFA setup for user {user.user}. Invalid token.", + ctx_less=True, + display_in_ui=False, + ) + flash("Invalid token or password. Please try again.", "danger") temp_otp_secret = pyotp.random_base32() - otp_uri = pyotp.TOTP(temp_otp_secret).provisioning_uri(user.email, issuer_name="IRIS") + otp_uri = pyotp.TOTP(temp_otp_secret).provisioning_uri( + user.email, issuer_name="IRIS" + ) form.mfa_secret.data = temp_otp_secret img = qrcode.make(otp_uri) buf = io.BytesIO() - img.save(buf, format='PNG') + img.save(buf, format="PNG") img_str = base64.b64encode(buf.getvalue()).decode() - return render_template('mfa_setup.html', form=form, img_data=img_str, otp_setup_key=temp_otp_secret) + return render_template( + "mfa_setup.html", form=form, img_data=img_str, otp_setup_key=temp_otp_secret + ) -@app.route('/auth/mfa-verify', methods=['GET', 'POST']) +@app.route("/auth/mfa-verify", methods=["GET", "POST"]) def mfa_verify(): - if 'username' not in session: - - return redirect(url_for('login.login')) + if "username" not in session: + return redirect(url_for("login.login")) - user = retrieve_user_by_username(username=session['username']) + user = retrieve_user_by_username(username=session["username"]) # Redirect user to MFA setup if MFA is not fully set up if not user.mfa_secrets or not user.mfa_setup_complete: - track_activity(f'MFA setup required for user {user.user}', ctx_less=True, display_in_ui=False) - return redirect(url_for('mfa_setup')) + track_activity( + f"MFA setup required for user {user.user}", + ctx_less=True, + display_in_ui=False, + ) + return redirect(url_for("mfa_setup")) form = MFASetupForm() - form.user_password.data = 'not required for verification' + form.user_password.data = "not required for verification" if form.submit() and form.validate(): token = form.token.data if not token: - flash('Token is required.', 'danger') - return render_template('mfa_verify.html', form=form) + flash("Token is required.", "danger") + return render_template("mfa_verify.html", form=form) totp = pyotp.TOTP(user.mfa_secrets) if totp.verify(token): - session.pop('username', None) - session['mfa_verified'] = True - track_activity(f'MFA verification successful for user {user.user}', ctx_less=True, display_in_ui=False) + session.pop("username", None) + session["mfa_verified"] = True + track_activity( + f"MFA verification successful for user {user.user}", + ctx_less=True, + display_in_ui=False, + ) return wrap_login_user(user) - track_activity(f'Failed MFA verification for user {user.user}. Invalid token.', ctx_less=True, display_in_ui=False) - flash('Invalid token. Please try again.', 'danger') - - return render_template('mfa_verify.html', form=form) + track_activity( + f"Failed MFA verification for user {user.user}. Invalid token.", + ctx_less=True, + display_in_ui=False, + ) + flash("Invalid token. Please try again.", "danger") + + return render_template("mfa_verify.html", form=form) From 90e38600db13c8ce97c17e374c2450aebb464e7c Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 15 Jan 2026 11:20:15 +0100 Subject: [PATCH 257/286] [ADD] Added manage server routes for authentication details --- source/app/blueprints/rest/v2/manage.py | 4 +- .../rest/v2/manage_routes/server.py | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 source/app/blueprints/rest/v2/manage_routes/server.py diff --git a/source/app/blueprints/rest/v2/manage.py b/source/app/blueprints/rest/v2/manage.py index f24bd02b5..e16905efe 100644 --- a/source/app/blueprints/rest/v2/manage.py +++ b/source/app/blueprints/rest/v2/manage.py @@ -21,9 +21,11 @@ from app.blueprints.rest.v2.manage_routes.groups import groups_blueprint from app.blueprints.rest.v2.manage_routes.users import users_blueprint from app.blueprints.rest.v2.manage_routes.customers import customers_blueprint +from app.blueprints.rest.v2.manage_routes.server import server_blueprint -manage_v2_blueprint = Blueprint('manage', __name__, url_prefix='/manage') +manage_v2_blueprint = Blueprint("manage", __name__, url_prefix="/manage") manage_v2_blueprint.register_blueprint(groups_blueprint) manage_v2_blueprint.register_blueprint(users_blueprint) manage_v2_blueprint.register_blueprint(customers_blueprint) +manage_v2_blueprint.register_blueprint(server_blueprint) diff --git a/source/app/blueprints/rest/v2/manage_routes/server.py b/source/app/blueprints/rest/v2/manage_routes/server.py new file mode 100644 index 000000000..50ead3641 --- /dev/null +++ b/source/app/blueprints/rest/v2/manage_routes/server.py @@ -0,0 +1,49 @@ +# IRIS Source Code +# Copyright (C) 2026 - 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 Response +from flask import Blueprint + +from app import app +from app.blueprints.rest.endpoints import response_api_success +from app.blueprints.rest.endpoints import response_api_error + + +class ServerOperations: + def __init__(self): + pass + + @staticmethod + def get_authentication_settings(): + try: + auth_requirements = { + "oidc_enabled": app.config.get("AUTHENTICATION_TYPE") == "oidc", + "mfa_enabled": app.config.get("MFA_ENABLED"), + } + return response_api_success(auth_requirements) + except Exception as e: + return response_api_error("Data error", data=str(e)) + + +server_blueprint = Blueprint("server_rest_v2", __name__, url_prefix="/server") + +server_operations = ServerOperations() + + +@server_blueprint.get("/authentication-settings") +def server_get_authsettings() -> Response: + return server_operations.get_authentication_settings() From b0539479aa9ad303e6fc625b6bdd2ab4a384fc0d Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Thu, 15 Jan 2026 17:44:49 +0100 Subject: [PATCH 258/286] Added support OIDC in New UI. --- source/app/__init__.py | 2 +- .../blueprints/pages/login/login_routes.py | 66 ++++++++++++++----- source/app/blueprints/rest/v2/auth.py | 22 +++++++ 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/source/app/__init__.py b/source/app/__init__.py index 42c86a0ae..ad4cb167f 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -126,7 +126,7 @@ def ac_current_user_has_manage_perms(): ]}}) -app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1) +app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1) #app.wsgi_app = store.wsgi_middleware(app.wsgi_app) socket_io = SocketIO(app, cors_allowed_origins="*") diff --git a/source/app/blueprints/pages/login/login_routes.py b/source/app/blueprints/pages/login/login_routes.py index 65702b389..634b9e8ea 100644 --- a/source/app/blueprints/pages/login/login_routes.py +++ b/source/app/blueprints/pages/login/login_routes.py @@ -177,12 +177,25 @@ def oidc_login(): session["oidc_state"] = rndstr() session["oidc_nonce"] = rndstr() + xf_proto = request.headers.get("X-Forwarded-Proto") + xf_host = request.headers.get("X-Forwarded-Host") + + if xf_proto: + xf_proto = xf_proto.split(",")[0].strip() + if xf_host: + xf_host = xf_host.split(",")[0].strip() + + redirect_uri = url_for("login.oidc_authorise", _external=True) + + if xf_proto and xf_host: + redirect_uri = f"{xf_proto}://{xf_host}/oidc-authorize" + args = { "client_id": oidc_client.client_id, "response_type": "code", "scope": app.config.get("OIDC_SCOPES"), "nonce": session["oidc_nonce"], - "redirect_uri": url_for("login.oidc_authorise", _external=True), + "redirect_uri": redirect_uri, "state": session["oidc_state"], } @@ -212,6 +225,18 @@ def oidc_authorise(): "code": auth_resp["code"], } + xf_proto = request.headers.get("X-Forwarded-Proto") + xf_host = request.headers.get("X-Forwarded-Host") + + if xf_proto: + xf_proto = xf_proto.split(",")[0].strip() + if xf_host: + xf_host = xf_host.split(",")[0].strip() + + if xf_proto and xf_host: + public_base = f"{xf_proto}://{xf_host}" + args["redirect_uri"] = f"{public_base}/oidc-authorize" + access_token_resp = oidc_client.do_access_token_request( state=auth_resp["state"], request_args=args ) @@ -223,25 +248,32 @@ def oidc_authorise(): usergroup_field = app.config.get("OIDC_MAPPING_USERGROUP") userroles_mapping_field = app.config.get("OIDC_MAPPING_ROLES") try: - if "id_token" not in access_token_resp: - log.error( - "OIDC authentication failed: 'id_token' not found in access token response" - ) - track_activity( - "OIDC authentication failed: missing id_token in response", - ctx_less=True, - display_in_ui=False, + if "id_token" in access_token_resp and access_token_resp["id_token"]: + claims = access_token_resp["id_token"] + else: + if "access_token" not in access_token_resp: + err = access_token_resp.get("error") + desc = access_token_resp.get("error_description") + log.error( + f"OIDC authentication failed: token response missing access_token (error={err}, desc={desc})" + ) + track_activity( + f"OIDC authentication failed: token response missing access_token (error={err}, desc={desc})", + ctx_less=True, + display_in_ui=False, + ) + return redirect(url_for("login.login")) + + claims = oidc_client.do_user_info_request( + state=auth_resp["state"], + access_token=access_token_resp["access_token"], ) - return redirect(url_for("login.login")) - user_login = access_token_resp["id_token"].get( - username_field - ) or access_token_resp["id_token"].get(email_field) - user_name = access_token_resp["id_token"].get( - email_field - ) or access_token_resp["id_token"].get(username_field) + user_login = claims.get(username_field) or claims.get(email_field) + user_name = claims.get(email_field) or claims.get(username_field) + if usergroup_field is not None: - user_group = access_token_resp["id_token"].get(usergroup_field) + user_group = claims.get(usergroup_field) else: user_group = None diff --git a/source/app/blueprints/rest/v2/auth.py b/source/app/blueprints/rest/v2/auth.py index 43f86270e..7287b1226 100644 --- a/source/app/blueprints/rest/v2/auth.py +++ b/source/app/blueprints/rest/v2/auth.py @@ -86,6 +86,28 @@ def login(): return response_api_success(data=user_data) +@auth_blueprint.get('/whoami') +def whoami(): + """ + Returns current authenticated user info (based on the existing session) and API tokens. + Output shape matches the frontend's existing local-login handler: + { responseData, tokenInfo, redirectTo } + """ + if not iris_current_user.is_authenticated: + return response_api_error('Unauthorized', 401) + + user = users_get_active(iris_current_user.id) + response_data = UserSchema(exclude=['user_password', 'mfa_secrets', 'webauthn_credentials']).dump(user) + + token_info = generate_auth_tokens(user) + + return response_api_success(data={ + 'responseData': response_data, + 'tokenInfo': token_info, + 'redirectTo': '/' + }) + + @auth_blueprint.post('/logout') def logout(): """ From 67d0f57f2022ebc7a8c7b88900ae2d4f14cc9bb7 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Mon, 19 Jan 2026 20:28:25 +0100 Subject: [PATCH 259/286] Added support for MFA in New UI. --- source/app/blueprints/rest/v2/auth.py | 131 ++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/source/app/blueprints/rest/v2/auth.py b/source/app/blueprints/rest/v2/auth.py index 7287b1226..6763a933b 100644 --- a/source/app/blueprints/rest/v2/auth.py +++ b/source/app/blueprints/rest/v2/auth.py @@ -17,6 +17,8 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import jwt +import pyotp + from flask import Blueprint from flask import session from flask import redirect @@ -26,6 +28,7 @@ from oic.oauth2.exception import GrantError from app import app +from app import bc from app.db import db from app import oidc_client from app.blueprints.iris_user import iris_current_user @@ -86,6 +89,134 @@ def login(): return response_api_success(data=user_data) +@auth_blueprint.post('/mfa-setup') +def mfa_setup(): + """ + Persist user's MFA secret after validating: + - refresh_token is valid (used to identify user_id) + - provided TOTP token matches provided secret + - provided password matches user (LDAP or local) + """ + data = request.get_json(silent=True) or {} + + refresh_token = data.get('refresh_token') + token = data.get('token') + mfa_secret = data.get('mfa_secret') + user_password = data.get('user_password') or data.get('password') + + if not refresh_token or not token or not mfa_secret or not user_password: + return response_api_error('Missing required fields: refresh_token, token, mfa_secret, password') + + try: + payload = jwt.decode(refresh_token, app.config.get('SECRET_KEY'), algorithms=['HS256']) + + if payload.get('type') != 'refresh': + return response_api_error('Invalid token type') + + user_id = payload.get('user_id') + user = users_get_active(user_id) + + totp = pyotp.TOTP(mfa_secret) + if not totp.verify(str(token)): + track_activity( + f"Failed MFA setup for user {user.user}. Invalid token.", + ctx_less=True, + display_in_ui=False, + ) + return response_api_error('Invalid token') + + has_valid_password = False + + if is_authentication_ldap() is True: + if validate_ldap_login( + user.user, + user_password, + local_fallback=app.config.get("AUTHENTICATION_LOCAL_FALLBACK"), + ): + has_valid_password = True + else: + if bc.check_password_hash(user.password, user_password): + has_valid_password = True + + if not has_valid_password: + track_activity( + f"Failed MFA setup for user {user.user}. Invalid password.", + ctx_less=True, + display_in_ui=False, + ) + return response_api_error('Invalid password') + + user.mfa_secrets = mfa_secret + user.mfa_setup_complete = True + db.session.commit() + + track_activity( + f"MFA setup successful for user {user.user}", + ctx_less=True, + display_in_ui=False, + ) + + return response_api_success({'mfa_setup_complete': True}) + + except ObjectNotFoundError: + return response_api_not_found() + except jwt.ExpiredSignatureError: + return response_api_error('Refresh token has expired') + except jwt.InvalidTokenError: + return response_api_error('Invalid refresh token') + + +@auth_blueprint.post('/mfa-verify') +def mfa_verify(): + """ + Verify a TOTP token against the saved MFA secret. + Uses refresh_token to identify the user (no reliance on iris_current_user). + """ + data = request.get_json(silent=True) or {} + + refresh_token = data.get('refresh_token') + token = data.get('token') + + if not refresh_token or not token: + return response_api_error('Missing required fields: refresh_token, token') + + try: + payload = jwt.decode(refresh_token, app.config.get('SECRET_KEY'), algorithms=['HS256']) + + if payload.get('type') != 'refresh': + return response_api_error('Invalid token type') + + user_id = payload.get('user_id') + user = users_get_active(user_id) + + if not user.mfa_secrets or not user.mfa_setup_complete: + return response_api_error('MFA setup required') + + totp = pyotp.TOTP(user.mfa_secrets) + if not totp.verify(str(token), valid_window=1): + track_activity( + f"Failed MFA verification for user {user.user}. Invalid token.", + ctx_less=True, + display_in_ui=False, + ) + return response_api_error('Invalid token') + + track_activity( + f"MFA verification successful for user {user.user}", + ctx_less=True, + display_in_ui=False, + ) + + return response_api_success({'mfa_verified': True}) + + except ObjectNotFoundError: + return response_api_not_found() + except jwt.ExpiredSignatureError: + return response_api_error('Refresh token has expired') + except jwt.InvalidTokenError: + return response_api_error('Invalid refresh token') + + @auth_blueprint.get('/whoami') def whoami(): """ From 576c00ad2e944599c47def940318a813d7313db2 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Wed, 21 Jan 2026 13:35:42 +0100 Subject: [PATCH 260/286] Added requirement of MFA verification in JWT authentication. --- source/app/blueprints/access_controls.py | 31 +++++++++- source/app/blueprints/rest/api_auth.py | 72 ++++++++++++++++++++++++ source/app/blueprints/rest/v2/auth.py | 30 ++++++---- source/app/business/auth.py | 25 +++++++- 4 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 source/app/blueprints/rest/api_auth.py diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index 485fd6beb..84ba6615c 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -583,12 +583,39 @@ def ac_fast_check_current_user_has_case_access(cid, access_level): return ac_fast_check_user_has_case_access(iris_current_user.id, cid, access_level) +def _get_current_permissions_mask(): + # Token-based authentication + if hasattr(g, 'auth_token_user_id'): + if hasattr(g, 'auth_user_permissions'): + return g.auth_user_permissions + + user = get_user(g.auth_token_user_id) + if not user: + return 0 + + perms = ac_get_effective_permissions_of_user(user) + g.auth_user_permissions = perms + return perms + + # Session-based authentication + perms = session.get('permissions') + if perms is None and current_user.is_authenticated: + perms = ac_get_effective_permissions_of_user(current_user) + session['permissions'] = perms + + return perms or 0 + + def ac_current_user_has_permission(permission): """ Return True if current user has permission """ - return ac_flag_match_mask(session['permissions'], permission.value) + return ac_flag_match_mask(_get_current_permissions_mask(), permission.value) def ac_current_user_has_customer_access(customer_identifier): - return access_controls_user_has_customer_access(iris_current_user, session['permissions'], customer_identifier) + return access_controls_user_has_customer_access( + iris_current_user, + _get_current_permissions_mask(), + customer_identifier + ) diff --git a/source/app/blueprints/rest/api_auth.py b/source/app/blueprints/rest/api_auth.py new file mode 100644 index 000000000..ac0e99b5b --- /dev/null +++ b/source/app/blueprints/rest/api_auth.py @@ -0,0 +1,72 @@ +from functools import wraps +from flask import request, g +from flask_login import current_user +import jwt + +from app import app +from app.business.users import users_get_active +from app.blueprints.rest.endpoints import response_api_error + + +def _jwt_user(): + auth = request.headers.get("Authorization") + if not auth or not auth.startswith("Bearer "): + return None + + try: + payload = jwt.decode( + auth.split(" ", 1)[1], + app.config["SECRET_KEY"], + algorithms=["HS256"], + ) + except jwt.InvalidTokenError: + return "invalid" + + if payload.get("type") != "access": + return "invalid" + + return users_get_active(payload["user_id"]) + + +def _legacy_token_user(): + if not hasattr(g, "auth_user"): + return None + return users_get_active(g.auth_user["user_id"]) + + +def _session_user(): + if not current_user.is_authenticated: + return None + return users_get_active(current_user.id) + + +def api_auth(*, require_mfa: bool = False): + def decorator(fn): + @wraps(fn) + def wrapper(*args, **kwargs): + user = _jwt_user() + + if user == "invalid": + return response_api_error("Invalid token", 401) + + if user is None: + user = _legacy_token_user() + + if user is None: + user = _session_user() + + if user is None: + return response_api_error("Unauthorized", 401) + + if ( + require_mfa + and app.config.get("MFA_ENABLED") + and not user.mfa_setup_complete + ): + return response_api_error("MFA required", 403) + + g.api_user = user + return fn(*args, **kwargs) + + return wrapper + return decorator diff --git a/source/app/blueprints/rest/v2/auth.py b/source/app/blueprints/rest/v2/auth.py index 6763a933b..637f13fed 100644 --- a/source/app/blueprints/rest/v2/auth.py +++ b/source/app/blueprints/rest/v2/auth.py @@ -24,6 +24,7 @@ from flask import redirect from flask import url_for from flask import request +from flask import g from flask_login import logout_user from oic.oauth2.exception import GrantError @@ -37,6 +38,7 @@ from app.blueprints.access_controls import is_authentication_ldap from app.blueprints.access_controls import is_authentication_oidc from app.blueprints.access_controls import not_authenticated_redirection_url +from app.blueprints.rest.api_auth import api_auth from app.blueprints.rest.endpoints import response_api_error, response_api_not_found from app.blueprints.rest.endpoints import response_api_success from app.business.auth import validate_ldap_login @@ -65,8 +67,9 @@ def login(): if is_authentication_oidc() and app.config.get('AUTHENTICATION_LOCAL_FALLBACK') is False: return redirect(url_for('login.oidc_login')) - username = request.json.get('username') - password = request.json.get('password') + data = request.get_json(silent=True) or {} + username = data.get('username') + password = data.get('password') if is_authentication_ldap() is True: authed_user = validate_ldap_login(username, password, app.config.get('AUTHENTICATION_LOCAL_FALLBACK')) @@ -207,7 +210,9 @@ def mfa_verify(): display_in_ui=False, ) - return response_api_success({'mfa_verified': True}) + tokens = generate_auth_tokens(user, mfa_verified=True) + + return response_api_success({'mfa_verified': True, 'tokens': tokens}) except ObjectNotFoundError: return response_api_not_found() @@ -218,35 +223,35 @@ def mfa_verify(): @auth_blueprint.get('/whoami') +@api_auth() def whoami(): """ Returns current authenticated user info (based on the existing session) and API tokens. Output shape matches the frontend's existing local-login handler: { responseData, tokenInfo, redirectTo } """ - if not iris_current_user.is_authenticated: - return response_api_error('Unauthorized', 401) - - user = users_get_active(iris_current_user.id) - response_data = UserSchema(exclude=['user_password', 'mfa_secrets', 'webauthn_credentials']).dump(user) + user = g.api_user - token_info = generate_auth_tokens(user) + response_data = UserSchema( + exclude=['user_password', 'mfa_secrets', 'webauthn_credentials'] + ).dump(user) return response_api_success(data={ 'responseData': response_data, - 'tokenInfo': token_info, + 'tokenInfo': None, 'redirectTo': '/' }) @auth_blueprint.post('/logout') +@api_auth() def logout(): """ Logout function. Erase its session and redirect to index i.e login :return: Page """ - if session['current_case']: + if session.get('current_case'): iris_current_user.ctx_case = session['current_case']['case_id'] db.session.commit() @@ -282,7 +287,8 @@ def refresh_token_endpoint(): """ Refresh authentication tokens using a valid refresh token """ - refresh_token = request.json.get('refresh_token') + data = request.get_json(silent=True) or {} + refresh_token = data.get('refresh_token') if not refresh_token: return response_api_error('Refresh token is required') diff --git a/source/app/business/auth.py b/source/app/business/auth.py index 89e85ec60..db75484a4 100644 --- a/source/app/business/auth.py +++ b/source/app/business/auth.py @@ -153,7 +153,16 @@ def update_session_current_case(user: User): } -def generate_auth_tokens(user): +def _mfa_required_for_user(user) -> bool: + if 'SERVER_SETTINGS' not in app.config: + app.config['SERVER_SETTINGS'] = get_server_settings_as_dict() + + enforce_mfa = bool(app.config['SERVER_SETTINGS'].get('enforce_mfa')) + user_has_mfa = bool(getattr(user, 'mfa_setup_complete', False)) and bool(getattr(user, 'mfa_secrets', None)) + return enforce_mfa and user_has_mfa + + +def generate_auth_tokens(user, mfa_verified: bool = False): """ Generate access and refresh tokens with essential user data @@ -168,12 +177,18 @@ def generate_auth_tokens(user): days=app.config.get('REFRESH_TOKEN_EXPIRES_DAYS', 14) ) + mfa_required = _mfa_required_for_user(user) + effective_mfa_verified = True if not mfa_required else bool(mfa_verified) + # Generate access token with user data access_token_payload = { 'user_id': user.id, 'user_name': user.name, 'user_email': user.email, 'user_login': user.user, + 'type': 'access', + 'mfa_required': mfa_required, + 'mfa_verified': effective_mfa_verified, 'exp': access_token_expiry } access_token = jwt.encode( @@ -214,11 +229,17 @@ def validate_auth_token(token): """ try: payload = jwt.decode(token, app.config.get('SECRET_KEY'), algorithms=['HS256']) + + if payload.get('type') != 'access': + return None + return { 'user_id': payload.get('user_id'), 'user_login': payload.get('user_login'), - 'user_name': payload.get('username'), + 'user_name': payload.get('user_name'), 'user_email': payload.get('user_email'), + 'mfa_required': payload.get('mfa_required', False), + 'mfa_verified': payload.get('mfa_verified', False), } except jwt.ExpiredSignatureError: return None From ba6c62e5c52144c26f77188ab4c8b8fd595a5cfd Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Mon, 9 Feb 2026 17:04:51 +0100 Subject: [PATCH 261/286] Added GET methods for user and group cases access API. --- .../app/blueprints/rest/manage/manage_groups.py | 11 +++++++++++ source/app/blueprints/rest/manage/manage_users.py | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/source/app/blueprints/rest/manage/manage_groups.py b/source/app/blueprints/rest/manage/manage_groups.py index 0e005ba2d..57f8c93c6 100644 --- a/source/app/blueprints/rest/manage/manage_groups.py +++ b/source/app/blueprints/rest/manage/manage_groups.py @@ -220,6 +220,17 @@ def manage_groups_members_delete(cur_id, cur_id_2): return response_success('Member deleted from group', data=group) +@manage_groups_rest_blueprint.route('/manage/groups//cases-access', methods=['GET']) +@ac_api_requires(Permissions.server_administrator) +def manage_groups_cac_list_cases(cur_id): + + group = get_group_details(cur_id) + if not group: + return response_error("Invalid group ID") + + return response_success(data=group) + + @manage_groups_rest_blueprint.route('/manage/groups//cases-access/update', methods=['POST']) @ac_api_requires(Permissions.server_administrator) def manage_groups_cac_add_case(cur_id): diff --git a/source/app/blueprints/rest/manage/manage_users.py b/source/app/blueprints/rest/manage/manage_users.py index 240f36769..75d2431f0 100644 --- a/source/app/blueprints/rest/manage/manage_users.py +++ b/source/app/blueprints/rest/manage/manage_users.py @@ -207,6 +207,21 @@ def manage_user_customers_(cur_id): return response_success("User customers updated", data=user) +@manage_users_rest_blueprint.route('/manage/users//cases-access', methods=['GET']) +@ac_api_requires(Permissions.server_administrator) +def manage_user_cac_list_cases(cur_id): + + user = get_user_details(user_id=cur_id) + + if not user: + return response_error("Invalid user ID") + + # get_user_details returns user_cases_access (API v2 schema) but keep fallback for older payloads + cases_access = user.get('user_cases_access', user.get('cases_access', [])) + + return response_success(data=cases_access) + + @manage_users_rest_blueprint.route('/manage/users//cases-access/update', methods=['POST']) @ac_api_requires(Permissions.server_administrator) def manage_user_cac_add_case(cur_id): From b584742ac1f53293c41bd2da8cefc38583c461ba Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Fri, 13 Feb 2026 10:50:54 +0100 Subject: [PATCH 262/286] Fixed access control for token auth in API V2. --- source/app/blueprints/access_controls.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index 84ba6615c..d2c4942c0 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -177,7 +177,15 @@ def _update_denied_case(caseid): def _update_current_case(caseid, restricted_access): - if session['current_case']['case_id'] != caseid: + if 'current_case' not in session or not isinstance(session.get('current_case'), dict): + session['current_case'] = { + 'case_name': '', + 'case_info': '', + 'case_id': caseid, + 'access': '' + } + + if session['current_case'].get('case_id') != caseid: case = get_case(caseid) if case: session['current_case'] = { From 6e0203cba64551413d17556404d6e6a3c625d467 Mon Sep 17 00:00:00 2001 From: Lelian Thorel Date: Tue, 17 Feb 2026 13:44:43 +0100 Subject: [PATCH 263/286] imp: export env IRIS_AUTHENTICATION_TYPE too. --- deploy/kubernetes/charts/templates/iris_app.yaml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/deploy/kubernetes/charts/templates/iris_app.yaml b/deploy/kubernetes/charts/templates/iris_app.yaml index 1a8614af1..a4e47d51d 100644 --- a/deploy/kubernetes/charts/templates/iris_app.yaml +++ b/deploy/kubernetes/charts/templates/iris_app.yaml @@ -58,10 +58,10 @@ spec: {{- end }} {{- end }} - - name: IRIS_SECRET_KEY + - name: IRIS_SECRET_KEY value: {{ .Values.irisapp.IRIS_SECRET_KEY | quote }} - - name: IRIS_SECURITY_PASSWORD_SALT + - name: IRIS_SECURITY_PASSWORD_SALT value: {{ .Values.irisapp.IRIS_SECURITY_PASSWORD_SALT | quote }} - name: DB_RETRY_COUNT @@ -78,8 +78,12 @@ spec: - name: IRIS_ADM_PASSWORD value: {{ .Values.irisapp.IRIS_ADM_PASSWORD | quote }} - + {{- if eq .Values.irisapp.IRIS_AUTHENTICATION_TYPE "oidc" }} + + - name: IRIS_AUTHENTICATION_TYPE: + value: {{ .Values.irisapp.IRIS_AUTHENTICATION_TYPE | quote }} + - name: OIDC_ISSUER_URL value: {{ .Values.irisapp.OIDC_ISSUER_URL | quote }} @@ -104,13 +108,13 @@ spec: - name: OIDC_MAPPING_ROLES value: {{ .Values.irisapp.OIDC_MAPPING_ROLES | quote }} {{- end }} - + ports: - containerPort: 8000 volumeMounts: - mountPath: /home/iris/downloads - name: iris-downloads + name: iris-downloads - mountPath: /home/iris/user_templates name: user-templates - mountPath: /home/iris/server_data From 934de802fa831fefc6b380c84f6d87529f8ddb15 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Thu, 19 Feb 2026 14:43:19 +0100 Subject: [PATCH 264/286] Added option to have multiple cases filters. --- .../rest/manage/manage_cases_routes.py | 78 +++++++- source/app/datamgmt/manage/manage_cases_db.py | 175 +++++++++++++++--- 2 files changed, 228 insertions(+), 25 deletions(-) diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index a68de7505..079520c43 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -15,6 +15,7 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import json import logging as log import os import traceback @@ -25,6 +26,7 @@ from werkzeug import Response from werkzeug.utils import secure_filename from marshmallow import ValidationError +from typing import Any from app.db import db from app.blueprints.rest.parsing import parse_comma_separated_identifiers @@ -88,6 +90,68 @@ def manage_case_filter() -> Response: pagination_parameters = parse_pagination_parameters(request) + logic = request.args.get('logic', 'and', type=str) + logic = (logic or 'and').lower() + if logic not in ('and', 'or'): + return response_error("Invalid logic (expected 'and' or 'or')") + + raw_filters = request.args.get('filters', None, type=str) + advanced_filters: list[dict[str, Any]] | None = None + + if raw_filters: + try: + decoded = urllib.parse.unquote(raw_filters) + parsed = json.loads(decoded) + except Exception: + return response_error('Invalid filters JSON') + + if not isinstance(parsed, list): + return response_error('Invalid filters (expected a JSON array)') + + advanced_filters = [] + for i, f in enumerate(parsed): + if not isinstance(f, dict): + return response_error(f'Invalid filter at index {i} (expected object)') + + field_id = f.get('fieldId') + operation = f.get('operation') + value = f.get('value', '') + + if not isinstance(field_id, str) or not field_id: + return response_error(f'Invalid fieldId at index {i}') + if not isinstance(operation, str) or not operation: + return response_error(f'Invalid operation at index {i}') + if not isinstance(value, str): + return response_error(f'Invalid value at index {i}') + + operation = operation.lower() + + allowed_ops = { + 'equals', + 'not', + 'starts_with', + 'not_starts_with', + 'contains', + 'not_contains', + 'ends_with', + 'not_ends_with', + 'empty', + 'not_empty' + } + if operation not in allowed_ops: + return response_error(f'Invalid operation at index {i}') + + if operation in ('empty', 'not_empty'): + value = '' + + advanced_filters.append( + { + 'fieldId': field_id, + 'operation': operation, + 'value': value + } + ) + case_ids_str = request.args.get('case_ids', None, type=str) if case_ids_str: @@ -111,6 +175,15 @@ def manage_case_filter() -> Response: draw = request.args.get('draw', 1, type=int) search_value = request.args.get('search[value]', type=str) # Get the search value from the request + is_open_raw = request.args.get('is_open', None, type=str) + is_open = None + if is_open_raw is not None: + v = is_open_raw.strip().lower() + if v in ('1', 'true', 'yes', 'y', 'on'): + is_open = True + elif v in ('0', 'false', 'no', 'n', 'off'): + is_open = False + if type(draw) is not int: draw = 1 @@ -129,7 +202,10 @@ def manage_case_filter() -> Response: case_soc_id=case_soc_id, start_open_date=start_open_date, end_open_date=end_open_date, - search_value=search_value + search_value=search_value, + is_open=is_open, + advanced_filters=advanced_filters, + advanced_logic=logic ) if filtered_cases is None: return response_error('Filtering error') diff --git a/source/app/datamgmt/manage/manage_cases_db.py b/source/app/datamgmt/manage/manage_cases_db.py index 91840f886..e421f59d5 100644 --- a/source/app/datamgmt/manage/manage_cases_db.py +++ b/source/app/datamgmt/manage/manage_cases_db.py @@ -20,8 +20,9 @@ from datetime import date from datetime import timedelta from pathlib import Path +from typing import Any -from sqlalchemy import and_ +from sqlalchemy import and_, or_, cast, String from sqlalchemy.orm import aliased from functools import reduce @@ -561,27 +562,153 @@ def build_filter_case_query(current_user_id, def get_filtered_cases(current_user_id, pagination_parameters: PaginationParameters, - start_open_date: str = None, - end_open_date: str = None, - case_customer_id: int = None, - case_ids: list = None, - case_name: str = None, - case_description: str = None, - case_classification_id: int = None, - case_owner_id: int = None, - case_opening_user_id: int = None, - case_severity_id: int = None, - case_state_id: int = None, - case_soc_id: str = None, - case_open_since: int = None, - search_value=None, - is_open: bool = None + start_open_date: str | None = None, + end_open_date: str | None = None, + case_customer_id: int | None = None, + case_ids: list[int] | None = None, + case_name: str | None = None, + case_description: str | None = None, + case_classification_id: int | None = None, + case_owner_id: int | None = None, + case_opening_user_id: int | None = None, + case_severity_id: int | None = None, + case_state_id: int | None = None, + case_soc_id: str | None = None, + case_open_since: int | None = None, + search_value: str | None = None, + is_open: bool | None = None, + advanced_filters: list[dict[str, Any]] | None = None, + advanced_logic: str = 'and' ): - data = build_filter_case_query(case_classification_id=case_classification_id, case_customer_id=case_customer_id, case_description=case_description, - case_ids=case_ids, case_name=case_name, case_opening_user_id=case_opening_user_id, case_owner_id=case_owner_id, - case_severity_id=case_severity_id, case_soc_id=case_soc_id, case_open_since=case_open_since, - case_state_id=case_state_id, current_user_id=current_user_id, end_open_date=end_open_date, - search_value=search_value, start_open_date=start_open_date, is_open=is_open, - sort_by=pagination_parameters.get_order_by(), sort_dir=pagination_parameters.get_direction()) - - return data.paginate(page=pagination_parameters.get_page(), per_page=pagination_parameters.get_per_page(), error_out=False) + kwargs: dict[str, Any] = { + 'current_user_id': current_user_id, + 'sort_by': pagination_parameters.get_order_by(), + 'sort_dir': pagination_parameters.get_direction() + } + + if start_open_date is not None: + kwargs['start_open_date'] = start_open_date + if end_open_date is not None: + kwargs['end_open_date'] = end_open_date + + if case_customer_id is not None: + kwargs['case_customer_id'] = case_customer_id + + if case_ids is not None: + kwargs['case_ids'] = case_ids + + if case_name is not None: + kwargs['case_name'] = case_name + + if case_description is not None: + kwargs['case_description'] = case_description + + if case_classification_id is not None: + kwargs['case_classification_id'] = case_classification_id + + if case_owner_id is not None: + kwargs['case_owner_id'] = case_owner_id + + if case_opening_user_id is not None: + kwargs['case_opening_user_id'] = case_opening_user_id + + if case_severity_id is not None: + kwargs['case_severity_id'] = case_severity_id + + if case_state_id is not None: + kwargs['case_state_id'] = case_state_id + + if case_soc_id is not None: + kwargs['case_soc_id'] = case_soc_id + + if case_open_since is not None: + kwargs['case_open_since'] = case_open_since + + if search_value is not None: + kwargs['search_value'] = search_value + + if is_open is not None: + kwargs['is_open'] = is_open + + query = build_filter_case_query(**kwargs) + + if advanced_filters: + adv_conditions = [] + joined_client = False + joined_state = False + joined_owner = False + + for f in advanced_filters: + field_id = f.get('fieldId') + operation = f.get('operation') + value = f.get('value', '') + + if not isinstance(field_id, str) or not isinstance(operation, str) or not isinstance(value, str): + continue + + field_expr: Any = None + + if field_id == 'title': + field_expr = getattr(Cases, 'name') + elif field_id == 'case_id': + field_expr = cast(getattr(Cases, 'case_id'), String) + elif field_id == 'outcome': + field_expr = getattr(Cases, 'closing_note') + elif field_id == 'open_date': + field_expr = cast(getattr(Cases, 'open_date'), String) + elif field_id == 'classification': + field_expr = cast(getattr(Cases, 'classification_id'), String) + elif field_id == 'customer': + if not joined_client: + query = query.join(Client, Cases.client_id == Client.client_id) + joined_client = True + field_expr = getattr(Client, 'name') + elif field_id == 'state': + if not joined_state: + query = query.join(CaseState, Cases.state_id == CaseState.state_id) + joined_state = True + field_expr = getattr(CaseState, 'state_name') + elif field_id == 'owner': + if not joined_owner: + query = query.join(User, Cases.owner_id == User.id) + joined_owner = True + field_expr = getattr(User, 'user') + + if field_expr is None: + continue + + op = operation.lower() + + if op == 'empty': + adv_conditions.append(or_(field_expr.is_(None), field_expr == '')) + continue + if op == 'not_empty': + adv_conditions.append(and_(field_expr.is_not(None), field_expr != '')) + continue + + if op == 'equals': + adv_conditions.append(field_expr == value) + elif op == 'not': + adv_conditions.append(field_expr != value) + elif op == 'starts_with': + adv_conditions.append(field_expr.ilike(f'{value}%')) + elif op == 'not_starts_with': + adv_conditions.append(~field_expr.ilike(f'{value}%')) + elif op == 'contains': + adv_conditions.append(field_expr.ilike(f'%{value}%')) + elif op == 'not_contains': + adv_conditions.append(~field_expr.ilike(f'%{value}%')) + elif op == 'ends_with': + adv_conditions.append(field_expr.ilike(f'%{value}')) + elif op == 'not_ends_with': + adv_conditions.append(~field_expr.ilike(f'%{value}')) + + if adv_conditions: + if (advanced_logic or 'and').lower() == 'or': + query = query.filter(or_(*adv_conditions)) + else: + query = query.filter(and_(*adv_conditions)) + + return query.paginate(page=pagination_parameters.get_page(), + per_page=pagination_parameters.get_per_page(), + error_out=False) From d97166f36d5743f15b203df16db5786016e3f3e5 Mon Sep 17 00:00:00 2001 From: Lelian Thorel Date: Fri, 20 Feb 2026 09:08:19 +0100 Subject: [PATCH 265/286] fix(postgres): typo in postgres.yaml template. --- deploy/kubernetes/charts/templates/postgres.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/deploy/kubernetes/charts/templates/postgres.yaml b/deploy/kubernetes/charts/templates/postgres.yaml index 09ca30fda..c51ff941d 100644 --- a/deploy/kubernetes/charts/templates/postgres.yaml +++ b/deploy/kubernetes/charts/templates/postgres.yaml @@ -1,5 +1,5 @@ {{- if .Values.postgres.enabled }} ---- +--- # Here I have used a hostpath # Local volumes can only be used as a statically created PersistentVolume. Dynamic provisioning is not supported. # If you need to go with Dynamic volumes you may choose AWS EBS or EFS @@ -68,13 +68,13 @@ spec: - name: POSTGRES_USER # Setting Database username value: {{ .Values.postgres.POSTGRES_USER | quote }} - - name: POSTGRES_PASSWORDD # Setting Database password + - name: POSTGRES_PASSWORD # Setting Database password value: {{ .Values.postgres.POSTGRES_PASSWORD | quote }} - - name: POSTGRES_ADMIN_USER # Setting Database admin user + - name: POSTGRES_ADMIN_USER # Setting Database admin user value: {{ .Values.postgres.POSTGRES_ADMIN_USER | quote }} - - name: POSTGRES_ADMIN_PASSWORD # Setting Database admin password + - name: POSTGRES_ADMIN_PASSWORD # Setting Database admin password value: {{ .Values.postgres.POSTGRES_ADMIN_PASSWORD | quote }} - name: POSTGRES_PORT # Setting Database port @@ -108,5 +108,5 @@ spec: - port: {{ .Values.postgres.service.port }} selector: app: {{ .Values.postgres.app }} ---- -{{- end }} \ No newline at end of file +--- +{{- end }} From 2185e58589795467bdc3300ccbef171c45c96635 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Mon, 23 Feb 2026 18:18:39 +0100 Subject: [PATCH 266/286] Moved cases filters logic to api/v2 endpoints. --- .../rest/manage/manage_cases_routes.py | 78 +--------- source/app/blueprints/rest/v2/cases.py | 144 ++++++++++++++++++ 2 files changed, 145 insertions(+), 77 deletions(-) diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index 079520c43..a68de7505 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -15,7 +15,6 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import json import logging as log import os import traceback @@ -26,7 +25,6 @@ from werkzeug import Response from werkzeug.utils import secure_filename from marshmallow import ValidationError -from typing import Any from app.db import db from app.blueprints.rest.parsing import parse_comma_separated_identifiers @@ -90,68 +88,6 @@ def manage_case_filter() -> Response: pagination_parameters = parse_pagination_parameters(request) - logic = request.args.get('logic', 'and', type=str) - logic = (logic or 'and').lower() - if logic not in ('and', 'or'): - return response_error("Invalid logic (expected 'and' or 'or')") - - raw_filters = request.args.get('filters', None, type=str) - advanced_filters: list[dict[str, Any]] | None = None - - if raw_filters: - try: - decoded = urllib.parse.unquote(raw_filters) - parsed = json.loads(decoded) - except Exception: - return response_error('Invalid filters JSON') - - if not isinstance(parsed, list): - return response_error('Invalid filters (expected a JSON array)') - - advanced_filters = [] - for i, f in enumerate(parsed): - if not isinstance(f, dict): - return response_error(f'Invalid filter at index {i} (expected object)') - - field_id = f.get('fieldId') - operation = f.get('operation') - value = f.get('value', '') - - if not isinstance(field_id, str) or not field_id: - return response_error(f'Invalid fieldId at index {i}') - if not isinstance(operation, str) or not operation: - return response_error(f'Invalid operation at index {i}') - if not isinstance(value, str): - return response_error(f'Invalid value at index {i}') - - operation = operation.lower() - - allowed_ops = { - 'equals', - 'not', - 'starts_with', - 'not_starts_with', - 'contains', - 'not_contains', - 'ends_with', - 'not_ends_with', - 'empty', - 'not_empty' - } - if operation not in allowed_ops: - return response_error(f'Invalid operation at index {i}') - - if operation in ('empty', 'not_empty'): - value = '' - - advanced_filters.append( - { - 'fieldId': field_id, - 'operation': operation, - 'value': value - } - ) - case_ids_str = request.args.get('case_ids', None, type=str) if case_ids_str: @@ -175,15 +111,6 @@ def manage_case_filter() -> Response: draw = request.args.get('draw', 1, type=int) search_value = request.args.get('search[value]', type=str) # Get the search value from the request - is_open_raw = request.args.get('is_open', None, type=str) - is_open = None - if is_open_raw is not None: - v = is_open_raw.strip().lower() - if v in ('1', 'true', 'yes', 'y', 'on'): - is_open = True - elif v in ('0', 'false', 'no', 'n', 'off'): - is_open = False - if type(draw) is not int: draw = 1 @@ -202,10 +129,7 @@ def manage_case_filter() -> Response: case_soc_id=case_soc_id, start_open_date=start_open_date, end_open_date=end_open_date, - search_value=search_value, - is_open=is_open, - advanced_filters=advanced_filters, - advanced_logic=logic + search_value=search_value ) if filtered_cases is None: return response_error('Filtering error') diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index 9d9b7c5ba..92bf2520b 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -16,6 +16,10 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import json +import urllib.parse +from typing import Any + from flask import Blueprint from flask import request from marshmallow import ValidationError @@ -45,6 +49,8 @@ from app.models.errors import BusinessProcessingError, ObjectNotFoundError from app.business.cases import cases_filter from app.schema.marshables import CaseSchemaForAPIV2 +from app.schema.marshables import CaseDetailsSchema +from app.datamgmt.manage.manage_cases_db import get_filtered_cases from app.blueprints.access_controls import ac_api_requires from app.blueprints.access_controls import ac_current_user_has_customer_access from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access @@ -99,6 +105,138 @@ def search(self): return response_api_paginated(self._schema, filtered_cases) + def filter(self) -> Response: + pagination_parameters = parse_pagination_parameters(request) + + logic = request.args.get('logic', 'and', type=str) + logic = (logic or 'and').lower() + if logic not in ('and', 'or'): + return response_api_error("Invalid logic (expected 'and' or 'or')") + + raw_filters = request.args.get('filters', None, type=str) + advanced_filters: list[dict[str, Any]] | None = None + + if raw_filters: + try: + decoded = urllib.parse.unquote(raw_filters) + parsed = json.loads(decoded) + except Exception: + return response_api_error('Invalid filters JSON') + + if not isinstance(parsed, list): + return response_api_error('Invalid filters (expected a JSON array)') + + advanced_filters = [] + for i, f in enumerate(parsed): + if not isinstance(f, dict): + return response_api_error(f'Invalid filter at index {i} (expected object)') + + field_id = f.get('fieldId') + operation = f.get('operation') + value = f.get('value', '') + + if not isinstance(field_id, str) or not field_id: + return response_api_error(f'Invalid fieldId at index {i}') + if not isinstance(operation, str) or not operation: + return response_api_error(f'Invalid operation at index {i}') + if not isinstance(value, str): + return response_api_error(f'Invalid value at index {i}') + + operation = operation.lower() + + allowed_ops = { + 'equals', + 'not', + 'starts_with', + 'not_starts_with', + 'contains', + 'not_contains', + 'ends_with', + 'not_ends_with', + 'empty', + 'not_empty' + } + if operation not in allowed_ops: + return response_api_error(f'Invalid operation at index {i}') + + if operation in ('empty', 'not_empty'): + value = '' + + advanced_filters.append( + { + 'fieldId': field_id, + 'operation': operation, + 'value': value + } + ) + + case_ids_str = request.args.get('case_ids', None, type=str) + if case_ids_str: + try: + case_ids_str = parse_comma_separated_identifiers(case_ids_str) + except ValueError: + return response_api_error('Invalid case id') + + case_customer_id = request.args.get('case_customer_id', None, type=str) + case_name = request.args.get('case_name', None, type=str) + case_description = request.args.get('case_description', None, type=str) + case_classification_id = request.args.get('case_classification_id', None, type=int) + case_owner_id = request.args.get('case_owner_id', None, type=int) + case_opening_user_id = request.args.get('case_opening_user_id', None, type=int) + case_severity_id = request.args.get('case_severity_id', None, type=int) + case_state_id = request.args.get('case_state_id', None, type=int) + case_soc_id = request.args.get('case_soc_id', None, type=str) + start_open_date = request.args.get('start_open_date', None, type=str) + end_open_date = request.args.get('end_open_date', None, type=str) + draw = request.args.get('draw', 1, type=int) + search_value = request.args.get('search[value]', type=str) + + is_open_raw = request.args.get('is_open', None, type=str) + is_open = None + if is_open_raw is not None: + v = is_open_raw.strip().lower() + if v in ('1', 'true', 'yes', 'y', 'on'): + is_open = True + elif v in ('0', 'false', 'no', 'n', 'off'): + is_open = False + + if type(draw) is not int: + draw = 1 + + filtered_cases = get_filtered_cases( + iris_current_user.id, + pagination_parameters, + case_ids=case_ids_str, + case_customer_id=case_customer_id, + case_name=case_name, + case_description=case_description, + case_classification_id=case_classification_id, + case_owner_id=case_owner_id, + case_opening_user_id=case_opening_user_id, + case_severity_id=case_severity_id, + case_state_id=case_state_id, + case_soc_id=case_soc_id, + start_open_date=start_open_date, + end_open_date=end_open_date, + search_value=search_value, + is_open=is_open, + advanced_filters=advanced_filters, + advanced_logic=logic + ) + if filtered_cases is None: + return response_api_error('Filtering error') + + cases_payload = { + 'total': filtered_cases.total, + 'cases': CaseDetailsSchema().dump(filtered_cases.items, many=True), + 'last_page': filtered_cases.pages, + 'current_page': filtered_cases.page, + 'next_page': filtered_cases.next_num if filtered_cases.has_next else None, + 'draw': draw + } + + return response_api_success(cases_payload) + def create(self): try: request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json()) @@ -191,6 +329,12 @@ def get_cases() -> Response: return cases_operations.search() +@cases_blueprint.get('/filter') +@ac_api_requires() +def filter_cases() -> Response: + return cases_operations.filter() + + @cases_blueprint.post('') @ac_api_requires(Permissions.standard_user) def create_case(): From 40a2f6b4db5c71284d05f21b45b7e16bb7df0eb6 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Mon, 2 Mar 2026 16:19:53 +0100 Subject: [PATCH 267/286] Added alerts filters presets. --- .../app/blueprints/rest/v2/alerts_filters.py | 41 +++++++++++-------- source/app/business/alerts_filters.py | 11 ++++- source/app/datamgmt/filters/filters_db.py | 21 +++++++--- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 46edc6e47..9c345c322 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -1,21 +1,3 @@ -# 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 @@ -36,11 +18,13 @@ 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 +from app.business.alerts_filters import alert_filter_list class AlertsFiltersOperations: def __init__(self): self._schema = SavedFilterSchema() + self._schema_many = SavedFilterSchema(many=True) def _load(self, request_data, **kwargs): return self._schema.load(request_data, **kwargs) @@ -60,6 +44,21 @@ def create(self): except BusinessProcessingError as e: return response_api_error(e.get_message(), data=e.get_data()) + def list(self): + try: + filter_type = request.args.get("filter_type", "alerts") + include_public = request.args.get("include_public", "1") == "1" + + items = alert_filter_list( + iris_current_user, + filter_type=filter_type, + include_public=include_public + ) + return response_api_success(self._schema_many.dump(items)) + + 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(iris_current_user, identifier) @@ -117,6 +116,12 @@ def create_alert_filter(): return alerts_filters_operations.create() +@alerts_filters_blueprint.get("") +@ac_api_requires() +def list_alert_filters(): + return alerts_filters_operations.list() + + @alerts_filters_blueprint.get("/") @ac_api_requires() def get_alert_filter(identifier): diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index 6d1b5c245..1bb6176b1 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -17,7 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from app.db import db -from app.datamgmt.filters.filters_db import get_filter_by_id +from app.datamgmt.filters.filters_db import get_filters, get_filter_by_id from app.models.errors import ObjectNotFoundError @@ -26,6 +26,15 @@ def alert_filter_add(new_saved_filter): db.session.commit() +def alert_filter_list(user, filter_type="alerts", include_public=True): + filters = get_filters( + user_id=user.id, + filter_type=filter_type, + include_public=include_public + ) + return filters + + def alert_filter_get(user, identifier): alert_filter = get_filter_by_id(user.id, identifier) if not alert_filter: diff --git a/source/app/datamgmt/filters/filters_db.py b/source/app/datamgmt/filters/filters_db.py index 4b5ef82b1..392d54d09 100644 --- a/source/app/datamgmt/filters/filters_db.py +++ b/source/app/datamgmt/filters/filters_db.py @@ -39,21 +39,18 @@ def get_filter_by_id(user_identifier, filter_id): return saved_filter -def list_filters_by_type(user_identifier, filter_type): +def list_filters_by_type(user_identifier, filter_type, include_public=True): """ List filters by type args: + user_identifier: user id filter_type: the type of filter to list + include_public: whether to include non-private filters returns: List of SavedFilter objects """ - public_filters = SavedFilter.query.filter( - SavedFilter.filter_is_private == False, - SavedFilter.filter_type == filter_type - ) - private_filters_for_user = SavedFilter.query.filter( and_( SavedFilter.filter_is_private == True, @@ -62,6 +59,18 @@ def list_filters_by_type(user_identifier, filter_type): ) ) + if not include_public: + return private_filters_for_user.all() + + public_filters = SavedFilter.query.filter( + SavedFilter.filter_is_private == False, + SavedFilter.filter_type == filter_type + ) + all_filters = public_filters.union_all(private_filters_for_user).all() return all_filters + + +def get_filters(user_id, filter_type="alerts", include_public=True): + return list_filters_by_type(user_id, filter_type, include_public) From 49738eb28586cba48e3189bd17216964d34488f6 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Tue, 3 Mar 2026 10:43:02 +0100 Subject: [PATCH 268/286] Fixed ruff errors. --- source/app/datamgmt/manage/manage_cases_db.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/app/datamgmt/manage/manage_cases_db.py b/source/app/datamgmt/manage/manage_cases_db.py index e421f59d5..0c673836c 100644 --- a/source/app/datamgmt/manage/manage_cases_db.py +++ b/source/app/datamgmt/manage/manage_cases_db.py @@ -649,30 +649,30 @@ def get_filtered_cases(current_user_id, field_expr: Any = None if field_id == 'title': - field_expr = getattr(Cases, 'name') + field_expr = Cases.name elif field_id == 'case_id': - field_expr = cast(getattr(Cases, 'case_id'), String) + field_expr = cast(Cases.case_id, String) elif field_id == 'outcome': - field_expr = getattr(Cases, 'closing_note') + field_expr = Cases.closing_note elif field_id == 'open_date': - field_expr = cast(getattr(Cases, 'open_date'), String) + field_expr = cast(Cases.open_date, String) elif field_id == 'classification': - field_expr = cast(getattr(Cases, 'classification_id'), String) + field_expr = cast(Cases.classification_id, String) elif field_id == 'customer': if not joined_client: query = query.join(Client, Cases.client_id == Client.client_id) joined_client = True - field_expr = getattr(Client, 'name') + field_expr = Client.name elif field_id == 'state': if not joined_state: query = query.join(CaseState, Cases.state_id == CaseState.state_id) joined_state = True - field_expr = getattr(CaseState, 'state_name') + field_expr = CaseState.state_name elif field_id == 'owner': if not joined_owner: query = query.join(User, Cases.owner_id == User.id) joined_owner = True - field_expr = getattr(User, 'user') + field_expr = User.user if field_expr is None: continue From 164ff8f34c967433454d5f194cb1de4b46979020 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Thu, 5 Mar 2026 10:11:10 +0100 Subject: [PATCH 269/286] Added quick fix for alerts endpoint. --- source/app/blueprints/rest/v2/alerts.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index e77f1c45b..771d57816 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -166,7 +166,7 @@ def create(self): def read(self, identifier): try: - alert = alerts_get(iris_current_user, session['permissions'], identifier) + alert = alerts_get(iris_current_user, (session.get('permissions') or 0), identifier) return response_api_success(self._schema.dump(alert)) except ObjectNotFoundError: @@ -175,7 +175,7 @@ def read(self, identifier): def get_related_alerts(self, identifier): try: - alert = alerts_get(iris_current_user, session['permissions'], identifier) + alert = alerts_get(iris_current_user, (session.get('permissions') or 0), identifier) open_alerts = request.args.get('open-alerts', 'false').lower() == 'true' open_cases = request.args.get('open-cases', 'false').lower() == 'true' @@ -198,7 +198,7 @@ def get_related_alerts(self, identifier): def update(self, identifier): try: - alert = alerts_get(iris_current_user, session['permissions'], identifier) + alert = alerts_get(iris_current_user, (session.get('permissions') or 0), identifier) request_data = request.get_json() updated_alert = self._schema.load(request_data, instance=alert, partial=True) activity_data = [] @@ -232,7 +232,7 @@ def update(self, identifier): def delete(self, identifier): try: - alert = alerts_get(iris_current_user, session['permissions'], identifier) + alert = alerts_get(iris_current_user, (session.get('permissions') or 0), identifier) alerts_delete(alert) return response_api_deleted() From 50e82a5d49b614911e992ebbfe19fd7a6696c655 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Wed, 11 Mar 2026 09:13:18 +0100 Subject: [PATCH 270/286] Fixed permissions on alert comments V2. --- .../app/blueprints/rest/v2/alerts_routes/comments.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_routes/comments.py b/source/app/blueprints/rest/v2/alerts_routes/comments.py index e62c52778..433ab9bdb 100644 --- a/source/app/blueprints/rest/v2/alerts_routes/comments.py +++ b/source/app/blueprints/rest/v2/alerts_routes/comments.py @@ -51,7 +51,7 @@ def __init__(self): def search(self, alert_identifier): pagination_parameters = parse_pagination_parameters(request) try: - comments = comments_get_filtered_by_alert(iris_current_user, session['permissions'], alert_identifier, pagination_parameters) + comments = comments_get_filtered_by_alert(iris_current_user, (session.get('permissions') or 0), alert_identifier, pagination_parameters) return response_api_paginated(self._schema, comments) except ObjectNotFoundError: return response_api_not_found() @@ -59,7 +59,7 @@ def search(self, alert_identifier): def create(self, alert_identifier): try: comment = self._schema.load(request.get_json()) - comments_create_for_alert(iris_current_user, session['permissions'], comment, alert_identifier) + comments_create_for_alert(iris_current_user, (session.get('permissions') or 0), comment, alert_identifier) result = self._schema.dump(comment) return response_api_created(result) except ValidationError as e: @@ -69,7 +69,7 @@ def create(self, alert_identifier): def read(self, alert_identifier, identifier): try: - alert = alerts_get(iris_current_user, session['permissions'], alert_identifier) + alert = alerts_get(iris_current_user, (session.get('permissions') or 0), alert_identifier) comment = comments_get_for_alert(alert, identifier) result = self._schema.dump(comment) return response_api_success(result) @@ -79,13 +79,13 @@ def read(self, alert_identifier, identifier): return response_api_not_found() def update(self, alert_identifier, identifier): - if not alerts_exists(iris_current_user, session['permissions'], alert_identifier): + if not alerts_exists(iris_current_user, (session.get('permissions') or 0), alert_identifier): return response_api_not_found() return case_comment_update(identifier, 'events', None) def delete(self, alert_identifier, identifier): try: - alert = alerts_get(iris_current_user, session['permissions'], alert_identifier) + alert = alerts_get(iris_current_user, (session.get('permissions') or 0), alert_identifier) comment = comments_get_for_alert(alert, identifier) if comment.comment_user_id != iris_current_user.id: return ac_api_return_access_denied() From 4a7a1d90ea5369dc1ab6714574747bc3a3739c20 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Fri, 20 Mar 2026 12:41:04 +0100 Subject: [PATCH 271/286] Fixed relations rendering. --- source/app/datamgmt/alerts/alerts_db.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index f224b85db..7bd8500c3 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -1122,13 +1122,10 @@ def _get_open_alerts_status_identifiers(): def _get_alerts_status_identifiers(status_names): - open_alert_status_ids = AlertStatus.query.with_entities( + alert_status_ids = AlertStatus.query.with_entities( AlertStatus.status_id ).filter(AlertStatus.status_name.in_(status_names)).all() - result = [] - for status_id in open_alert_status_ids: - result.append(status_id) - return result + return [status_id for status_id, in alert_status_ids] def get_alert_comments(alert_id: int) -> List[Comments]: From 25badc7167a10c47e341d080f809332bee37e0e3 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Mon, 23 Mar 2026 18:07:31 +0100 Subject: [PATCH 272/286] Fixed related-alerts endpoint permissions. --- source/app/blueprints/rest/v2/alerts.py | 2 +- source/app/business/access_controls.py | 16 +++++++++++++++- source/app/business/alerts.py | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index 771d57816..41684d1f7 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -276,7 +276,7 @@ def delete_alert(identifier): return alerts_operations.delete(identifier) -@alerts_blueprint.get('/related-alerts') +@alerts_blueprint.get('//related-alerts') @ac_api_requires(Permissions.alerts_read) def get_related_alerts(identifier): return alerts_operations.get_related_alerts(identifier) diff --git a/source/app/business/access_controls.py b/source/app/business/access_controls.py index 3797f62cb..76e217c43 100644 --- a/source/app/business/access_controls.py +++ b/source/app/business/access_controls.py @@ -99,4 +99,18 @@ def access_controls_user_has_customer_access(user, permissions, customer_identif if ac_has_permission_server_administrator(permissions): return True - return user_has_client_access(user.id, customer_identifier) + user_id = getattr(user, 'id', None) + if user_id is None: + return False + + if user_has_client_access(user_id, customer_identifier): + return True + + if hasattr(user, 'is_authenticated') or hasattr(user, 'user'): + try: + from app.blueprints.access_controls import ac_current_user_has_customer_access + return ac_current_user_has_customer_access(customer_identifier) + except Exception: + return False + + return False diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 2bea4fc3c..0e271dd4d 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -290,7 +290,7 @@ def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, clos 'edges': [] } - in_dark_mode = user.in_dark_mode + in_dark_mode = getattr(user, 'in_dark_mode', False) alerts_dict = get_related_alerts_details(alert.alert_customer_id, assets, iocs, open_alerts, closed_alerts, days_back, number_of_results) return _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, alert.alert_customer_id, in_dark_mode) From 2924a376656d8550b329a7a7b12fa38055c7d40d Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Wed, 25 Mar 2026 13:35:26 +0100 Subject: [PATCH 273/286] Added cases to related alerts. --- source/app/blueprints/rest/v2/alerts.py | 26 +++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index 41684d1f7..9b5b09b89 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -178,9 +178,18 @@ def get_related_alerts(self, identifier): alert = alerts_get(iris_current_user, (session.get('permissions') or 0), identifier) open_alerts = request.args.get('open-alerts', 'false').lower() == 'true' - open_cases = request.args.get('open-cases', 'false').lower() == 'true' - closed_cases = request.args.get('closed-cases', 'false').lower() == 'true' closed_alerts = request.args.get('closed-alerts', 'false').lower() == 'true' + + open_cases_arg = request.args.get('open-cases') + closed_cases_arg = request.args.get('closed-cases') + + if open_cases_arg is None and closed_cases_arg is None: + open_cases = True + closed_cases = True + else: + open_cases = (open_cases_arg or 'false').lower() == 'true' + closed_cases = (closed_cases_arg or 'false').lower() == 'true' + days_back = request.args.get('days-back', 180, type=int) number_of_results = request.args.get('number-of-nodes', 100, type=int) @@ -189,8 +198,17 @@ def get_related_alerts(self, identifier): if days_back < 0: days_back = 180 - similar_alerts = alerts_get_related(iris_current_user, alert, open_alerts, closed_alerts, open_cases, - closed_cases, days_back, number_of_results) + similar_alerts = alerts_get_related( + iris_current_user, + alert, + open_alerts, + closed_alerts, + open_cases, + closed_cases, + days_back, + number_of_results + ) + return response_api_success(similar_alerts) except ObjectNotFoundError: From 2c532f5d437f08136964b182d5c5f249ccaacce5 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Fri, 27 Mar 2026 14:38:54 +0100 Subject: [PATCH 274/286] Fixed unlinking cases. --- source/app/schema/marshables.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 6c1ea8fa0..51a19168f 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -2240,6 +2240,14 @@ class Meta: unknown = EXCLUDE +class AlertCaseSchema(ma.Schema): + case_id: int = fields.Integer(required=True) + + @post_load + def make_case(self, data: Dict[str, Any], **kwargs: Any) -> Cases: + return Cases.query.filter(Cases.case_id == data.get('case_id')).first() + + class AlertSchema(ma.SQLAlchemyAutoSchema): """Schema for serializing and deserializing Alert objects. @@ -2255,6 +2263,7 @@ class AlertSchema(ma.SQLAlchemyAutoSchema): iocs = ma.Nested(IocSchema, many=True) assets = ma.Nested(CaseAssetsSchema, many=True, exclude=['alerts']) resolution_status = ma.Nested(AlertResolutionSchema) + cases = fields.Pluck('AlertCaseSchema', 'case_id', many=True, required=False) class Meta: model = Alert From d5894c63f6f018a819f02a57ad0dab1cf182ebb4 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Wed, 1 Apr 2026 17:31:18 +0200 Subject: [PATCH 275/286] Fixed updating reviewer and review status through API. --- source/app/blueprints/rest/v2/cases.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index 92bf2520b..e2f2761d9 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -58,6 +58,7 @@ from app.models.authorization import Permissions from app.models.authorization import CaseAccessLevel from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook +from app.db import db class CasesOperations: @@ -240,7 +241,7 @@ def filter(self) -> Response: def create(self): try: request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json()) - case = self._schema.load(request_data) + case = self._schema.load(request_data, session=db.session) case_template_id = request_data.pop('case_template_id', None) case = cases_create(iris_current_user, case, case_template_id) result = self._schema.dump(case) @@ -284,8 +285,14 @@ def update(self, identifier): reviewer_identifier = request_data.get('reviewer_id') if reviewer_identifier == '': request_data['reviewer_id'] = None - - updated_case = self._schema.load(request_data, instance=case, partial=True) + request_data.pop('review_status', None) + + updated_case = self._schema.load( + request_data, + instance=case, + partial=True, + session=db.session + ) protagonists = request_data.get('protagonists') tags = request_data.get('case_tags') From 04109e1381f810b53b7db8d34b179249d36bce52 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Thu, 2 Apr 2026 15:50:16 +0200 Subject: [PATCH 276/286] Fixed dim task routes auth. --- source/app/blueprints/rest/dim_tasks_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/dim_tasks_routes.py b/source/app/blueprints/rest/dim_tasks_routes.py index 91c5e6d8d..c08e0e623 100644 --- a/source/app/blueprints/rest/dim_tasks_routes.py +++ b/source/app/blueprints/rest/dim_tasks_routes.py @@ -43,8 +43,8 @@ @dim_tasks_rest_blueprint.route('/dim/hooks/call', methods=['POST']) -@ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() +@ac_requires_case_identifier(CaseAccessLevel.full_access) def dim_hooks_call(caseid): logs = [] js_data = request.json From 5be348586be89851e8391ff0d8cbbaf96ddf7d0a Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Tue, 7 Apr 2026 12:47:54 +0200 Subject: [PATCH 277/286] Fixed import issues. --- source/app/blueprints/rest/v2/cases.py | 25 ++++++++++++------------- source/app/business/access_controls.py | 7 ------- source/app/business/cases.py | 19 ++++++++++++------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index e2f2761d9..619b37a24 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -50,7 +50,6 @@ from app.business.cases import cases_filter from app.schema.marshables import CaseSchemaForAPIV2 from app.schema.marshables import CaseDetailsSchema -from app.datamgmt.manage.manage_cases_db import get_filtered_cases from app.blueprints.access_controls import ac_api_requires from app.blueprints.access_controls import ac_current_user_has_customer_access from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access @@ -204,23 +203,23 @@ def filter(self) -> Response: if type(draw) is not int: draw = 1 - filtered_cases = get_filtered_cases( - iris_current_user.id, + filtered_cases = cases_filter( + iris_current_user, pagination_parameters, - case_ids=case_ids_str, - case_customer_id=case_customer_id, case_name=case_name, - case_description=case_description, - case_classification_id=case_classification_id, - case_owner_id=case_owner_id, - case_opening_user_id=case_opening_user_id, - case_severity_id=case_severity_id, - case_state_id=case_state_id, - case_soc_id=case_soc_id, + case_identifiers=case_ids_str, + customer_identifier=case_customer_id, + description=case_description, + classification_identifier=case_classification_id, + owner_identifier=case_owner_id, + opening_user_identifier=case_opening_user_id, + severity_identifier=case_severity_id, + status_identifier=case_state_id, + soc_identifier=case_soc_id, start_open_date=start_open_date, end_open_date=end_open_date, - search_value=search_value, is_open=is_open, + search_value=search_value, advanced_filters=advanced_filters, advanced_logic=logic ) diff --git a/source/app/business/access_controls.py b/source/app/business/access_controls.py index 76e217c43..8bab2e77c 100644 --- a/source/app/business/access_controls.py +++ b/source/app/business/access_controls.py @@ -106,11 +106,4 @@ def access_controls_user_has_customer_access(user, permissions, customer_identif if user_has_client_access(user_id, customer_identifier): return True - if hasattr(user, 'is_authenticated') or hasattr(user, 'user'): - try: - from app.blueprints.access_controls import ac_current_user_has_customer_access - return ac_current_user_has_customer_access(customer_identifier) - except Exception: - return False - return False diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 5ffc80433..8ac55bd32 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -60,11 +60,14 @@ from app.models.customers import Client -def cases_filter(current_user, pagination_parameters, name, case_identifiers, customer_identifier, - description, classification_identifier, owner_identifier, opening_user_identifier, - severity_identifier, status_identifier, soc_identifier, - start_open_date, end_open_date, is_open): - return get_filtered_cases(current_user.id, pagination_parameters, +def cases_filter(current_user, pagination_parameters, name=None, case_identifiers=None, customer_identifier=None, + description=None, classification_identifier=None, owner_identifier=None, opening_user_identifier=None, + severity_identifier=None, status_identifier=None, soc_identifier=None, + start_open_date=None, end_open_date=None, is_open=None, search_value='', + advanced_filters=None, advanced_logic='and'): + return get_filtered_cases( + current_user.id, + pagination_parameters, start_open_date, end_open_date, customer_identifier, @@ -77,8 +80,10 @@ def cases_filter(current_user, pagination_parameters, name, case_identifiers, cu severity_identifier, status_identifier, soc_identifier, - search_value='', - is_open=is_open) + search_value=search_value, + is_open=is_open, + advanced_filters=advanced_filters, + advanced_logic=advanced_logic) def cases_filter_by_user(user, show_all: bool): From 551e6a9e7cf24fe78a9fa773190200951062523a Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Tue, 7 Apr 2026 15:27:06 +0200 Subject: [PATCH 278/286] Better pluck for AlertCaseSchema. --- source/app/schema/marshables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 51a19168f..4c039e086 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -2263,7 +2263,7 @@ class AlertSchema(ma.SQLAlchemyAutoSchema): iocs = ma.Nested(IocSchema, many=True) assets = ma.Nested(CaseAssetsSchema, many=True, exclude=['alerts']) resolution_status = ma.Nested(AlertResolutionSchema) - cases = fields.Pluck('AlertCaseSchema', 'case_id', many=True, required=False) + cases = fields.Pluck(AlertCaseSchema, 'case_id', many=True, required=False) class Meta: model = Alert From f240cf865165d80a3bfb68dc411581035cd8449a Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Tue, 7 Apr 2026 18:39:17 +0200 Subject: [PATCH 279/286] Fixed access to related-alerts. --- source/app/blueprints/rest/v2/alerts.py | 21 ++++++++++++++++++--- source/app/business/access_controls.py | 13 ++++++++++++- source/app/business/alerts.py | 17 +++++++++++------ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index 9b5b09b89..1245fe2ba 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -166,7 +166,12 @@ def create(self): def read(self, identifier): try: - alert = alerts_get(iris_current_user, (session.get('permissions') or 0), identifier) + alert = alerts_get( + iris_current_user, + (session.get('permissions') or 0), + identifier, + fallback_customer_access=ac_current_user_has_customer_access + ) return response_api_success(self._schema.dump(alert)) except ObjectNotFoundError: @@ -175,7 +180,12 @@ def read(self, identifier): def get_related_alerts(self, identifier): try: - alert = alerts_get(iris_current_user, (session.get('permissions') or 0), identifier) + alert = alerts_get( + iris_current_user, + (session.get('permissions') or 0), + identifier, + fallback_customer_access=ac_current_user_has_customer_access + ) open_alerts = request.args.get('open-alerts', 'false').lower() == 'true' closed_alerts = request.args.get('closed-alerts', 'false').lower() == 'true' @@ -250,7 +260,12 @@ def update(self, identifier): def delete(self, identifier): try: - alert = alerts_get(iris_current_user, (session.get('permissions') or 0), identifier) + alert = alerts_get( + iris_current_user, + (session.get('permissions') or 0), + identifier, + fallback_customer_access=ac_current_user_has_customer_access + ) alerts_delete(alert) return response_api_deleted() diff --git a/source/app/business/access_controls.py b/source/app/business/access_controls.py index 8bab2e77c..bf7bb4ec3 100644 --- a/source/app/business/access_controls.py +++ b/source/app/business/access_controls.py @@ -95,7 +95,12 @@ def ac_fast_check_user_has_case_access(user_id, cid, expected_access_levels: lis return None -def access_controls_user_has_customer_access(user, permissions, customer_identifier): +def access_controls_user_has_customer_access( + user, + permissions, + customer_identifier, + fallback_customer_access=None +): if ac_has_permission_server_administrator(permissions): return True @@ -106,4 +111,10 @@ def access_controls_user_has_customer_access(user, permissions, customer_identif if user_has_client_access(user_id, customer_identifier): return True + if fallback_customer_access and (hasattr(user, 'is_authenticated') or hasattr(user, 'user')): + try: + return fallback_customer_access(customer_identifier) + except Exception: + return False + return False diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index 0e271dd4d..e93321b8d 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -98,17 +98,22 @@ def alerts_create(alert: Alert, iocs: list[Ioc], assets: list[CaseAssets]) -> Al return alert -def _get(user, permissions, identifier) -> Optional[Alert]: +def _get(user, permissions, identifier, fallback_customer_access=None) -> Optional[Alert]: alert = get_alert_by_id(identifier) if not alert: return None - if not access_controls_user_has_customer_access(user, permissions, alert.alert_customer_id): + if not access_controls_user_has_customer_access( + user, + permissions, + alert.alert_customer_id, + fallback_customer_access=fallback_customer_access + ): return None return alert -def alerts_get(user, permissions, identifier) -> Alert: - alert = _get(user, permissions, identifier) +def alerts_get(user, permissions, identifier, fallback_customer_access=None) -> Alert: + alert = _get(user, permissions, identifier, fallback_customer_access=fallback_customer_access) if not alert: raise ObjectNotFoundError() return alert @@ -296,8 +301,8 @@ def alerts_get_related(user, alert, open_alerts, closed_alerts, open_cases, clos return _build_related_alerts_graph(alerts_dict, open_cases, closed_cases, alert.alert_customer_id, in_dark_mode) -def alerts_exists(user, permissions, identifier) -> bool: - alert = _get(user, permissions, identifier) +def alerts_exists(user, permissions, identifier, fallback_customer_access=None) -> bool: + alert = _get(user, permissions, identifier, fallback_customer_access=fallback_customer_access) return alert is not None From beb4e841baf0ba9fa120ba180c25306c68132406 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Wed, 8 Apr 2026 11:12:26 +0200 Subject: [PATCH 280/286] Fixed permissions on alert comments. --- source/app/blueprints/rest/v2/alerts.py | 7 ++++- .../rest/v2/alerts_routes/comments.py | 31 ++++++++++++++++--- source/app/business/comments.py | 8 ++--- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index 1245fe2ba..b3a4f3d21 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -226,7 +226,12 @@ def get_related_alerts(self, identifier): def update(self, identifier): try: - alert = alerts_get(iris_current_user, (session.get('permissions') or 0), identifier) + alert = alerts_get( + iris_current_user, + (session.get('permissions') or 0), + identifier, + fallback_customer_access=ac_current_user_has_customer_access + ) request_data = request.get_json() updated_alert = self._schema.load(request_data, instance=alert, partial=True) activity_data = [] diff --git a/source/app/blueprints/rest/v2/alerts_routes/comments.py b/source/app/blueprints/rest/v2/alerts_routes/comments.py index 433ab9bdb..84c92d629 100644 --- a/source/app/blueprints/rest/v2/alerts_routes/comments.py +++ b/source/app/blueprints/rest/v2/alerts_routes/comments.py @@ -22,6 +22,7 @@ from marshmallow.exceptions import ValidationError from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_customer_access from app.models.authorization import Permissions from app.blueprints.rest.endpoints import response_api_paginated from app.blueprints.rest.endpoints import response_api_success @@ -51,7 +52,13 @@ def __init__(self): def search(self, alert_identifier): pagination_parameters = parse_pagination_parameters(request) try: - comments = comments_get_filtered_by_alert(iris_current_user, (session.get('permissions') or 0), alert_identifier, pagination_parameters) + comments = comments_get_filtered_by_alert( + iris_current_user, + (session.get('permissions') or 0), + alert_identifier, + pagination_parameters, + fallback_customer_access=ac_current_user_has_customer_access + ) return response_api_paginated(self._schema, comments) except ObjectNotFoundError: return response_api_not_found() @@ -59,7 +66,13 @@ def search(self, alert_identifier): def create(self, alert_identifier): try: comment = self._schema.load(request.get_json()) - comments_create_for_alert(iris_current_user, (session.get('permissions') or 0), comment, alert_identifier) + comments_create_for_alert( + iris_current_user, + (session.get('permissions') or 0), + comment, + alert_identifier, + fallback_customer_access=ac_current_user_has_customer_access + ) result = self._schema.dump(comment) return response_api_created(result) except ValidationError as e: @@ -69,7 +82,12 @@ def create(self, alert_identifier): def read(self, alert_identifier, identifier): try: - alert = alerts_get(iris_current_user, (session.get('permissions') or 0), alert_identifier) + alert = alerts_get( + iris_current_user, + (session.get('permissions') or 0), + alert_identifier, + fallback_customer_access=ac_current_user_has_customer_access + ) comment = comments_get_for_alert(alert, identifier) result = self._schema.dump(comment) return response_api_success(result) @@ -85,7 +103,12 @@ def update(self, alert_identifier, identifier): def delete(self, alert_identifier, identifier): try: - alert = alerts_get(iris_current_user, (session.get('permissions') or 0), alert_identifier) + alert = alerts_get( + iris_current_user, + (session.get('permissions') or 0), + alert_identifier, + fallback_customer_access=ac_current_user_has_customer_access + ) comment = comments_get_for_alert(alert, identifier) if comment.comment_user_id != iris_current_user.id: return ac_api_return_access_denied() diff --git a/source/app/business/comments.py b/source/app/business/comments.py index de06bd162..03c30e3dd 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -67,8 +67,8 @@ from app.models.alerts import Alert -def comments_get_filtered_by_alert(current_user, permissions, alert_identifier: int, pagination_parameters: PaginationParameters) -> Pagination: - if not alerts_exists(current_user, permissions, alert_identifier): +def comments_get_filtered_by_alert(current_user, permissions, alert_identifier: int, pagination_parameters: PaginationParameters, fallback_customer_access=None) -> Pagination: + if not alerts_exists(current_user, permissions, alert_identifier, fallback_customer_access): raise ObjectNotFoundError() return get_filtered_alert_comments(alert_identifier, pagination_parameters) @@ -121,8 +121,8 @@ def comments_update_for_case(current_user, comment_text, comment_id, object_type return comment -def comments_create_for_alert(current_user, permissions, comment: Comments, alert_identifier: int): - alert = alerts_get(current_user, permissions, alert_identifier) +def comments_create_for_alert(current_user, permissions, comment: Comments, alert_identifier: int, fallback_customer_access=None): + alert = alerts_get(current_user, permissions, alert_identifier, fallback_customer_access) comment.comment_alert_id = alert_identifier comment.comment_user_id = current_user.id comment.comment_date = datetime.now() From a9b194d5b77b90787b47b4e935b3a89a59b4b966 Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Mon, 13 Apr 2026 15:25:11 +0200 Subject: [PATCH 281/286] Added endpoints to list and search notes in V2 Rest API. --- .../blueprints/rest/v2/case_routes/notes.py | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/case_routes/notes.py b/source/app/blueprints/rest/v2/case_routes/notes.py index 51fb8a538..9d6081d91 100644 --- a/source/app/blueprints/rest/v2/case_routes/notes.py +++ b/source/app/blueprints/rest/v2/case_routes/notes.py @@ -35,6 +35,7 @@ from app.business.notes import notes_get from app.business.notes import notes_update from app.business.notes import notes_delete +from app.business.notes import notes_search from app.business.cases import cases_exists from app.models.errors import BusinessProcessingError from app.models.errors import ObjectNotFoundError @@ -58,6 +59,63 @@ def _check_note_and_case_identifier_match(note: Notes, case_identifier): if note.note_case_id != case_identifier: raise ObjectNotFoundError + @staticmethod + def _check_case_access(case_identifier, access_levels): + if not cases_exists(case_identifier): + return response_api_not_found() + + if not ac_fast_check_current_user_has_case_access(case_identifier, access_levels): + return ac_api_return_access_denied(caseid=case_identifier) + + return None + + def list(self, case_identifier): + access_error = self._check_case_access( + case_identifier, + [CaseAccessLevel.read_only, CaseAccessLevel.full_access] + ) + if access_error: + return access_error + + try: + notes = ( + Notes.query + .filter(Notes.note_case_id == case_identifier) + .order_by(Notes.note_id.asc()) + .all() + ) + + result = self._schema.dump(notes, many=True) + return response_api_success(result) + + 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 search(self, case_identifier): + access_error = self._check_case_access( + case_identifier, + [CaseAccessLevel.read_only, CaseAccessLevel.full_access] + ) + if access_error: + return access_error + + try: + search_input = request.args.get('search_input') + + notes = notes_search(case_identifier, search_input) + + result = self._schema.dump(notes, many=True) + return response_api_success(result) + + 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 create(self, case_identifier): if not cases_exists(case_identifier): return response_api_not_found() @@ -146,6 +204,18 @@ def delete(self, case_identifier, identifier): url_prefix='//notes') +@case_notes_blueprint.get('') +@ac_api_requires() +def list_notes(case_identifier): + return notes_operations.list(case_identifier) + + +@case_notes_blueprint.get('/search') +@ac_api_requires() +def search_notes(case_identifier): + return notes_operations.search(case_identifier) + + @case_notes_blueprint.post('') @ac_api_requires() def create_note(case_identifier): @@ -158,13 +228,13 @@ def get_note(case_identifier, identifier): return notes_operations.get(case_identifier, identifier) -@case_notes_blueprint.put('') +@case_notes_blueprint.put('/') @ac_api_requires() def update_note(case_identifier, identifier): return notes_operations.update(case_identifier, identifier) -@case_notes_blueprint.delete('') +@case_notes_blueprint.delete('/') @ac_api_requires() def delete_note(case_identifier, identifier): return notes_operations.delete(case_identifier, identifier) From 2218a7d1b52e4b9d045c4ddccd23fc297287ca4a Mon Sep 17 00:00:00 2001 From: fatpeppapig Date: Fri, 17 Apr 2026 11:34:50 +0200 Subject: [PATCH 282/286] Fixed case filters. --- source/app/blueprints/rest/v2/cases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index 619b37a24..ab65c3a07 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -206,7 +206,7 @@ def filter(self) -> Response: filtered_cases = cases_filter( iris_current_user, pagination_parameters, - case_name=case_name, + name=case_name, case_identifiers=case_ids_str, customer_identifier=case_customer_id, description=case_description, From f5d613c5886c6a421b761a6701a662ebe5361741 Mon Sep 17 00:00:00 2001 From: whitekernel <74464599+whikernel@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:57:41 +0200 Subject: [PATCH 283/286] [IMP] Improved search in notes Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- source/app/blueprints/rest/v2/case_routes/notes.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/v2/case_routes/notes.py b/source/app/blueprints/rest/v2/case_routes/notes.py index 9d6081d91..5b216f4d8 100644 --- a/source/app/blueprints/rest/v2/case_routes/notes.py +++ b/source/app/blueprints/rest/v2/case_routes/notes.py @@ -104,8 +104,13 @@ def search(self, case_identifier): try: search_input = request.args.get('search_input') + if search_input is None or not search_input.strip(): + return response_api_error( + 'Data error', + data={'search_input': ['Missing or blank search_input query parameter']} + ) - notes = notes_search(case_identifier, search_input) + notes = notes_search(case_identifier, search_input.strip()) result = self._schema.dump(notes, many=True) return response_api_success(result) From c0d1e340be4d642e2c279519589d5d68b0b2720a Mon Sep 17 00:00:00 2001 From: Paul Amicelli Date: Fri, 17 Apr 2026 10:48:05 +0200 Subject: [PATCH 284/286] [FIX] Fixed unsafe direct access to depreceated endpoints - cf GHSA-97ph-cq32-jw7g --- .../blueprints/rest/case/case_assets_routes.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/case/case_assets_routes.py b/source/app/blueprints/rest/case/case_assets_routes.py index 1cb93b1a1..8eb8f1dcb 100644 --- a/source/app/blueprints/rest/case/case_assets_routes.py +++ b/source/app/blueprints/rest/case/case_assets_routes.py @@ -31,6 +31,7 @@ from app.business.assets import assets_update from app.blueprints.iris_user import iris_current_user from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.case_assets_db import get_raw_assets from app.datamgmt.case.case_assets_db import get_linked_iocs_finfo_from_asset from app.datamgmt.case.case_assets_db import add_comment_to_asset @@ -285,6 +286,8 @@ def deprecated_asset_view(cur_id, caseid): try: asset = assets_get(cur_id) + if asset.case_id != caseid: + return response_error('Invalid asset ID for this case') # TODO this is a code smell: shouldn't have schemas in the business layer + the CaseAssetsSchema is instantiated twice case_assets_schema = CaseAssetsSchema() data = case_assets_schema.dump(asset) @@ -292,6 +295,8 @@ def deprecated_asset_view(cur_id, caseid): data['linked_ioc'] = [row._asdict() for row in asset_iocs] return response_success(msg='Asset added', data=data) + except ObjectNotFoundError: + return response_error('Invalid asset ID for this case') except BusinessProcessingError as e: return response_error(e.get_message()) @@ -303,7 +308,7 @@ def deprecated_asset_view(cur_id, caseid): def asset_update(cur_id, caseid): try: asset = get_asset(cur_id) - if not asset: + if not asset or asset.case_id != caseid: return response_error("Invalid asset ID for this case") request_data = call_modules_hook('on_preload_asset_update', request.get_json(), caseid=caseid) @@ -341,6 +346,10 @@ def deprecated_asset_delete(cur_id, caseid): @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access) @ac_api_requires() def case_comment_asset_list(cur_id, caseid): + asset = get_asset(cur_id) + if not asset or asset.case_id != caseid: + return response_error('Invalid asset ID') + asset_comments = get_case_asset_comments(cur_id) if asset_comments is None: return response_error('Invalid asset ID') @@ -355,7 +364,7 @@ def case_comment_asset_list(cur_id, caseid): def case_comment_asset_add(cur_id, caseid): try: asset = get_asset(cur_id) - if not asset: + if not asset or asset.case_id != caseid: return response_error('Invalid asset ID') comment_schema = CommentSchema() @@ -390,6 +399,10 @@ def case_comment_asset_add(cur_id, caseid): @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access) @ac_api_requires() def case_comment_asset_get(cur_id, com_id, caseid): + asset = get_asset(cur_id) + if not asset or asset.case_id != caseid: + return response_error("Invalid comment ID") + comment = get_case_asset_comment(cur_id, com_id) if not comment: return response_error("Invalid comment ID") From 3efae01044b99c27d5f2467ab5294e906f26a666 Mon Sep 17 00:00:00 2001 From: whitekernel Date: Wed, 22 Apr 2026 11:39:59 +0200 Subject: [PATCH 285/286] [ADD] Added datastore handler for v2 --- source/app/blueprints/rest/v2/cases.py | 2 ++ .../socket_io_event_handlers/case_notes_event_handlers.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index ab65c3a07..6e3f57846 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -41,6 +41,7 @@ from app.blueprints.rest.v2.case_routes.tasks import case_tasks_blueprint from app.blueprints.rest.v2.case_routes.evidences import case_evidences_blueprint from app.blueprints.rest.v2.case_routes.events import case_events_blueprint +from app.blueprints.rest.v2.case_routes.datastore import case_datastore_blueprint from app.blueprints.iris_user import iris_current_user from app.business.cases import cases_create from app.business.cases import cases_delete @@ -325,6 +326,7 @@ def delete(self, identifier): cases_blueprint.register_blueprint(case_tasks_blueprint) cases_blueprint.register_blueprint(case_evidences_blueprint) cases_blueprint.register_blueprint(case_events_blueprint) +cases_blueprint.register_blueprint(case_datastore_blueprint) cases_operations = CasesOperations() diff --git a/source/app/blueprints/socket_io_event_handlers/case_notes_event_handlers.py b/source/app/blueprints/socket_io_event_handlers/case_notes_event_handlers.py index 149572663..02b5081b6 100644 --- a/source/app/blueprints/socket_io_event_handlers/case_notes_event_handlers.py +++ b/source/app/blueprints/socket_io_event_handlers/case_notes_event_handlers.py @@ -30,14 +30,14 @@ def socket_change_note(data): data['last_change'] = iris_current_user.user - emit('change-note', data, to=data['channel'], skip_sid=request.sid, room=data['channel']) + emit('change-note', data, to=data['channel'], skip_sid=request.sid) @ac_socket_requires(CaseAccessLevel.full_access) def socket_save_note(data): data['last_saved'] = iris_current_user.user - emit('save-note', data, to=data['channel'], skip_sid=request.sid, room=data['channel']) + emit('save-note', data, to=data['channel'], skip_sid=request.sid) @ac_socket_requires(CaseAccessLevel.full_access) From 2419b1c1bc576cd4c1a33c23ff0cee5814f5e8f7 Mon Sep 17 00:00:00 2001 From: whitekernel Date: Wed, 22 Apr 2026 11:40:18 +0200 Subject: [PATCH 286/286] [ADD] Mising v2 DS file --- .../rest/v2/case_routes/datastore.py | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 source/app/blueprints/rest/v2/case_routes/datastore.py diff --git a/source/app/blueprints/rest/v2/case_routes/datastore.py b/source/app/blueprints/rest/v2/case_routes/datastore.py new file mode 100644 index 000000000..65476e45f --- /dev/null +++ b/source/app/blueprints/rest/v2/case_routes/datastore.py @@ -0,0 +1,159 @@ +# 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. + +import base64 +import marshmallow.exceptions +from flask import Blueprint +from flask import request +from flask import send_file +from pathlib import Path + +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.access_controls import ac_api_return_access_denied +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_not_found +from app.business.cases import cases_exists +from app.datamgmt.datastore.datastore_db import datastore_get_file +from app.datamgmt.datastore.datastore_db import datastore_get_interactive_path_node +from app.datamgmt.datastore.datastore_db import datastore_get_local_file_path +from app.iris_engine.utils.tracker import track_activity +from app.models.authorization import CaseAccessLevel +from app.schema.marshables import DSFileSchema + + +class DatastoreOperations: + """Case-scoped datastore file operations used by the rich-text editor + (inline image uploads embedded in notes and case summaries). + + Only the two endpoints the editor actually needs are exposed here: + the interactive upload and the file view. The broader tree / folder + management still lives on the legacy `datastore_rest_blueprint` until + that UI is migrated too. + """ + + @staticmethod + def _check_case_access(case_identifier, access_levels): + if not cases_exists(case_identifier): + return response_api_not_found() + + if not ac_fast_check_current_user_has_case_access(case_identifier, access_levels): + return ac_api_return_access_denied(caseid=case_identifier) + + return None + + def add_interactive(self, case_identifier): + access_error = self._check_case_access(case_identifier, [CaseAccessLevel.full_access]) + if access_error: + return access_error + + dsp = datastore_get_interactive_path_node(case_identifier) + if not dsp: + return response_api_error('Invalid path node for this case') + + dsf_schema = DSFileSchema() + try: + js_data = request.get_json() or {} + + try: + file_content = base64.b64decode(js_data.get('file_content')) + filename = js_data.get('file_original_name') + except Exception as e: + return response_api_error(str(e)) + + if not filename: + return response_api_error('Data error', data={'file_original_name': ['Missing filename']}) + + dsf_sc, existed = dsf_schema.ds_store_file_b64(filename, file_content, dsp, case_identifier) + + track_activity( + f'File "{dsf_sc.file_original_name}" added to DS', + caseid=case_identifier + ) + + # URL is returned relative to /api/v2 so the frontend can embed it + # directly as an . It's a REST path the browser can GET + # without any further rewriting — `cid` is part of the path. + return response_api_created({ + 'existed': existed, + 'file_url': f'/api/v2/cases/{case_identifier}/datastore/files/{dsf_sc.file_id}', + **dsf_schema.dump(dsf_sc) + }) + + except marshmallow.exceptions.ValidationError as e: + return response_api_error('Data error', data=e.messages) + + def view(self, case_identifier, identifier): + access_error = self._check_case_access( + case_identifier, + [CaseAccessLevel.read_only, CaseAccessLevel.full_access] + ) + if access_error: + return access_error + + # Sanity-check that the file actually belongs to this case before + # resolving its on-disk location. `datastore_get_local_file_path` + # already filters by case id, but this keeps 404s consistent with + # the rest of the v2 case routes. + if not datastore_get_file(identifier, case_identifier): + return response_api_not_found() + + has_error, dsf = datastore_get_local_file_path(identifier, case_identifier) + if has_error: + return response_api_error('Unable to get requested file ID', data=dsf) + + if dsf.file_is_ioc or dsf.file_password: + destination_name = dsf.file_original_name + '.zip' + else: + destination_name = dsf.file_original_name + + if not Path(dsf.file_local_name).is_file(): + return response_api_error( + f'File {dsf.file_local_name} does not exist on the server. ' + f'Update or delete virtual entry' + ) + + resp = send_file(dsf.file_local_name, as_attachment=False, download_name=destination_name) + + track_activity( + f'File "{destination_name}" downloaded', + caseid=case_identifier, + display_in_ui=False + ) + return resp + + +datastore_operations = DatastoreOperations() +case_datastore_blueprint = Blueprint( + 'case_datastore_rest_v2', + __name__, + url_prefix='//datastore' +) + + +@case_datastore_blueprint.post('/files/interactive') +@ac_api_requires() +def datastore_add_interactive_file(case_identifier): + return datastore_operations.add_interactive(case_identifier) + + +@case_datastore_blueprint.get('/files/') +@ac_api_requires() +def datastore_view_file(case_identifier, identifier): + return datastore_operations.view(case_identifier, identifier)