Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4604cd5
One import per line
c8y3 Sep 5, 2025
559c152
Started implementation of POST /api/v2/alerts/{object_identifier}/com…
c8y3 Sep 5, 2025
da7830e
POST /api/v2/alerts/{object_identifier}/comments should set comment_text
c8y3 Sep 5, 2025
0ef9c57
POST /api/v2/alerts/{object_identifier}/comments should set comment_a…
c8y3 Sep 5, 2025
16bf676
POST /api/v2/alerts/{object_identifier}/comments should set comment_u…
c8y3 Sep 5, 2025
f99dbda
POST /api/v2/alerts/{object_identifier}/comments should add an histor…
c8y3 Sep 5, 2025
2b417a0
Factored some testing utilities for modules and hooks
c8y3 Sep 5, 2025
9574766
POST /api/v2/alerts/{object_identifier}/comments should send a module…
c8y3 Sep 5, 2025
3a1c273
POST /api/v2/alerts/{object_identifier}/comments should send track ac…
c8y3 Sep 5, 2025
cd5af30
POST /api/v2/alerts/{object_identifier}/comments should return 404 wh…
c8y3 Sep 5, 2025
b03a688
POST /api/v2/alerts/{object_identifier}/comments should return 400 wh…
c8y3 Sep 5, 2025
97fdd28
Started POST /api/v2/assets/{object_identifier}/comments
c8y3 Sep 8, 2025
1b7669c
Factored code of alerts somewhat
c8y3 Sep 8, 2025
b879318
POST /api/v2/assets/{object_identifier}/comments sets comment's text
c8y3 Sep 8, 2025
cb81bac
POST /api/v2/assets/{object_identifier}/comments implemented 400 and 404
c8y3 Sep 8, 2025
6ce6f58
GET /api/v2/assets/{object_identifier}/comments should return 404, ra…
c8y3 Sep 8, 2025
4bd52fd
Implementation of GET /api/v2/evidences/{object_identifier}/comments
c8y3 Sep 9, 2025
5f48689
Removed import of iris_current_user in business/comments (should not …
c8y3 Sep 9, 2025
bf7b08d
Use objects instead of identifiers
c8y3 Sep 9, 2025
ddc239d
Fixed incorrect argument passed
c8y3 Sep 9, 2025
21356d3
Code cleanup
c8y3 Sep 9, 2025
7c189c7
Code cleanup
c8y3 Sep 9, 2025
04ee659
Removed seemingly unused method
c8y3 Sep 9, 2025
e9c15b5
Removed unused imports
c8y3 Sep 9, 2025
f68b34d
Moved code down into the persistence layer
c8y3 Sep 9, 2025
18f5a1f
Changed back type to int (Ideally, it should be a list of CaseAccessL…
c8y3 Sep 9, 2025
7d3adf6
Removed unused parameter
c8y3 Sep 9, 2025
5b1a351
Moved checks up in API layer
c8y3 Sep 9, 2025
b166162
Used logger
c8y3 Sep 9, 2025
1031b31
More precise return type
c8y3 Sep 10, 2025
1ffc405
Create comments is allowed only with write case access
c8y3 Sep 10, 2025
8477e36
Create comments is allowed only with write case access
c8y3 Sep 10, 2025
9d2aa34
POST /api/v2/iocs/{object_identifier}/comments
c8y3 Sep 10, 2025
bb68852
POST /api/v2/notes/{object_identifier}/comments
c8y3 Sep 10, 2025
09029aa
Deleting a case should delete associated comment
c8y3 Sep 10, 2025
a976c39
Fixed missing argument when calling function
c8y3 Sep 10, 2025
c96b9c6
Fixed deletion of case when there are comments on assets
c8y3 Sep 10, 2025
bb9f43f
Deleting a case, does not incorrectly delete comments on iocs in anot…
c8y3 Sep 10, 2025
9dd165d
Fixed deletion of case with evidence comments
c8y3 Sep 10, 2025
59fa6b4
Fixed deletion of case with notes comments
c8y3 Sep 10, 2025
d1d4738
POST /api/v2/tasks/{object_identifier}/comments
c8y3 Sep 10, 2025
0b7214b
Delete tasks comments when deleting a case
c8y3 Sep 10, 2025
37edc1a
Factored code somewhat
c8y3 Sep 10, 2025
f1a8576
commit seems to be necessary
c8y3 Sep 10, 2025
353e339
POST /api/v2/events/{object_identifier}/comments
c8y3 Sep 10, 2025
dbaf980
Deletion of case with events comments does not fail
c8y3 Sep 10, 2025
d797adb
Fixed some ruff warnings
c8y3 Sep 10, 2025
a0414aa
Moved out definition of tables related to Comments in their own model…
c8y3 Sep 10, 2025
c751620
After test generate log file and upload as an artifact
c8y3 Sep 12, 2025
977590b
Deprecated previous endpoints to create comments
c8y3 Sep 12, 2025
aa1e580
Forgot to redirect file
c8y3 Sep 12, 2025
61a93b1
Renamed CI artifact so that it doesn't conflict
c8y3 Sep 12, 2025
943a136
Clean: more precise return type
c8y3 Sep 12, 2025
798036d
Avoid unnecessary iteration
c8y3 Sep 12, 2025
5e54cab
Started to move access_control method out of namespace iris_engine in…
c8y3 Sep 12, 2025
a3e5979
Moved function ac_flag_match_mask out of iris_engine namespace down i…
c8y3 Sep 12, 2025
d416cdf
Moved functions out of iris_engine.access_control.utils into the busi…
c8y3 Sep 12, 2025
374dddb
Split code correctly between business and persistence layer
c8y3 Sep 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,13 @@ jobs:
PYTHONUNBUFFERED=true python -m unittest --verbose
- name: Stop development server
run: |
docker compose logs app > ${{ runner.temp }}/iriswebapp_app.log
docker compose down
- name: Upload artifact
Comment on lines 186 to +190
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make log capture resilient and step always-run

Logs step can fail if the service isn’t named “app,” causing the stop step to error and skip teardown. Add always-run, fallback to all services, quote paths, add useful flags, and reference the same compose file used for up.

-      - name: Stop development server
-        run: |
-          docker compose logs app > ${{ runner.temp }}/iriswebapp_app.log
-          docker compose down
+      - name: Stop development server
+        if: ${{ always() }}
+        run: |
+          docker compose -f docker-compose.dev.yml logs --no-color --timestamps --tail=5000 app > "${{ runner.temp }}/iriswebapp_app.log" \
+            || docker compose -f docker-compose.dev.yml logs --no-color --timestamps --tail=5000 > "${{ runner.temp }}/iriswebapp_app.log" || true
+          docker compose -f docker-compose.dev.yml down
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Stop development server
run: |
docker compose logs app > ${{ runner.temp }}/iriswebapp_app.log
docker compose down
- name: Upload artifact
- name: Stop development server
if: ${{ always() }}
run: |
docker compose -f docker-compose.dev.yml logs --no-color --timestamps --tail=5000 app > "${{ runner.temp }}/iriswebapp_app.log" \
|| docker compose -f docker-compose.dev.yml logs --no-color --timestamps --tail=5000 > "${{ runner.temp }}/iriswebapp_app.log" || true
docker compose -f docker-compose.dev.yml down
- name: Upload artifact
🤖 Prompt for AI Agents
In .github/workflows/ci.yml around lines 186-190, the "Stop development server"
step is brittle: make it always run (use if: always()), reference the same
compose file used for "up" via -f with the path, quote the artifact path when
redirecting logs, capture logs for the named service but fall back to all
services (e.g., attempt docker compose logs app || docker compose logs --all),
add useful flags like --no-color and --timestamps to logs, and ensure docker
compose down also uses the same -f and runs even if logs fail; then keep the
subsequent "Upload artifact" step unchanged.

uses: actions/upload-artifact@v4
with:
name: Test API iriswebapp_app logs
path: ${{ runner.temp }}/iriswebapp_app.log
Comment on lines +191 to +194
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure artifact upload always executes and doesn’t fail job if file missing

Run regardless of previous failures and avoid hard-failing when the log file isn’t present; set a short retention.

-      - name: Upload artifact
-        uses: actions/upload-artifact@v4
-        with:
-          name: Test API iriswebapp_app logs
-          path: ${{ runner.temp }}/iriswebapp_app.log
+      - name: Upload artifact
+        if: ${{ always() }}
+        uses: actions/upload-artifact@v4
+        with:
+          name: Test API iriswebapp_app logs
+          path: ${{ runner.temp }}/iriswebapp_app.log
+          if-no-files-found: ignore
+          retention-days: 7
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uses: actions/upload-artifact@v4
with:
name: Test API iriswebapp_app logs
path: ${{ runner.temp }}/iriswebapp_app.log
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: Test API iriswebapp_app logs
path: ${{ runner.temp }}/iriswebapp_app.log
if-no-files-found: ignore
retention-days: 7
🤖 Prompt for AI Agents
In .github/workflows/ci.yml around lines 191 to 194, the upload-artifact step
should always run and not fail the job if the log file is missing; add an if:
always() condition to the step, and in the with: block set if-no-files-found:
'ignore' (or 'warn') and add a short retention-days value (e.g. 1 or 3) so
missing files don’t hard-fail and artifacts are kept only briefly.


test-database-migration:
name: Database migration tests
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/access_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from app.datamgmt.manage.manage_access_control_db import user_has_client_access
from app.datamgmt.manage.manage_users_db import get_user
from app.iris_engine.access_control.iris_user import iris_current_user
from app.iris_engine.access_control.utils import ac_fast_check_user_has_case_access
from app.business.access_controls import ac_fast_check_user_has_case_access
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
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/graphql/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from app.blueprints.access_controls import get_case_access_from_api
from app.iris_engine.access_control.iris_user import iris_current_user
from app.iris_engine.access_control.utils import ac_get_effective_permissions_of_user
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access


class PermissionDeniedError(Exception):
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/pages/manage/manage_cases_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
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_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
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
Expand Down
1 change: 1 addition & 0 deletions source/app/blueprints/rest/alerts_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,7 @@ def alert_comment_edit(alert_id, com_id):


@alerts_rest_blueprint.route('/alerts/<int:alert_id>/comments/add', methods=['POST'])
@endpoint_deprecated('POST', '/api/v2/alerts/{alert_identifier}/comments')
@ac_api_requires(Permissions.alerts_write)
def case_comment_add(alert_id):
"""
Expand Down
3 changes: 2 additions & 1 deletion source/app/blueprints/rest/case/case_assets_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
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
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
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
Expand Down Expand Up @@ -349,6 +349,7 @@ def case_comment_asset_list(cur_id, caseid):


@case_assets_rest_blueprint.route('/case/assets/<int:cur_id>/comments/add', methods=['POST'])
@endpoint_deprecated('POST', '/api/v2/assets/{asset_identifier}/comments')
@ac_requires_case_identifier(CaseAccessLevel.full_access)
@ac_api_requires()
def case_comment_asset_add(cur_id, caseid):
Expand Down
1 change: 1 addition & 0 deletions source/app/blueprints/rest/case/case_evidences_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def case_comment_evidence_list(cur_id, caseid):


@case_evidences_rest_blueprint.route('/case/evidences/<int:cur_id>/comments/add', methods=['POST'])
@endpoint_deprecated('POST', '/api/v2/evidences/{evidence_identifier}/comments')
@ac_requires_case_identifier(CaseAccessLevel.full_access)
@ac_api_requires()
def case_comment_evidence_add(cur_id, caseid):
Expand Down
3 changes: 2 additions & 1 deletion source/app/blueprints/rest/case/case_ioc_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from app.datamgmt.case.case_iocs_db import get_tlps_dict
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.states import get_ioc_state
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import CaseAccessLevel
Expand Down Expand Up @@ -259,6 +259,7 @@ def case_comment_ioc_list(cur_id, caseid):


@case_ioc_rest_blueprint.route('/case/ioc/<int:cur_id>/comments/add', methods=['POST'])
@endpoint_deprecated('POST', '/api/v2/iocs/{ioc_identifier}/comments')
@ac_requires_case_identifier(CaseAccessLevel.full_access)
@ac_api_requires()
def case_comment_ioc_add(cur_id, caseid):
Expand Down
1 change: 1 addition & 0 deletions source/app/blueprints/rest/case/case_notes_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ def case_comment_note_list(cur_id, caseid):


@case_notes_rest_blueprint.route('/case/notes/<int:cur_id>/comments/add', methods=['POST'])
@endpoint_deprecated('POST', '/api/v2/notes/{note_identifier}/comments')
@ac_requires_case_identifier(CaseAccessLevel.full_access)
@ac_api_requires()
def case_comment_note_add(cur_id, caseid):
Expand Down
25 changes: 16 additions & 9 deletions source/app/blueprints/rest/case/case_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@
from app.datamgmt.manage.manage_groups_db import get_group_with_members
from app.datamgmt.manage.manage_users_db import get_user
from app.datamgmt.manage.manage_users_db import get_users_list_restricted_from_case
from app.datamgmt.manage.manage_users_db import set_user_case_access
from app.business.access_controls import set_user_case_access, ac_fast_check_user_has_case_access
from app.business.cases import cases_export_to_json
from app.iris_engine.access_control.utils import ac_fast_check_user_has_case_access
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
Expand Down Expand Up @@ -243,21 +242,29 @@ def user_cac_set_case(caseid):

try:

success, logs = set_user_case_access(user.id, data.get('case_id'), data.get('access_level'))
case_identifier = data.get('case_id')
access_level = data.get('access_level')

if user.id is None or type(user.id) is not int:
return response_error('Invalid user id')
if case_identifier is None or type(case_identifier) is not int:
return response_error('Invalid case id')
if access_level is None or type(access_level) is not int:
return response_error('Invalid access level')
if CaseAccessLevel.has_value(access_level) is False:
return response_error('Invalid access level')

set_user_case_access(user.id, case_identifier, access_level)
track_activity('case access set to {} for user {}'.format(data.get('access_level'), user.name), caseid)
add_obj_history_entry(case, 'access changed to {} for user {}'.format(data.get('access_level'), user.name))

db.session.commit()
return response_success(msg=f'Case access set to {access_level} for user {user.id}')

except Exception as e:
log.error(f'Error while setting case access for user: {e}')
log.error(traceback.format_exc())
return response_error(msg=str(e))

if success:
return response_success(msg=logs)

return response_error(msg=logs)
return response_error(str(e))


@case_rest_blueprint.route('/case/update-status', methods=['POST'])
Expand Down
1 change: 1 addition & 0 deletions source/app/blueprints/rest/case/case_tasks_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def case_comment_task_list(cur_id: int, caseid: int):


@case_tasks_rest_blueprint.route('/case/tasks/<int:cur_id>/comments/add', methods=['POST'])
@endpoint_deprecated('POST', '/api/v2/tasks/{task_identifier}/comments')
@ac_requires_case_identifier(CaseAccessLevel.full_access)
@ac_api_requires()
def case_comment_task_add(cur_id: int, caseid: int):
Expand Down
1 change: 1 addition & 0 deletions source/app/blueprints/rest/case/case_timeline_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def case_comment_edit(cur_id, com_id, caseid):


@case_timeline_rest_blueprint.route('/case/timeline/events/<int:cur_id>/comments/add', methods=['POST'])
@endpoint_deprecated('POST', '/api/v2/events/{event_identifier}/comments')
@ac_requires_case_identifier(CaseAccessLevel.full_access)
@ac_api_requires()
def case_comment_add(cur_id, caseid):
Expand Down
3 changes: 2 additions & 1 deletion source/app/blueprints/rest/case_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
from app.blueprints.responses import response_success
from app.business.comments import comments_update_for_case
from app.business.errors import BusinessProcessingError
from app.iris_engine.access_control.iris_user import iris_current_user


def case_comment_update(comment_id, object_type, caseid):
try:
comment_schema = CommentSchema()
rq_t = request.get_json()
comment_text = rq_t.get('comment_text')
comment = comments_update_for_case(comment_text, comment_id, object_type, caseid)
comment = comments_update_for_case(iris_current_user, comment_text, comment_id, object_type, caseid)
return response_success("Comment edited", data=comment_schema.dump(comment))
except BusinessProcessingError as e:
return response_error(e.get_message(), data=e.get_data())
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/rest/manage/manage_assets_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from werkzeug import Response

from app.datamgmt.manage.manage_assets_db import get_filtered_assets
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
from app.models.authorization import CaseAccessLevel
from app.schema.marshables import CaseAssetsSchema
from app.blueprints.access_controls import ac_api_requires
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/rest/manage/manage_cases_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from app.datamgmt.manage.manage_cases_db import get_case_details_rt
from app.datamgmt.manage.manage_cases_db import list_cases_dict
from app.datamgmt.manage.manage_cases_db import reopen_case
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.module_handler.module_handler import configure_module_on_init
from app.iris_engine.module_handler.module_handler import instantiate_module_from_name
Expand Down
3 changes: 1 addition & 2 deletions source/app/blueprints/rest/manage/manage_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@
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_flag_match_mask
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
from app.models.authorization import Permissions, ac_flag_match_mask
from app.schema.marshables import AuthorizationGroupSchema
from app.blueprints.access_controls import ac_api_requires
from app.blueprints.access_controls import ac_api_return_access_denied
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/rest/search_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from sqlalchemy import and_

from app.iris_engine.utils.tracker import track_activity
from app.models.models import Comments
from app.models.comments import Comments
from app.models.authorization import Permissions
from app.models.cases import Cases
from app.models.models import Client
Expand Down
25 changes: 23 additions & 2 deletions source/app/blueprints/rest/v2/alerts_routes/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@

from flask import Blueprint
from flask import request
from marshmallow.exceptions import ValidationError

from app.blueprints.access_controls import ac_api_requires
from app.models.authorization import Permissions
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_created
from app.blueprints.rest.endpoints import response_api_error
from app.blueprints.rest.parsing import parse_pagination_parameters
from app.schema.marshables import CommentSchema
from app.business.comments import comments_get_filtered_by_alert
from app.business.comments import comments_create_for_alert
from app.iris_engine.access_control.iris_user import iris_current_user
from app.business.errors import ObjectNotFoundError

Expand All @@ -35,14 +39,25 @@ class CommentsOperations:
def __init__(self):
self._schema = CommentSchema()

def get(self, alert_identifier):
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)
return response_api_paginated(self._schema, comments)
except ObjectNotFoundError:
return response_api_not_found()

def create(self, alert_identifier):
try:
comment = self._schema.load(request.get_json())
comments_create_for_alert(iris_current_user, comment, alert_identifier)
result = self._schema.dump(comment)
return response_api_created(result)
except ValidationError as e:
return response_api_error('Data error', data=e.normalized_messages())
except ObjectNotFoundError:
return response_api_not_found()


alerts_comments_blueprint = Blueprint('alerts_comments', __name__, url_prefix='/<int:alert_identifier>/comments')
comments_operations = CommentsOperations()
Expand All @@ -51,4 +66,10 @@ def get(self, alert_identifier):
@alerts_comments_blueprint.get('')
@ac_api_requires(Permissions.alerts_read)
def get_alerts_comments(alert_identifier):
return comments_operations.get(alert_identifier)
return comments_operations.search(alert_identifier)


@alerts_comments_blueprint.post('')
@ac_api_requires(Permissions.alerts_write)
def create_alerts_comment(alert_identifier):
return comments_operations.create(alert_identifier)
2 changes: 1 addition & 1 deletion source/app/blueprints/rest/v2/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from app.business.assets import assets_get
from app.business.errors import BusinessProcessingError
from app.business.errors import ObjectNotFoundError
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
from app.models.authorization import CaseAccessLevel
from app.schema.marshables import CaseAssetsSchema
from app.blueprints.access_controls import ac_api_return_access_denied
Expand Down
41 changes: 34 additions & 7 deletions source/app/blueprints/rest/v2/assets_routes/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@

from flask import Blueprint
from flask import request
from marshmallow.exceptions import ValidationError

from app.blueprints.access_controls import ac_api_requires
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_created
from app.blueprints.rest.endpoints import response_api_error
from app.blueprints.rest.parsing import parse_pagination_parameters
from app.blueprints.access_controls import ac_api_return_access_denied
from app.business.comments import comments_get_filtered_by_asset
from app.business.comments import comments_create_for_asset
from app.business.assets import assets_get
from app.business.errors import ObjectNotFoundError
from app.schema.marshables import CommentSchema
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
from app.iris_engine.access_control.iris_user import iris_current_user
from app.models.authorization import CaseAccessLevel


Expand All @@ -37,20 +41,37 @@ class CommentsOperations:
def __init__(self):
self._schema = CommentSchema()

@staticmethod
def _get_asset(asset_identifier, possible_case_access_levels):
asset = assets_get(asset_identifier)
if not ac_fast_check_current_user_has_case_access(asset.case_id, possible_case_access_levels):
raise ObjectNotFoundError()
return asset

Comment on lines +44 to +50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm access-check API accepts a list; otherwise pass a single threshold level.

The call site passes a list of levels, but ac_fast_check_current_user_has_case_access(cid, access_level) appears to take a single level. If lists aren’t supported, refactor to a single “minimum required” level and update callers.

-    @staticmethod
-    def _get_asset(asset_identifier, possible_case_access_levels):
+    @staticmethod
+    def _get_asset(asset_identifier, required_level):
         asset = assets_get(asset_identifier)
-        if not ac_fast_check_current_user_has_case_access(asset.case_id, possible_case_access_levels):
+        if not ac_fast_check_current_user_has_case_access(asset.case_id, required_level):
             raise ObjectNotFoundError()
         return asset
-            asset = self._get_asset(asset_identifier, [CaseAccessLevel.read_only, CaseAccessLevel.full_access])
+            asset = self._get_asset(asset_identifier, CaseAccessLevel.read_only)
-            asset = self._get_asset(asset_identifier, [CaseAccessLevel.full_access])
+            asset = self._get_asset(asset_identifier, CaseAccessLevel.full_access)

Also applies to: 53-53, 64-64


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== target file snippet: source/app/blueprints/rest/v2/assets_routes/comments.py ==="
sed -n '1,200p' source/app/blueprints/rest/v2/assets_routes/comments.py || true
echo

echo "=== locating definition(s) of ac_fast_check_current_user_has_case_access ==="
rg -n --hidden -S "def ac_fast_check_current_user_has_case_access\b" -g '!**/node_modules/**' -g '!**/.venv/**' || true
echo

echo "=== showing usages of ac_fast_check_current_user_has_case_access (context) ==="
rg -n --hidden -S "ac_fast_check_current_user_has_case_access\(" -C3 -g '!**/node_modules/**' -g '!**/.venv/**' || true
echo

echo "=== locating CaseAccessLevel definition/usages ==="
rg -n --hidden -S "class CaseAccessLevel\b|CaseAccessLevel\." -C3 -g '!**/node_modules/**' -g '!**/.venv/**' || true
echo

echo "=== locate assets_get definition/usages ==="
rg -n --hidden -S "def assets_get\b|assets_get\(" -C3 -g '!**/node_modules/**' -g '!**/.venv/**' || true
echo

# If a definition file for the function was found, print its surrounding function block (up to 200 lines).
for file in $(rg -n --hidden -S "def ac_fast_check_current_user_has_case_access\b" -l || true); do
  echo "---- contents of $file (first 300 lines) ----"
  sed -n '1,300p' "$file"
done

Length of output: 5820


Fix: pass a single CaseAccessLevel to ac_fast_check_current_user_has_case_access (don't pass lists)

ac_fast_check_current_user_has_case_access(cid, access_level) accepts a single level (it delegates to ac_fast_check_user_has_case_access in source/app/business/access_controls.py); current code passes lists — change _get_asset to take a single required level and update callers.

Location: source/app/blueprints/rest/v2/assets_routes/comments.py (lines ~44-50; call sites at ~53 and ~64)

-    @staticmethod
-    def _get_asset(asset_identifier, possible_case_access_levels):
+    @staticmethod
+    def _get_asset(asset_identifier, required_level):
         asset = assets_get(asset_identifier)
-        if not ac_fast_check_current_user_has_case_access(asset.case_id, possible_case_access_levels):
+        if not ac_fast_check_current_user_has_case_access(asset.case_id, required_level):
             raise ObjectNotFoundError()
         return asset
-            asset = self._get_asset(asset_identifier, [CaseAccessLevel.read_only, CaseAccessLevel.full_access])
+            asset = self._get_asset(asset_identifier, CaseAccessLevel.read_only)
-            asset = self._get_asset(asset_identifier, [CaseAccessLevel.full_access])
+            asset = self._get_asset(asset_identifier, CaseAccessLevel.full_access)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@staticmethod
def _get_asset(asset_identifier, possible_case_access_levels):
asset = assets_get(asset_identifier)
if not ac_fast_check_current_user_has_case_access(asset.case_id, possible_case_access_levels):
raise ObjectNotFoundError()
return asset
@staticmethod
def _get_asset(asset_identifier, required_level):
asset = assets_get(asset_identifier)
if not ac_fast_check_current_user_has_case_access(asset.case_id, required_level):
raise ObjectNotFoundError()
return asset
🤖 Prompt for AI Agents
In source/app/blueprints/rest/v2/assets_routes/comments.py around lines 44-50,
_get_asset currently accepts possible_case_access_levels and passes that list
into ac_fast_check_current_user_has_case_access which expects a single
CaseAccessLevel; change _get_asset to accept a single required_case_access_level
(or similarly named param) and pass that single level to
ac_fast_check_current_user_has_case_access, then update the two call sites near
lines ~53 and ~64 to pass a single CaseAccessLevel value instead of a list;
ensure parameter name and usages are consistent and adjust any tests or imports
if necessary.

def get(self, asset_identifier):
try:
asset = assets_get(asset_identifier)
if not ac_fast_check_current_user_has_case_access(asset.case_id,
[CaseAccessLevel.read_only, CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=asset.case_id)
asset = self._get_asset(asset_identifier, [CaseAccessLevel.read_only, CaseAccessLevel.full_access])

pagination_parameters = parse_pagination_parameters(request)

comments = comments_get_filtered_by_asset(asset_identifier, pagination_parameters)
comments = comments_get_filtered_by_asset(asset, pagination_parameters)
return response_api_paginated(self._schema, comments)
except ObjectNotFoundError:
return response_api_not_found()

def create(self, asset_identifier):
try:
asset = self._get_asset(asset_identifier, [CaseAccessLevel.full_access])
comment = self._schema.load(request.get_json())
comments_create_for_asset(iris_current_user, asset, comment)
Comment on lines +65 to +66
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Block mass-assignment of ownership fields (“user”, “user_id”, “case_id”).

CommentSchema currently exposes user and includes FKs on load. A client could try to set author/case. Business logic may overwrite, but don’t rely on it here. Exclude these fields on load for this endpoint.

-            comment = self._schema.load(data, unknown=RAISE)
+            # Prevent client-controlled ownership fields
+            comment = CommentSchema(
+                exclude=('user', 'user_id', 'case_id', 'comment_id')
+            ).load(data, unknown=RAISE)

Follow-up (in schema): mark user as dump_only=True or move to a dedicated CommentCreateSchema that only allows the text/body field. I can open a patch if you want.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
comment = self._schema.load(request.get_json())
comments_create_for_asset(iris_current_user, asset, comment)
# Prevent client-controlled ownership fields
comment = CommentSchema(
exclude=('user', 'user_id', 'case_id', 'comment_id')
).load(request.get_json(), unknown=RAISE)
comments_create_for_asset(iris_current_user, asset, comment)
🤖 Prompt for AI Agents
In source/app/blueprints/rest/v2/assets_routes/comments.py around lines 65-66,
the endpoint currently loads the entire incoming JSON which allows clients to
mass-assign ownership fields (“user”, “user_id”, “case_id”); change the load
call to explicitly exclude these fields when deserializing (e.g. call the schema
load with exclude=("user","user_id","case_id") or load only the allowed fields
from request.get_json()) so the client cannot set author/case; as a follow-up,
update CommentSchema to mark user as dump_only=True or create a dedicated
CommentCreateSchema that only exposes the body/text field.


result = self._schema.dump(comment)
return response_api_created(result)
except ValidationError as e:
return response_api_error('Data error', data=e.normalized_messages())
except ObjectNotFoundError:
return response_api_not_found()

Comment on lines +62 to +74
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden JSON parsing and fail on unknown fields.

  • Return 400 on malformed/non-JSON payloads instead of letting a BadRequest bubble.
  • Reject unknown fields up-front to avoid silent drops.
     def create(self, asset_identifier):
         try:
-            asset = self._get_asset(asset_identifier, [CaseAccessLevel.full_access])
-            comment = self._schema.load(request.get_json())
+            asset = self._get_asset(asset_identifier, [CaseAccessLevel.full_access])
+            try:
+                data = request.get_json()
+            except BadRequest:
+                return response_api_error('Invalid JSON payload')
+            if data is None:
+                return response_api_error('Expected application/json payload')
+            comment = self._schema.load(data, unknown=RAISE)
             comments_create_for_asset(iris_current_user, asset, comment)
 
             result = self._schema.dump(comment)
             return response_api_created(result)
         except ValidationError as e:
             return response_api_error('Data error', data=e.normalized_messages())
         except ObjectNotFoundError:
             return response_api_not_found()

Add imports (outside this hunk):

from marshmallow import RAISE
from werkzeug.exceptions import BadRequest
🤖 Prompt for AI Agents
In source/app/blueprints/rest/v2/assets_routes/comments.py around lines 62 to
74, the handler currently allows malformed JSON to bubble up and silently
ignores unknown fields; update it to explicitly catch BadRequest from
request.get_json() and return a 400 response for invalid/malformed/non-JSON
payloads, and call self._schema.load(request.get_json(), unknown=RAISE) so
marshmallow raises on unknown fields; also add the required imports at top of
the file: from marshmallow import RAISE and from werkzeug.exceptions import
BadRequest.


assets_comments_blueprint = Blueprint('assets_comments', __name__, url_prefix='/<int:asset_identifier>/comments')
comments_operations = CommentsOperations()
Expand All @@ -60,3 +81,9 @@ def get(self, asset_identifier):
@ac_api_requires()
def get_assets_comments(asset_identifier):
return comments_operations.get(asset_identifier)


@assets_comments_blueprint.post('')
@ac_api_requires()
def create_assets_comment(asset_identifier):
return comments_operations.create(asset_identifier)
2 changes: 1 addition & 1 deletion source/app/blueprints/rest/v2/case_objects/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from app.business.assets import assets_delete
from app.business.errors import BusinessProcessingError
from app.business.errors import ObjectNotFoundError
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
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
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/rest/v2/case_objects/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from app.business.errors import BusinessProcessingError
from app.business.errors import ObjectNotFoundError
from app.business.cases import cases_exists
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
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
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/rest/v2/case_objects/evidences.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from flask import request

from app.blueprints.access_controls import ac_api_requires
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.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
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/rest/v2/case_objects/iocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from app.business.iocs import iocs_delete
from app.business.iocs import iocs_update
from app.datamgmt.case.case_iocs_db import get_filtered_iocs
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.business.access_controls import ac_fast_check_current_user_has_case_access
from app.models.authorization import CaseAccessLevel
from app.schema.marshables import IocSchemaForAPIV2
from app.blueprints.access_controls import ac_api_return_access_denied
Expand Down
Loading
Loading