Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
54932da
Moved code into persistence layer
c8y3 Oct 1, 2025
8c18603
Import logger directly
c8y3 Oct 1, 2025
2f8e1ce
Reuse method
c8y3 Oct 1, 2025
a6ef962
Extracted method
c8y3 Oct 3, 2025
c205de7
Started implementation of POST /api/v2/manage/customers
c8y3 Oct 3, 2025
5335c51
Create customer should allow to set customer_name
c8y3 Oct 3, 2025
ee4a875
Create customer should return 400 when another customer with the same…
c8y3 Oct 3, 2025
5611cbc
Fixed regression
c8y3 Oct 3, 2025
9fdaf1e
POST /api/v2/manage/customers adds an activity
c8y3 Oct 3, 2025
06d65fd
Fixed incorrect test
c8y3 Oct 3, 2025
5e4b536
POST /api/v2/manage/customers adds user to customer
c8y3 Oct 3, 2025
d4a6b49
Fixed ruff warning
c8y3 Oct 3, 2025
e36816a
Deprecated POST /manage/customers/add
c8y3 Oct 3, 2025
70fd435
Changed branch alert_similarities
Elise17 Oct 1, 2025
3b9a545
Changed test rest alerts
Elise17 Oct 1, 2025
afde506
Changed import from the API layer
Elise17 Oct 1, 2025
c99d817
Fixed check analysis
Elise17 Oct 1, 2025
7f3dcd7
changed message api error
Elise17 Oct 3, 2025
2f1a1d4
Removed unused function
Elise17 Oct 3, 2025
0516cc4
Removed import
Elise17 Oct 6, 2025
9a33abf
Started implementation of POST /api/v2/alerts-filters
Elise17 Oct 8, 2025
b7088fd
Fixed analysis check
Elise17 Oct 8, 2025
103ed2c
Added function _load
Elise17 Oct 8, 2025
82bfec6
Deprecated endpoint POST /filters/add in favor of POST /api/v2/alerts…
Elise17 Oct 8, 2025
5b1290e
Added test create alert when filter data is missing
Elise17 Oct 8, 2025
ea45b99
Added test test_create_alert_filter_should_return_filter_type
Elise17 Oct 8, 2025
4c5ddd6
Added test_create_alert_filter_should_return_filter_name
Elise17 Oct 8, 2025
0a0f63d
Added test_create_alert_filter_should_return_in_filter_data_alert_title
Elise17 Oct 8, 2025
7a24e1e
Fixed problem with filter_id
Elise17 Oct 17, 2025
b898352
Fixed static analysis
Elise17 Oct 17, 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
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 @@ -257,6 +257,7 @@ def alerts_get_route(alert_id) -> Response:


@alerts_rest_blueprint.route('/alerts/similarities/<int:alert_id>', methods=['GET'])
@endpoint_deprecated('GET', '/api/v2/alerts/{identifier}/related-alerts')
@ac_api_requires(Permissions.alerts_read)
def alerts_similarities_route(alert_id) -> Response:
"""
Expand Down
2 changes: 2 additions & 0 deletions source/app/blueprints/rest/api_v2_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
116 changes: 55 additions & 61 deletions source/app/blueprints/rest/case/case_timeline_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
from app.blueprints.rest.endpoints import endpoint_deprecated
from app.blueprints.iris_user import iris_current_user
from app.datamgmt.case.case_assets_db import get_asset_by_name
from app.datamgmt.case.case_assets_db import get_assets_by_case
from app.datamgmt.case.case_events_db import add_comment_to_event
from app.datamgmt.case.case_events_db import get_events_by_case
from app.datamgmt.case.case_events_db import get_category_by_name
from app.datamgmt.case.case_events_db import get_default_category
from app.datamgmt.case.case_events_db import delete_event_comment
Expand Down Expand Up @@ -178,19 +180,8 @@ def case_get_timeline_state(caseid):
@ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
@ac_api_requires()
def case_getgraph_assets(caseid):
assets_cache = CaseAssets.query.with_entities(
CaseEventsAssets.event_id,
CaseAssets.asset_name
).filter(
CaseEventsAssets.case_id == caseid,
).join(CaseEventsAssets.asset).all()

timeline = CasesEvent.query.filter(and_(
CasesEvent.case_id == caseid,
CasesEvent.event_in_summary
)).order_by(
CasesEvent.event_date
).all()
assets_cache = get_assets_by_case(caseid)
timeline = get_events_by_case(caseid)

tim = []
for row in timeline:
Expand All @@ -216,12 +207,7 @@ def case_getgraph_assets(caseid):
@ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
@ac_api_requires()
def case_getgraph(caseid):
timeline = CasesEvent.query.filter(and_(
CasesEvent.case_id == caseid,
CasesEvent.event_in_summary
)).order_by(
CasesEvent.event_date
).all()
timeline = get_events_by_case(caseid)

tim = []
for row in timeline:
Expand Down Expand Up @@ -359,6 +345,11 @@ def case_filter_timeline(caseid):
assets = filter_d.get('asset')
assets_id = filter_d.get('asset_id')
event_ids = filter_d.get('event_id')
if event_ids:
try:
event_ids = [int(event_id) for event_id in event_ids]
except Exception as _:
return response_error('Invalid event id')
iocs = filter_d.get('ioc')
iocs_id = filter_d.get('ioc_id')
tags = filter_d.get('tag')
Expand All @@ -371,6 +362,46 @@ def case_filter_timeline(caseid):
sources = filter_d.get('source')
flag = filter_d.get('flag')

cache, events_list, tim = _extract_timeline(assets, assets_id, caseid, categories, descriptions, end_date, event_ids,
flag, iocs, iocs_id, raws, sources, start_date, tags, titles)

if request.cookies.get('session'):

iocs = Ioc.query.with_entities(
Ioc.ioc_id,
Ioc.ioc_value,
Ioc.ioc_description,
).filter(
Ioc.case_id == caseid
).all()

events_comments_map = {}
events_comments_set = get_case_events_comments_count(events_list)
for k, v in events_comments_set:
events_comments_map.setdefault(k, []).append(v)

resp = {
"tim": tim,
"comments_map": events_comments_map,
"assets": cache,
"iocs": [ioc._asdict() for ioc in iocs],
"categories": [cat.name for cat in get_events_categories()],
"state": get_timeline_state(caseid=caseid)
}

else:
resp = {
"timeline": tim,
"state": get_timeline_state(caseid=caseid)
}

return response_success("ok", data=resp)


def _extract_timeline(assets: str | None, assets_id: str | None, caseid, categories: str | None,
descriptions: str | None, end_date: str | None, event_ids: list[int] | None,
flag: str | None, iocs: str | None, iocs_id: str | None, raws: str | None, sources: str | None,
start_date: str | None, tags: str | None, titles: str | None):
condition = (CasesEvent.case_id == caseid)

if assets:
Expand Down Expand Up @@ -437,11 +468,6 @@ def case_filter_timeline(caseid):
EventCategory.name == category)

if event_ids:
try:
event_ids = [int(event_id) for event_id in event_ids]
except Exception as _:
return response_error('Invalid event id')

condition = and_(condition,
CasesEvent.event_id.in_(event_ids))

Expand Down Expand Up @@ -491,7 +517,7 @@ def case_filter_timeline(caseid):
).filter(
assets_cache_condition
).join(CaseEventsAssets.asset)
.join(CaseAssets.asset_type).all())
.join(CaseAssets.asset_type).all())

iocs_cache_condition = and_(
CaseEventsIoc.case_id == caseid
Expand Down Expand Up @@ -521,8 +547,7 @@ def case_filter_timeline(caseid):
if asset.asset_id not in cache:
cache[asset.asset_id] = [asset.asset_name, asset.type]

if (assets and asset.asset_name.lower() in assets) \
or (assets_id and asset.asset_id in assets_id):
if (assets and asset.asset_name.lower() in assets) or (assets_id and asset.asset_id in assets_id):
if asset.event_id in assets_map:
assets_map[asset.event_id] += 1
else:
Expand All @@ -549,10 +574,10 @@ def case_filter_timeline(caseid):
events_list = []
for row in timeline:
if (assets is not None or assets_id is not None) and row.event_id not in assets_filter:
continue
continue

if iocs is not None and row.event_id not in iocs_filter:
continue
continue

ras = row._asdict()

Expand Down Expand Up @@ -594,38 +619,7 @@ def case_filter_timeline(caseid):
ras['iocs'] = alki

tim.append(ras)

if request.cookies.get('session'):

iocs = Ioc.query.with_entities(
Ioc.ioc_id,
Ioc.ioc_value,
Ioc.ioc_description,
).filter(
Ioc.case_id == caseid
).all()

events_comments_map = {}
events_comments_set = get_case_events_comments_count(events_list)
for k, v in events_comments_set:
events_comments_map.setdefault(k, []).append(v)

resp = {
"tim": tim,
"comments_map": events_comments_map,
"assets": cache,
"iocs": [ioc._asdict() for ioc in iocs],
"categories": [cat.name for cat in get_events_categories()],
"state": get_timeline_state(caseid=caseid)
}

else:
resp = {
"timeline": tim,
"state": get_timeline_state(caseid=caseid)
}

return response_success("ok", data=resp)
return cache, events_list, tim


@case_timeline_rest_blueprint.route('/case/timeline/events/delete/<int:cur_id>', methods=['POST'])
Expand Down
4 changes: 3 additions & 1 deletion source/app/blueprints/rest/filters_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down
14 changes: 9 additions & 5 deletions source/app/blueprints/rest/manage/manage_customers_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from app.blueprints.access_controls import ac_api_requires_client_access
from app.blueprints.responses import response_error
from app.blueprints.responses import response_success
from app.blueprints.rest.endpoints import endpoint_deprecated

manage_customers_rest_blueprint = Blueprint('manage_customers_rest', __name__)

Expand Down Expand Up @@ -239,27 +240,30 @@ def view_customers(client_id):


@manage_customers_rest_blueprint.route('/manage/customers/add', methods=['POST'])
@endpoint_deprecated('POST', '/api/v2/manage/customers')
@ac_api_requires(Permissions.customers_write)
def add_customers():
if not request.is_json:
return response_error("Invalid request")

customer_schema = CustomerSchema()
try:
client = create_client(request.json)
customer = customer_schema.load(request.json)

create_client(customer)
except ValidationError as e:
return response_error(msg='Error adding customer', data=e.messages)
except Exception as e:
print(traceback.format_exc())
return response_error(f'An error occurred during customer addition. {e}')

track_activity(f"Added customer {client.name}", ctx_less=True)
track_activity(f"Added customer {customer.name}", ctx_less=True)

# Associate the created customer with the current user
add_user_to_customer(iris_current_user.id, client.client_id)
add_user_to_customer(iris_current_user.id, customer.client_id)

# Return the customer
client_schema = CustomerSchema()
return response_success("Added successfully", data=client_schema.dump(client))
return response_success('Added successfully', data=customer_schema.dump(customer))


@manage_customers_rest_blueprint.route('/manage/customers/delete/<int:client_id>', methods=['POST'])
Expand Down
31 changes: 31 additions & 0 deletions source/app/blueprints/rest/v2/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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.errors import BusinessProcessingError
from app.business.errors import ObjectNotFoundError
from app.business.access_controls import access_controls_user_has_customer_access
Expand Down Expand Up @@ -165,6 +166,30 @@ def get(self, identifier):
except ObjectNotFoundError:
return response_api_not_found()

def get_related_alerts(self, identifier):

try:
alert = alerts_get(iris_current_user, 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'
days_back = request.args.get('days-back', 180, type=int)
number_of_results = request.args.get('number-of-nodes', 100, type=int)

if number_of_results < 0:
number_of_results = 100
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)
return response_api_success(similar_alerts)

except ObjectNotFoundError:
return response_api_not_found()

def update(self, identifier):
try:
alert = alerts_get(iris_current_user, identifier)
Expand Down Expand Up @@ -243,3 +268,9 @@ def update_alert(identifier):
@ac_api_requires(Permissions.alerts_delete)
def delete_alert(identifier):
return alerts_operations.delete(identifier)


@alerts_blueprint.get('<int:identifier>/related-alerts')
@ac_api_requires(Permissions.alerts_read)
def get_related_alerts(identifier):
return alerts_operations.get_related_alerts(identifier)
60 changes: 60 additions & 0 deletions source/app/blueprints/rest/v2/alerts_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# IRIS Source Code
# Copyright (C) 2024 - DFIR-IRIS
# [email protected]
#
# 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

from app.business.alerts_filters import alerts_filters_add


class AlertsFiltersOperations:

def __init__(self):
self._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
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

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

There is an unnecessary space before the opening bracket. Change request_data ['created_by'] to request_data['created_by'] for consistent Python spacing conventions.

Suggested change
request_data ['created_by'] = iris_current_user.id
request_data['created_by'] = iris_current_user.id

Copilot uses AI. Check for mistakes.

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:
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()
3 changes: 2 additions & 1 deletion source/app/blueprints/rest/v2/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@

from app.blueprints.rest.v2.manage_routes.groups import create_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)
Loading
Loading