first sync
Some checks failed
Deployment Verification / deploy-and-test (push) Failing after 29s

This commit is contained in:
2025-03-04 07:59:21 +01:00
parent 9cdcf486b6
commit 506716e703
1450 changed files with 577316 additions and 62 deletions

View File

View File

@ -0,0 +1,132 @@
#!/usr/bin/env python3
#
# 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 sqlalchemy import and_
from sqlalchemy import desc
from app.models import Cases
from app.models.authorization import User
from app.models.models import UserActivity
def get_auto_activities(caseid):
"""
DB function to fetch the automatically generated activities
caseid: the case from which to get activities
"""
auto_activities = UserActivity.query.with_entities(
User.name.label("user_name"),
UserActivity.activity_date,
UserActivity.activity_desc,
UserActivity.user_input
).join(
UserActivity.user
).filter(
and_(
UserActivity.case_id == caseid,
UserActivity.activity_desc.notlike('[Unbound]%'),
UserActivity.activity_desc.notlike('Started a search for %'),
UserActivity.activity_desc.notlike('Updated global task %'),
UserActivity.activity_desc.notlike('Created new global task %'),
UserActivity.activity_desc.notlike('Started a new case creation %'),
UserActivity.user_input == False
)
).order_by(
UserActivity.activity_date
).all()
auto_activities = [row._asdict() for row in auto_activities]
return auto_activities
def get_manual_activities(caseid):
"""
DB function to fetch the manually generated activities
caseid: the case from which to get activities
"""
manual_activities = UserActivity.query.with_entities(
User.name.label("user_name"),
UserActivity.activity_date,
UserActivity.activity_desc,
UserActivity.user_input
).join(
UserActivity.user
).filter(
and_(
UserActivity.case_id == caseid,
UserActivity.user_input == True
)
).order_by(
UserActivity.activity_date
).all()
manual_activities = [row._asdict() for row in manual_activities]
return manual_activities
def get_users_activities():
user_activities = UserActivity.query.with_entities(
Cases.name.label("case_name"),
User.name.label("user_name"),
UserActivity.user_id,
UserActivity.case_id,
UserActivity.activity_date,
UserActivity.activity_desc,
UserActivity.user_input,
UserActivity.is_from_api
).filter(
UserActivity.display_in_ui == True
).outerjoin(
UserActivity.user
).outerjoin(
UserActivity.case
).order_by(desc(UserActivity.activity_date)).limit(10000).all()
return user_activities
def get_all_users_activities():
user_activities = UserActivity.query.with_entities(
Cases.name.label("case_name"),
User.name.label("user_name"),
UserActivity.user_id,
UserActivity.case_id,
UserActivity.activity_date,
UserActivity.activity_desc,
UserActivity.user_input,
UserActivity.is_from_api
).join(
UserActivity.case, UserActivity.user
).order_by(desc(UserActivity.activity_date)).limit(10000).all()
user_activities += UserActivity.query.with_entities(
UserActivity.case_id.label("case_name"),
UserActivity.user_id.label("user_name"),
UserActivity.activity_date,
UserActivity.activity_desc,
UserActivity.user_input,
UserActivity.is_from_api
).filter(and_(
UserActivity.case_id == None
)).order_by(desc(UserActivity.activity_date)).limit(10000).all()
return user_activities

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,399 @@
#!/usr/bin/env python3
#
# 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.
import datetime
from flask_login import current_user
from sqlalchemy import and_
from sqlalchemy import func
from app import db, app
from app.datamgmt.states import update_assets_state
from app.models import AnalysisStatus, CaseStatus
from app.models import AssetComments
from app.models import AssetsType
from app.models import CaseAssets
from app.models import CaseEventsAssets
from app.models import Cases
from app.models import Comments
from app.models import CompromiseStatus
from app.models import Ioc
from app.models import IocAssetLink
from app.models import IocLink
from app.models import IocType
from app.models.authorization import User
log = app.logger
def create_asset(asset, caseid, user_id):
asset.date_added = datetime.datetime.utcnow()
asset.date_update = datetime.datetime.utcnow()
asset.case_id = caseid
asset.user_id = user_id
db.session.add(asset)
update_assets_state(caseid=caseid, userid=user_id)
db.session.commit()
return asset
def get_assets(caseid):
assets = CaseAssets.query.with_entities(
CaseAssets.asset_id,
CaseAssets.asset_uuid,
CaseAssets.asset_name,
AssetsType.asset_name.label('asset_type'),
AssetsType.asset_icon_compromised,
AssetsType.asset_icon_not_compromised,
CaseAssets.asset_description,
CaseAssets.asset_domain,
CaseAssets.asset_compromise_status_id,
CaseAssets.asset_ip,
CaseAssets.asset_type_id,
AnalysisStatus.name.label('analysis_status'),
CaseAssets.analysis_status_id,
CaseAssets.asset_tags
).filter(
CaseAssets.case_id == caseid,
).join(
CaseAssets.asset_type, CaseAssets.analysis_status
).all()
return assets
def get_assets_name(caseid):
assets_names = CaseAssets.query.with_entities(
CaseAssets.asset_name
).filter(
CaseAssets.case_id == caseid
).all()
return assets_names
def get_asset(asset_id, caseid):
asset = CaseAssets.query.filter(
CaseAssets.asset_id == asset_id,
CaseAssets.case_id == caseid
).first()
return asset
def update_asset(asset_name, asset_description, asset_ip, asset_info, asset_domain,
asset_compromise_status_id, asset_type, asset_id, caseid, analysis_status, asset_tags):
asset = get_asset(asset_id, caseid)
asset.asset_name = asset_name
asset.asset_description = asset_description
asset.asset_ip = asset_ip
asset.asset_info = asset_info
asset.asset_domain = asset_domain
asset.asset_compromise_status_id = asset_compromise_status_id
asset.asset_type_id = asset_type
asset.analysis_status_id = analysis_status
asset.asset_tags = asset_tags
update_assets_state(caseid=caseid)
db.session.commit()
def delete_asset(asset_id, caseid):
case_asset = get_asset(asset_id, caseid)
if case_asset is None:
return
if case_asset.case_id and case_asset.alerts is not None:
CaseEventsAssets.query.filter(
case_asset.asset_id == CaseEventsAssets.asset_id
).delete()
case_asset.case_id = None
db.session.commit()
return
with db.session.begin_nested():
delete_ioc_asset_link(asset_id)
# Delete the relevant records from the CaseEventsAssets table
CaseEventsAssets.query.filter(
CaseEventsAssets.case_id == caseid,
CaseEventsAssets.asset_id == asset_id
).delete()
# Delete the relevant records from the AssetComments table
com_ids = AssetComments.query.with_entities(
AssetComments.comment_id
).filter(
AssetComments.comment_asset_id == asset_id,
).all()
com_ids = [c.comment_id for c in com_ids]
AssetComments.query.filter(AssetComments.comment_id.in_(com_ids)).delete()
Comments.query.filter(
Comments.comment_id.in_(com_ids)
).delete()
# Directly delete the relevant records from the CaseAssets table
CaseAssets.query.filter(
CaseAssets.asset_id == asset_id,
CaseAssets.case_id == caseid
).delete()
update_assets_state(caseid=caseid)
def get_assets_types():
assets_types = [(c.asset_id, c.asset_name) for c
in AssetsType.query.with_entities(AssetsType.asset_name,
AssetsType.asset_id).order_by(AssetsType.asset_name)
]
return assets_types
def get_unspecified_analysis_status_id():
"""
Get the id of the 'Unspecified' analysis status
"""
analysis_status = AnalysisStatus.query.filter(
AnalysisStatus.name == 'Unspecified'
).first()
return analysis_status.id if analysis_status else None
def get_analysis_status_list():
analysis_status = [(c.id, c.name) for c in AnalysisStatus.query.with_entities(
AnalysisStatus.id,
AnalysisStatus.name
)]
return analysis_status
def get_compromise_status_list():
return [(e.value, e.name.replace('_', ' ').capitalize()) for e in CompromiseStatus]
def get_compromise_status_dict():
return [{'value': e.value, 'name': e.name.replace('_', ' ').capitalize()} for e in CompromiseStatus]
def get_case_outcome_status_dict():
return [{'value': e.value, 'name': e.name.replace('_', ' ').capitalize()} for e in CaseStatus]
def get_asset_type_id(asset_type_name):
assets_type_id = AssetsType.query.with_entities(
AssetsType.asset_id
).filter(
func.lower(AssetsType.asset_name) == asset_type_name
).first()
return assets_type_id
def get_assets_ioc_links(caseid):
ioc_links_req = IocAssetLink.query.with_entities(
Ioc.ioc_id,
Ioc.ioc_value,
IocAssetLink.asset_id
).filter(
Ioc.ioc_id == IocAssetLink.ioc_id,
IocLink.case_id == caseid,
IocLink.ioc_id == Ioc.ioc_id
).all()
return ioc_links_req
def get_similar_assets(asset_name, asset_type_id, caseid, customer_id, cases_limitation):
linked_assets = CaseAssets.query.with_entities(
Cases.name.label('case_name'),
Cases.open_date.label('case_open_date'),
CaseAssets.asset_description,
CaseAssets.asset_compromise_status_id,
CaseAssets.asset_id,
CaseAssets.case_id
).filter(
Cases.client_id == customer_id,
CaseAssets.case_id != caseid
).filter(
CaseAssets.asset_name == asset_name,
CaseAssets.asset_type_id == asset_type_id,
Cases.case_id.in_(cases_limitation)
).join(CaseAssets.case).all()
return (lasset._asdict() for lasset in linked_assets)
def delete_ioc_asset_link(asset_id):
IocAssetLink.query.filter(
IocAssetLink.asset_id == asset_id
).delete()
def get_linked_iocs_from_asset(asset_id):
iocs = IocAssetLink.query.with_entities(
Ioc.ioc_id,
Ioc.ioc_value
).filter(
IocAssetLink.asset_id == asset_id,
Ioc.ioc_id == IocAssetLink.ioc_id
).all()
return iocs
def set_ioc_links(ioc_list, asset_id):
if ioc_list is None:
return False, "Empty IOC list"
# Reset IOC list
delete_ioc_asset_link(asset_id)
for ioc in ioc_list:
ial = IocAssetLink()
ial.asset_id = asset_id
ial.ioc_id = ioc
db.session.add(ial)
try:
db.session.commit()
except Exception as e:
db.session.rollback()
log.exception(e)
return True, e.__str__()
return False, ""
def get_linked_iocs_id_from_asset(asset_id):
iocs = IocAssetLink.query.with_entities(
IocAssetLink.ioc_id
).filter(
IocAssetLink.asset_id == asset_id
).all()
return iocs
def get_linked_iocs_finfo_from_asset(asset_id):
iocs = IocAssetLink.query.with_entities(
Ioc.ioc_id,
Ioc.ioc_value,
Ioc.ioc_tags,
Ioc.ioc_type_id,
IocType.type_name,
Ioc.ioc_description,
Ioc.ioc_tlp_id
).filter(and_(
IocAssetLink.asset_id == asset_id,
IocAssetLink.ioc_id == Ioc.ioc_id
)).join(Ioc.ioc_type).all()
return iocs
def get_case_asset_comments(asset_id):
return Comments.query.filter(
AssetComments.comment_asset_id == asset_id
).with_entities(
Comments
).join(AssetComments,
Comments.comment_id == AssetComments.comment_id
).order_by(
Comments.comment_date.asc()
).all()
def add_comment_to_asset(asset_id, comment_id):
ec = AssetComments()
ec.comment_asset_id = asset_id
ec.comment_id = comment_id
db.session.add(ec)
db.session.commit()
def get_case_assets_comments_count(asset_id):
return AssetComments.query.filter(
AssetComments.comment_asset_id.in_(asset_id)
).with_entities(
AssetComments.comment_asset_id,
AssetComments.comment_id
).group_by(
AssetComments.comment_asset_id,
AssetComments.comment_id
).all()
def get_case_asset_comment(asset_id, comment_id):
return AssetComments.query.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,
User.name,
User.user
).join(
AssetComments.comment,
Comments.user
).first()
def delete_asset_comment(asset_id, comment_id, case_id):
comment = Comments.query.filter(
Comments.comment_id == comment_id,
Comments.comment_user_id == current_user.id
).first()
if not comment:
return False, "You are not allowed to delete this comment"
AssetComments.query.filter(
AssetComments.comment_asset_id == asset_id,
AssetComments.comment_id == comment_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(
CaseAssets.asset_name == asset_name,
CaseAssets.case_id == caseid
).first()
return asset

View File

@ -0,0 +1,13 @@
from app.models import Comments
def get_case_comment(comment_id, caseid):
if caseid is None:
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()

View File

@ -0,0 +1,211 @@
#!/usr/bin/env python3
#
# 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.
import datetime
import binascii
from sqlalchemy import and_
from app import db
from app.models import Tags
from app.models.cases import CaseProtagonist
from app.models.cases import CaseTags
from app.models.cases import Cases
from app.models.models import CaseTemplateReport, ReviewStatus
from app.models.models import Client
from app.models.models import Languages
from app.models.models import ReportType
from app.models.authorization import User
def get_case_summary(caseid):
case_summary = Cases.query.filter(
Cases.case_id == caseid
).with_entities(
Cases.name.label('case_name'),
Cases.open_date.label('case_open'),
User.name.label('user'),
Client.name.label('customer')
).join(
Cases.user, Cases.client
).first()
return case_summary
def get_case(caseid):
return Cases.query.filter(Cases.case_id == caseid).first()
def case_exists(caseid):
return Cases.query.filter(Cases.case_id == caseid).count()
def get_case_client_id(caseid):
client_id = Cases.query.with_entities(
Client.client_id
).filter(
Cases.case_id == caseid
).join(Cases.client).first()
return client_id.client_id
def case_get_desc(caseid):
case_desc = Cases.query.with_entities(
Cases.description
).filter(
Cases.case_id == caseid
).first()
return case_desc
def case_get_desc_crc(caseid):
partial_case = case_get_desc(caseid)
if partial_case:
desc = partial_case.description
if not desc:
desc = ""
desc_crc32 = binascii.crc32(desc.encode('utf-8'))
else:
desc = None
desc_crc32 = None
return desc_crc32, desc
def case_set_desc_crc(desc, caseid):
lcase = get_case(caseid)
if lcase:
if not desc:
desc = ""
lcase.description = desc
db.session.commit()
return True
return False
def get_case_report_template():
reports = CaseTemplateReport.query.with_entities(
CaseTemplateReport.id,
CaseTemplateReport.name,
Languages.name,
CaseTemplateReport.description
).join(
CaseTemplateReport.language,
CaseTemplateReport.report_type
).filter(
ReportType.name == "Investigation"
).all()
return reports
def save_case_tags(tags, case):
if tags is None:
return
case.tags.clear()
for tag in tags.split(','):
tag = tag.strip()
if tag:
tg = Tags.query.filter_by(tag_title=tag).first()
if tg is None:
tg = Tags(tag_title=tag)
tg.save()
case.tags.append(tg)
db.session.commit()
def get_case_tags(case_id):
case = Cases.query.get(case_id)
if case:
return [tag.tag_title for tag in case.tags]
return []
def get_activities_report_template():
reports = CaseTemplateReport.query.with_entities(
CaseTemplateReport.id,
CaseTemplateReport.name,
Languages.name,
CaseTemplateReport.description
).join(
CaseTemplateReport.language,
CaseTemplateReport.report_type
).filter(
ReportType.name == "Activities"
).all()
return reports
def case_name_exists(case_name, client_name):
res = Cases.query.with_entities(
Cases.name, Client.name
).filter(and_(
Cases.name == case_name,
Client.name == client_name
)).join(
Cases.client
).first()
return True if res else False
def register_case_protagonists(case_id, protagonists):
if protagonists is None:
return
CaseProtagonist.query.filter(
CaseProtagonist.case_id == case_id
).delete()
for protagonist in protagonists:
for key in ['role', 'name']:
if not protagonist.get(key):
continue
cp = CaseProtagonist()
cp.case_id = case_id
cp.role = protagonist.get('role')
cp.name = protagonist.get('name')
cp.contact = protagonist.get('contact')
db.session.add(cp)
db.session.commit()
def get_review_id_from_name(review_name):
status = ReviewStatus.query.filter(ReviewStatus.status_name == review_name).first()
if status:
return status.id
return None

View File

@ -0,0 +1,402 @@
#!/usr/bin/env python3
#
# 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 flask_login import current_user
from sqlalchemy import and_
from app import db
from app.datamgmt.states import update_timeline_state
from app.models import AssetsType
from app.models import CaseAssets
from app.models import CaseEventCategory
from app.models import CaseEventsAssets
from app.models import CaseEventsIoc
from app.models import CasesEvent
from app.models import Comments
from app.models import EventCategory
from app.models import EventComments
from app.models import Ioc
from app.models import IocAssetLink
from app.models import IocLink
from app.models import IocType
from app.models.authorization import User
def get_case_events_assets_graph(caseid):
events = CaseEventsAssets.query.with_entities(
CaseEventsAssets.event_id,
CasesEvent.event_uuid,
CasesEvent.event_title,
CaseAssets.asset_name,
CaseAssets.asset_id,
AssetsType.asset_name.label('type_name'),
AssetsType.asset_icon_not_compromised,
AssetsType.asset_icon_compromised,
CasesEvent.event_color,
CaseAssets.asset_compromise_status_id,
CaseAssets.asset_description,
CaseAssets.asset_ip,
CasesEvent.event_date,
CasesEvent.event_tags
).filter(and_(
CaseEventsAssets.case_id == caseid,
CasesEvent.event_in_graph == True
)).join(
CaseEventsAssets.event,
CaseEventsAssets.asset,
CaseAssets.asset_type,
).all()
return events
def get_case_events_ioc_graph(caseid):
events = CaseEventsIoc.query.with_entities(
CaseEventsIoc.event_id,
CasesEvent.event_uuid,
CasesEvent.event_title,
CasesEvent.event_date,
Ioc.ioc_id,
Ioc.ioc_value,
Ioc.ioc_description,
IocType.type_name
).filter(and_(
CaseEventsIoc.case_id == caseid,
CasesEvent.event_in_graph == True
)).join(
CaseEventsIoc.event,
CaseEventsIoc.ioc,
Ioc.ioc_type,
).all()
return events
def get_events_categories():
return EventCategory.query.with_entities(
EventCategory.id,
EventCategory.name
).all()
def get_default_cat():
cat = EventCategory.query.with_entities(
EventCategory.id,
EventCategory.name
).filter(
EventCategory.name == "Unspecified"
).first()
return [cat._asdict()]
def get_case_event(event_id, caseid):
return CasesEvent.query.filter(
CasesEvent.event_id == event_id,
CasesEvent.case_id == caseid
).first()
def get_case_event_comments(event_id, caseid):
return Comments.query.filter(
EventComments.comment_event_id == event_id
).join(
EventComments,
Comments.comment_id == EventComments.comment_id
).order_by(
Comments.comment_date.asc()
).all()
def get_case_events_comments_count(events_list):
return EventComments.query.filter(
EventComments.comment_event_id.in_(events_list)
).with_entities(
EventComments.comment_event_id,
EventComments.comment_id
).group_by(
EventComments.comment_event_id,
EventComments.comment_id
).all()
def get_case_event_comment(event_id, comment_id, caseid):
return EventComments.query.filter(
EventComments.comment_event_id == event_id,
EventComments.comment_id == comment_id
).with_entities(
Comments.comment_id,
Comments.comment_text,
Comments.comment_date,
Comments.comment_update_date,
Comments.comment_uuid,
User.name,
User.user
).join(
EventComments.comment,
Comments.user
).first()
def delete_event_comment(event_id, comment_id):
comment = Comments.query.filter(
Comments.comment_id == comment_id,
Comments.comment_user_id == current_user.id
).first()
if not comment:
return False, "You are not allowed to delete this comment"
EventComments.query.filter(
EventComments.comment_event_id == event_id,
EventComments.comment_id == comment_id
).delete()
db.session.delete(comment)
db.session.commit()
return True, "Comment deleted"
def add_comment_to_event(event_id, comment_id):
ec = EventComments()
ec.comment_event_id = event_id
ec.comment_id = comment_id
db.session.add(ec)
db.session.commit()
def delete_event_category(event_id):
CaseEventCategory.query.filter(
CaseEventCategory.event_id == event_id
).delete()
def get_event_category(event_id):
cec = CaseEventCategory.query.filter(
CaseEventCategory.event_id == event_id
).first()
return cec
def save_event_category(event_id, category_id):
CaseEventCategory.query.filter(
CaseEventCategory.event_id == event_id
).delete()
cec = CaseEventCategory()
cec.event_id = event_id
cec.category_id = category_id
db.session.add(cec)
db.session.commit()
def get_event_assets_ids(event_id, caseid):
assets_list = CaseEventsAssets.query.with_entities(
CaseEventsAssets.asset_id
).filter(
CaseEventsAssets.event_id == event_id,
CaseEventsAssets.case_id == caseid
).all()
return [x[0] for x in assets_list]
def get_event_iocs_ids(event_id, caseid):
iocs_list = CaseEventsIoc.query.with_entities(
CaseEventsIoc.ioc_id
).filter(
CaseEventsIoc.event_id == event_id,
CaseEventsIoc.case_id == caseid
).all()
return [x[0] for x in iocs_list]
def update_event_assets(event_id, caseid, assets_list, iocs_list, sync_iocs_assets):
CaseEventsAssets.query.filter(
CaseEventsAssets.event_id == event_id,
CaseEventsAssets.case_id == caseid
).delete()
valid_assets = CaseAssets.query.with_entities(
CaseAssets.asset_id
).filter(
CaseAssets.asset_id.in_(assets_list),
CaseAssets.case_id == caseid
).all()
for asset in valid_assets:
try:
cea = CaseEventsAssets()
cea.asset_id = int(asset.asset_id)
cea.event_id = event_id
cea.case_id = caseid
db.session.add(cea)
if sync_iocs_assets:
for ioc in iocs_list:
link = IocAssetLink.query.filter(
IocAssetLink.asset_id == int(asset.asset_id),
IocAssetLink.ioc_id == int(ioc)
).first()
if link is None:
ial = IocAssetLink()
ial.asset_id = int(asset.asset_id)
ial.ioc_id = int(ioc)
db.session.add(ial)
except Exception as e:
return False, str(e)
db.session.commit()
return True, ''
def update_event_iocs(event_id, caseid, iocs_list):
CaseEventsIoc.query.filter(
CaseEventsIoc.event_id == event_id,
CaseEventsIoc.case_id == caseid
).delete()
valid_iocs = IocLink.query.with_entities(
IocLink.ioc_id
).filter(
IocLink.ioc_id.in_(iocs_list),
IocLink.case_id == caseid
).all()
for ioc in valid_iocs:
try:
cea = CaseEventsIoc()
cea.ioc_id = int(ioc.ioc_id)
cea.event_id = event_id
cea.case_id = caseid
db.session.add(cea)
except Exception as e:
return False, str(e)
db.session.commit()
return True, ''
def get_case_assets_for_tm(caseid):
"""
Return a list of all assets linked to the current case
:return: Tuple of assets
"""
assets = [{'asset_name': '', 'asset_id': '0'}]
assets_list = CaseAssets.query.with_entities(
CaseAssets.asset_name,
CaseAssets.asset_id,
AssetsType.asset_name.label('type')
).filter(
CaseAssets.case_id == caseid
).join(CaseAssets.asset_type).order_by(CaseAssets.asset_name).all()
for asset in assets_list:
assets.append({
'asset_name': "{} ({})".format(asset.asset_name, asset.type),
'asset_id': asset.asset_id
})
return assets
def get_case_iocs_for_tm(caseid):
iocs = [{'ioc_value': '', 'ioc_id': '0'}]
iocs_list = Ioc.query.with_entities(
Ioc.ioc_value,
Ioc.ioc_id
).filter(
IocLink.case_id == caseid
).join(
IocLink.ioc
).order_by(
Ioc.ioc_value
).all()
for ioc in iocs_list:
iocs.append({
'ioc_value': "{}".format(ioc.ioc_value),
'ioc_id': ioc.ioc_id
})
return iocs
def delete_event(event, caseid):
delete_event_category(event.event_id)
CaseEventsAssets.query.filter(
CaseEventsAssets.event_id == event.event_id,
CaseEventsAssets.case_id == caseid
).delete()
CaseEventsIoc.query.filter(
CaseEventsIoc.event_id == event.event_id,
CaseEventsIoc.case_id == caseid
).delete()
com_ids = EventComments.query.with_entities(
EventComments.comment_id
).filter(
EventComments.comment_event_id == event.event_id
).all()
com_ids = [c.comment_id for c in com_ids]
EventComments.query.filter(EventComments.comment_id.in_(com_ids)).delete()
Comments.query.filter(Comments.comment_id.in_(com_ids)).delete()
db.session.commit()
db.session.delete(event)
update_timeline_state(caseid=caseid)
db.session.commit()
def get_category_by_name(cat_name):
return EventCategory.query.filter(
EventCategory.name == cat_name,
).first()
def get_default_category():
return EventCategory.query.with_entities(
EventCategory.id,
EventCategory.name
).filter(
EventCategory.name == "Unspecified"
).first()

View File

@ -0,0 +1,356 @@
#!/usr/bin/env python3
#
# 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 flask_login import current_user
from sqlalchemy import and_
from app import db
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 import CaseEventsIoc
from app.models import Cases
from app.models import Client
from app.models import Comments
from app.models import Ioc
from app.models import IocAssetLink
from app.models import IocComments
from app.models import IocLink
from app.models import IocType
from app.models import Tlp
from app.models.authorization import User
def get_iocs(caseid):
iocs = IocLink.query.with_entities(
Ioc.ioc_value,
Ioc.ioc_id,
Ioc.ioc_uuid
).filter(
IocLink.case_id == caseid,
IocLink.ioc_id == Ioc.ioc_id
).all()
return iocs
def get_ioc(ioc_id, caseid=None):
if caseid:
return IocLink.query.with_entities(
Ioc
).filter(and_(
Ioc.ioc_id == ioc_id,
IocLink.case_id == caseid
)).join(
IocLink.ioc
).first()
return Ioc.query.filter(Ioc.ioc_id == ioc_id).first()
def update_ioc(ioc_type, ioc_tags, ioc_value, ioc_description, ioc_tlp, userid, ioc_id):
ioc = get_ioc(ioc_id)
if ioc:
ioc.ioc_type = ioc_type
ioc.ioc_tags = ioc_tags
ioc.ioc_value = ioc_value
ioc.ioc_description = ioc_description
ioc.ioc_tlp_id = ioc_tlp
ioc.user_id = userid
db.session.commit()
else:
return False
def delete_ioc(ioc, caseid):
with db.session.begin_nested():
IocLink.query.filter(
and_(
IocLink.ioc_id == ioc.ioc_id,
IocLink.case_id == caseid
)
).delete()
res = IocLink.query.filter(
IocLink.ioc_id == ioc.ioc_id,
).all()
if res:
return False
IocAssetLink.query.filter(
IocAssetLink.ioc_id == ioc.ioc_id
).delete()
CaseEventsIoc.query.filter(
CaseEventsIoc.ioc_id == ioc.ioc_id
).delete()
com_ids = IocComments.query.with_entities(
IocComments.comment_id
).filter(
IocComments.comment_ioc_id == ioc.ioc_id
).all()
com_ids = [c.comment_id for c in com_ids]
IocComments.query.filter(IocComments.comment_id.in_(com_ids)).delete()
Comments.query.filter(Comments.comment_id.in_(com_ids)).delete()
db.session.delete(ioc)
update_ioc_state(caseid=caseid)
return True
def get_detailed_iocs(caseid):
detailed_iocs = IocLink.query.with_entities(
Ioc.ioc_id,
Ioc.ioc_uuid,
Ioc.ioc_value,
Ioc.ioc_type_id,
IocType.type_name.label('ioc_type'),
Ioc.ioc_type_id,
Ioc.ioc_description,
Ioc.ioc_tags,
Ioc.ioc_misp,
Tlp.tlp_name,
Tlp.tlp_bscolor,
Ioc.ioc_tlp_id
).filter(
and_(IocLink.case_id == caseid,
IocLink.ioc_id == Ioc.ioc_id)
).join(IocLink.ioc,
Ioc.tlp,
Ioc.ioc_type
).order_by(IocType.type_name).all()
return detailed_iocs
def get_ioc_links(ioc_id, caseid):
search_condition = and_(Cases.case_id.in_([]))
user_search_limitations = ac_get_fast_user_cases_access(current_user.id)
if user_search_limitations:
search_condition = and_(Cases.case_id.in_(user_search_limitations))
ioc_link = IocLink.query.with_entities(
Cases.case_id,
Cases.name.label('case_name'),
Client.name.label('client_name')
).filter(and_(
IocLink.ioc_id == ioc_id,
IocLink.case_id != caseid,
search_condition)
).join(IocLink.case, Cases.client).all()
return ioc_link
def find_ioc(ioc_value, ioc_type_id):
ioc = Ioc.query.filter(Ioc.ioc_value == ioc_value,
Ioc.ioc_type_id == ioc_type_id).first()
return ioc
def add_ioc(ioc, user_id, caseid):
if not ioc:
return None, False
ioc.user_id = user_id
db_ioc = find_ioc(ioc.ioc_value, ioc.ioc_type_id)
if not db_ioc:
db.session.add(ioc)
update_ioc_state(caseid=caseid)
db.session.commit()
return ioc, False
else:
# IoC already exists
return db_ioc, True
def find_ioc_link(ioc_id, caseid):
db_link = IocLink.query.filter(
IocLink.case_id == caseid,
IocLink.ioc_id == ioc_id
).first()
return db_link
def add_ioc_link(ioc_id, caseid):
db_link = find_ioc_link(ioc_id, caseid)
if db_link:
# Link already exists
return True
else:
link = IocLink()
link.case_id = caseid
link.ioc_id = ioc_id
db.session.add(link)
db.session.commit()
return False
def get_ioc_types_list():
ioc_types = IocType.query.with_entities(
IocType.type_id,
IocType.type_name,
IocType.type_description,
IocType.type_taxonomy,
IocType.type_validation_regex,
IocType.type_validation_expect,
).all()
l_types = [row._asdict() for row in ioc_types]
return l_types
def add_ioc_type(name:str, description:str, taxonomy:str):
ioct = IocType(type_name=name,
type_description=description,
type_taxonomy=taxonomy
)
db.session.add(ioct)
db.session.commit()
return ioct
def check_ioc_type_id(type_id: int):
type_id = IocType.query.filter(
IocType.type_id == type_id
).first()
return type_id
def get_ioc_type_id(type_name: str):
type_id = IocType.query.filter(
IocType.type_name == type_name
).first()
return type_id if type_id else None
def get_tlps():
return [(tlp.tlp_id, tlp.tlp_name) for tlp in Tlp.query.all()]
def get_tlps_dict():
tlpDict = {}
for tlp in Tlp.query.all():
tlpDict[tlp.tlp_name]=tlp.tlp_id
return tlpDict
def get_case_ioc_comments(ioc_id):
return Comments.query.filter(
IocComments.comment_ioc_id == ioc_id
).with_entities(
Comments
).join(
IocComments,
Comments.comment_id == IocComments.comment_id
).order_by(
Comments.comment_date.asc()
).all()
def add_comment_to_ioc(ioc_id, comment_id):
ec = IocComments()
ec.comment_ioc_id = ioc_id
ec.comment_id = comment_id
db.session.add(ec)
db.session.commit()
def get_case_iocs_comments_count(iocs_list):
return IocComments.query.filter(
IocComments.comment_ioc_id.in_(iocs_list)
).with_entities(
IocComments.comment_ioc_id,
IocComments.comment_id
).group_by(
IocComments.comment_ioc_id,
IocComments.comment_id
).all()
def get_case_ioc_comment(ioc_id, comment_id):
return IocComments.query.filter(
IocComments.comment_ioc_id == ioc_id,
IocComments.comment_id == comment_id
).with_entities(
Comments.comment_id,
Comments.comment_text,
Comments.comment_date,
Comments.comment_update_date,
Comments.comment_uuid,
User.name,
User.user
).join(
IocComments.comment,
Comments.user
).first()
def delete_ioc_comment(ioc_id, comment_id):
comment = Comments.query.filter(
Comments.comment_id == comment_id,
Comments.comment_user_id == current_user.id
).first()
if not comment:
return False, "You are not allowed to delete this comment"
IocComments.query.filter(
IocComments.comment_ioc_id == ioc_id,
IocComments.comment_id == comment_id
).delete()
db.session.delete(comment)
db.session.commit()
return True, "Comment deleted"
def get_ioc_by_value(ioc_value, caseid=None):
if caseid:
return IocLink.query.with_entities(
Ioc
).filter(and_(
Ioc.ioc_value == ioc_value,
IocLink.case_id == caseid
)).join(
IocLink.ioc
).first()
return Ioc.query.filter(Ioc.ioc_value == ioc_value).first()

View File

@ -0,0 +1,374 @@
#!/usr/bin/env python3
#
# 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 flask_login import current_user
from sqlalchemy import and_
from app import db
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.states import update_notes_state
from app.models import Comments
from app.models import Notes
from app.models import NotesComments
from app.models import NotesGroup
from app.models import NotesGroupLink
from app.models.authorization import User
def get_note(note_id, caseid=None):
note = Notes.query.with_entities(
Notes.note_id,
Notes.note_uuid,
Notes.note_title,
Notes.note_content,
Notes.note_creationdate,
Notes.note_lastupdate,
NotesGroupLink.group_id,
NotesGroup.group_title,
NotesGroup.group_uuid,
Notes.custom_attributes,
Notes.note_case_id
).filter(and_(
Notes.note_id == note_id,
Notes.note_case_id == caseid
)).join(
NotesGroupLink.note,
NotesGroupLink.note_group
).first()
return note
def get_note_raw(note_id, caseid):
note = Notes.query.filter(
Notes.note_case_id == caseid,
Notes.note_id == note_id
).first()
return note
def delete_note(note_id, caseid):
with db.session.begin_nested():
NotesGroupLink.query.filter(and_(
NotesGroupLink.note_id == note_id,
NotesGroupLink.case_id == caseid
)).delete()
com_ids = NotesComments.query.with_entities(
NotesComments.comment_id
).filter(
NotesComments.comment_note_id == note_id
).all()
com_ids = [c.comment_id for c in com_ids]
NotesComments.query.filter(NotesComments.comment_id.in_(com_ids)).delete()
Comments.query.filter(Comments.comment_id.in_(com_ids)).delete()
Notes.query.filter(Notes.note_id == note_id).delete()
update_notes_state(caseid=caseid)
def update_note(note_content, note_title, update_date, user_id, note_id, caseid):
note = get_note_raw(note_id, caseid=caseid)
if note:
note.note_content = note_content
note.note_title = note_title
note.note_lastupdate = update_date
note.note_user = user_id
db.session.commit()
return note
else:
return None
def add_note(note_title, creation_date, user_id, caseid, group_id, note_content=""):
note = Notes()
note.note_title = note_title
note.note_creationdate = note.note_lastupdate = creation_date
note.note_content = note_content
note.note_case_id = caseid
note.note_user = user_id
note.custom_attributes = get_default_custom_attributes('note')
db.session.add(note)
update_notes_state(caseid=caseid, userid=user_id)
db.session.commit()
if note.note_id:
ngl = NotesGroupLink()
ngl.note_id = note.note_id
ngl.group_id = group_id
ngl.case_id = caseid
db.session.add(ngl)
db.session.commit()
return note
else:
return None
def get_groups_short(caseid):
groups_short = NotesGroup.query.with_entities(
NotesGroup.group_id,
NotesGroup.group_uuid,
NotesGroup.group_title
).filter(
NotesGroup.group_case_id == caseid
).order_by(
NotesGroup.group_id
).all()
return groups_short
def get_notes_from_group(caseid, group_id):
notes = NotesGroupLink.query.with_entities(
Notes.note_id,
Notes.note_uuid,
Notes.note_title,
User.user,
Notes.note_lastupdate
).filter(
NotesGroupLink.case_id == caseid,
NotesGroupLink.group_id == group_id,
).join(
NotesGroupLink.note,
Notes.user
).order_by(
Notes.note_id
).all()
return notes
def get_groups_detail(caseid):
groups = NotesGroupLink.query.with_entities(
NotesGroup.group_id,
NotesGroup.group_uuid,
NotesGroup.group_title,
Notes.note_id,
Notes.note_uuid,
Notes.note_title,
User.user,
Notes.note_lastupdate
).filter(
NotesGroupLink.case_id == caseid,
).join(
NotesGroupLink.note,
NotesGroupLink.note_group,
Notes.user
).group_by(
NotesGroup.group_id,
Notes.note_id,
User.user
).all()
return groups
def get_group_details(group_id, caseid):
group_l = NotesGroup.query.with_entities(
NotesGroup.group_id,
NotesGroup.group_uuid,
NotesGroup.group_title,
NotesGroup.group_creationdate,
NotesGroup.group_lastupdate
).filter(
NotesGroup.group_case_id == caseid
).filter(
NotesGroup.group_id == group_id
).first()
group = None
if group_l:
group = group_l._asdict()
group['notes'] = [note._asdict() for note in get_notes_from_group(caseid=caseid, group_id=group_id)]
return group
def add_note_group(group_title, caseid, userid, creationdate):
ng = NotesGroup()
ng.group_title = group_title
ng.group_case_id = caseid
ng.group_user = userid
ng.group_creationdate = creationdate
ng.group_lastupdate = creationdate
db.session.add(ng)
update_notes_state(caseid=caseid, userid=userid)
db.session.commit()
if group_title == '':
ng.group_title = "New notes group"
db.session.commit()
return ng
def delete_note_group(group_id, caseid):
ngl = NotesGroupLink.query.with_entities(
NotesGroupLink.note_id
).filter(
NotesGroupLink.group_id == group_id,
NotesGroupLink.case_id == caseid
).all()
if not ngl:
group = NotesGroup.query.filter(and_(
NotesGroup.group_id == group_id,
NotesGroup.group_case_id == caseid
)).first()
if not group:
return False
db.session.delete(group)
update_notes_state(caseid=caseid)
db.session.commit()
return True
to_delete = [row.note_id for row in ngl]
NotesGroupLink.query.filter(
NotesGroupLink.group_id == group_id,
NotesGroupLink.case_id == caseid
).delete()
db.session.commit()
for nid in to_delete:
Notes.query.filter(Notes.note_id == nid).delete()
NotesGroup.query.filter(and_(
NotesGroup.group_id == group_id,
NotesGroup.group_case_id == caseid
)).delete()
update_notes_state(caseid=caseid)
db.session.commit()
return True
def update_note_group(group_title, group_id, caseid):
ng = NotesGroup.query.filter(and_(
NotesGroup.group_id == group_id,
NotesGroup.group_case_id == caseid
)).first()
if ng:
ng.group_title = group_title
update_notes_state(caseid=caseid)
db.session.commit()
return ng
else:
return None
def find_pattern_in_notes(pattern, caseid):
notes = Notes.query.filter(
Notes.note_content.like(pattern),
Notes.note_case_id == caseid
).with_entities(
Notes.note_id,
Notes.note_title
).all()
return notes
def get_case_note_comments(note_id):
return Comments.query.filter(
NotesComments.comment_note_id == note_id
).join(
NotesComments,
Comments.comment_id == NotesComments.comment_id
).order_by(
Comments.comment_date.asc()
).all()
def add_comment_to_note(note_id, comment_id):
ec = NotesComments()
ec.comment_note_id = note_id
ec.comment_id = comment_id
db.session.add(ec)
db.session.commit()
def get_case_notes_comments_count(notes_list):
return NotesComments.query.filter(
NotesComments.comment_note_id.in_(notes_list)
).with_entities(
NotesComments.comment_note_id,
NotesComments.comment_id
).group_by(
NotesComments.comment_note_id,
NotesComments.comment_id
).all()
def get_case_note_comment(note_id, comment_id):
return NotesComments.query.filter(
NotesComments.comment_note_id == note_id,
NotesComments.comment_id == comment_id
).with_entities(
Comments.comment_id,
Comments.comment_text,
Comments.comment_date,
Comments.comment_update_date,
Comments.comment_uuid,
User.name,
User.user
).join(
NotesComments.comment,
Comments.user
).first()
def delete_note_comment(note_id, comment_id):
comment = Comments.query.filter(
Comments.comment_id == comment_id,
Comments.comment_user_id == current_user.id
).first()
if not comment:
return False, "You are not allowed to delete this comment"
NotesComments.query.filter(
NotesComments.comment_note_id == note_id,
NotesComments.comment_id == comment_id
).delete()
db.session.delete(comment)
db.session.commit()
return True, "Comment deleted"

View File

@ -0,0 +1,174 @@
#!/usr/bin/env python3
#
# 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.
import datetime
from flask_login import current_user
from sqlalchemy import and_
from sqlalchemy import desc
from app import db
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.states import update_evidences_state
from app.models import CaseReceivedFile
from app.models import Comments
from app.models import EvidencesComments
from app.models.authorization import User
def get_rfiles(caseid):
crf = CaseReceivedFile.query.with_entities(
CaseReceivedFile.id,
CaseReceivedFile.file_uuid,
CaseReceivedFile.filename,
CaseReceivedFile.date_added,
CaseReceivedFile.file_hash,
CaseReceivedFile.file_description,
CaseReceivedFile.file_size,
User.name.label('username')
).filter(
CaseReceivedFile.case_id == caseid
).join(CaseReceivedFile.user).order_by(desc(CaseReceivedFile.date_added)).all()
return crf
def add_rfile(evidence, caseid, user_id):
evidence.date_added = datetime.datetime.now()
evidence.case_id = caseid
evidence.user_id = user_id
evidence.custom_attributes = get_default_custom_attributes('evidence')
db.session.add(evidence)
update_evidences_state(caseid=caseid, userid=user_id)
db.session.commit()
return evidence
def get_rfile(rfile_id, caseid):
return CaseReceivedFile.query.filter(
CaseReceivedFile.id == rfile_id,
CaseReceivedFile.case_id == caseid
).first()
def update_rfile(evidence, user_id, caseid):
evidence.user_id = user_id
update_evidences_state(caseid=caseid, userid=user_id)
db.session.commit()
return evidence
def delete_rfile(rfile_id, caseid):
with db.session.begin_nested():
com_ids = EvidencesComments.query.with_entities(
EvidencesComments.comment_id
).filter(
EvidencesComments.comment_evidence_id == rfile_id
).all()
com_ids = [c.comment_id for c in com_ids]
EvidencesComments.query.filter(EvidencesComments.comment_id.in_(com_ids)).delete()
Comments.query.filter(Comments.comment_id.in_(com_ids)).delete()
CaseReceivedFile.query.filter(and_(
CaseReceivedFile.id == rfile_id,
CaseReceivedFile.case_id == caseid,
)).delete()
update_evidences_state(caseid=caseid)
db.session.commit()
def get_case_evidence_comments(evidence_id):
return Comments.query.filter(
EvidencesComments.comment_evidence_id == evidence_id
).join(
EvidencesComments,
Comments.comment_id == EvidencesComments.comment_id
).order_by(
Comments.comment_date.asc()
).all()
def add_comment_to_evidence(evidence_id, comment_id):
ec = EvidencesComments()
ec.comment_evidence_id = evidence_id
ec.comment_id = comment_id
db.session.add(ec)
db.session.commit()
def get_case_evidence_comments_count(evidences_list):
return EvidencesComments.query.filter(
EvidencesComments.comment_evidence_id.in_(evidences_list)
).with_entities(
EvidencesComments.comment_evidence_id,
EvidencesComments.comment_id
).group_by(
EvidencesComments.comment_evidence_id,
EvidencesComments.comment_id
).all()
def get_case_evidence_comment(evidence_id, comment_id):
return EvidencesComments.query.filter(
EvidencesComments.comment_evidence_id == evidence_id,
EvidencesComments.comment_id == comment_id
).with_entities(
Comments.comment_id,
Comments.comment_text,
Comments.comment_date,
Comments.comment_update_date,
Comments.comment_uuid,
User.name,
User.user
).join(
EvidencesComments.comment,
Comments.user
).first()
def delete_evidence_comment(evidence_id, comment_id):
comment = Comments.query.filter(
Comments.comment_id == comment_id,
Comments.comment_user_id == current_user.id
).first()
if not comment:
return False, "You are not allowed to delete this comment"
EvidencesComments.query.filter(
EvidencesComments.comment_evidence_id == evidence_id,
EvidencesComments.comment_id == comment_id
).delete()
db.session.delete(comment)
db.session.commit()
return True, "Comment deleted"

View File

@ -0,0 +1,335 @@
#!/usr/bin/env python3
#
# 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 datetime import datetime
from flask_login import current_user
from sqlalchemy import desc, and_
from app import db
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
from app.datamgmt.states import update_tasks_state
from app.models import CaseTasks, TaskAssignee
from app.models import Cases
from app.models import Comments
from app.models import TaskComments
from app.models import TaskStatus
from app.models.authorization import User
def get_tasks_status():
return TaskStatus.query.all()
def get_tasks(caseid):
return CaseTasks.query.with_entities(
CaseTasks.id.label("task_id"),
CaseTasks.task_uuid,
CaseTasks.task_title,
CaseTasks.task_description,
CaseTasks.task_open_date,
CaseTasks.task_tags,
CaseTasks.task_status_id,
TaskStatus.status_name,
TaskStatus.status_bscolor
).filter(
CaseTasks.task_case_id == caseid
).join(
CaseTasks.status
).order_by(
desc(TaskStatus.status_name)
).all()
def get_tasks_with_assignees(caseid):
tasks = get_tasks(caseid)
if not tasks:
return None
tasks = [c._asdict() for c in tasks]
task_with_assignees = []
for task in tasks:
task_id = task['task_id']
get_assignee_list = TaskAssignee.query.with_entities(
TaskAssignee.task_id,
User.user,
User.id,
User.name
).join(
TaskAssignee.user
).filter(
TaskAssignee.task_id == task_id
).all()
assignee_list = {}
for member in get_assignee_list:
if member.task_id not in assignee_list:
assignee_list[member.task_id] = [{
'user': member.user,
'name': member.name,
'id': member.id
}]
else:
assignee_list[member.task_id].append({
'user': member.user,
'name': member.name,
'id': member.id
})
task['task_assignees'] = assignee_list.get(task['task_id'], [])
task_with_assignees.append(task)
return task_with_assignees
def get_task(task_id, caseid):
return CaseTasks.query.filter(CaseTasks.id == task_id, CaseTasks.task_case_id == caseid).first()
def get_task_with_assignees(task_id: int, case_id: int):
"""
Returns a task with its assignees
Args:
task_id (int): Task ID
case_id (int): Case ID
Returns:
dict: Task with its assignees
"""
task = get_task(
task_id=task_id,
caseid=case_id
)
if not task:
return None
get_assignee_list = TaskAssignee.query.with_entities(
TaskAssignee.task_id,
User.user,
User.id,
User.name
).join(
TaskAssignee.user
).filter(
TaskAssignee.task_id == task_id
).all()
assignee_list = {}
for member in get_assignee_list:
if member.task_id not in assignee_list:
assignee_list[member.task_id] = [{
'user': member.user,
'name': member.name,
'id': member.id
}]
else:
assignee_list[member.task_id].append({
'user': member.user,
'name': member.name,
'id': member.id
})
setattr(task, 'task_assignees', assignee_list.get(task.id, []))
return task
def update_task_status(task_status, task_id, caseid):
task = get_task(task_id, caseid)
if task:
try:
task.task_status_id = task_status
update_tasks_state(caseid=caseid)
db.session.commit()
return True
except:
return False
else:
return False
def update_task_assignees(task, task_assignee_list, caseid):
if not task:
return None
cur_assignee_list = TaskAssignee.query.with_entities(
TaskAssignee.user_id
).filter(TaskAssignee.task_id == task.id).all()
# Some formatting
set_cur_assignees = set([assignee[0] for assignee in cur_assignee_list])
set_assignees = set(int(assignee) for assignee in task_assignee_list)
assignees_to_add = set_assignees - set_cur_assignees
assignees_to_remove = set_cur_assignees - set_assignees
allowed_users = [u.get('user_id') for u in get_users_list_restricted_from_case(caseid)]
for uid in assignees_to_add:
if uid not in allowed_users:
continue
user = User.query.filter(User.id == uid).first()
if user:
ta = TaskAssignee()
ta.task_id = task.id
ta.user_id = user.id
db.session.add(ta)
for uid in assignees_to_remove:
TaskAssignee.query.filter(
and_(TaskAssignee.task_id == task.id,
TaskAssignee.user_id == uid)
).delete()
db.session.commit()
return task
def add_task(task, assignee_id_list, user_id, caseid):
now = datetime.now()
task.task_case_id = caseid
task.task_userid_open = user_id
task.task_userid_update = user_id
task.task_open_date = now
task.task_last_update = now
task.custom_attributes = task.custom_attributes if task.custom_attributes else get_default_custom_attributes('task')
db.session.add(task)
update_tasks_state(caseid=caseid)
db.session.commit()
update_task_status(task.task_status_id, task.id, caseid)
update_task_assignees(task, assignee_id_list, caseid)
return task
def get_case_task_comments(task_id):
return Comments.query.filter(
TaskComments.comment_task_id == task_id
).join(
TaskComments,
Comments.comment_id == TaskComments.comment_id
).order_by(
Comments.comment_date.asc()
).all()
def add_comment_to_task(task_id, comment_id):
ec = TaskComments()
ec.comment_task_id = task_id
ec.comment_id = comment_id
db.session.add(ec)
db.session.commit()
def get_case_tasks_comments_count(tasks_list):
return TaskComments.query.filter(
TaskComments.comment_task_id.in_(tasks_list)
).with_entities(
TaskComments.comment_task_id,
TaskComments.comment_id
).group_by(
TaskComments.comment_task_id,
TaskComments.comment_id
).all()
def get_case_task_comment(task_id, comment_id):
return TaskComments.query.filter(
TaskComments.comment_task_id == task_id,
TaskComments.comment_id == comment_id
).with_entities(
Comments.comment_id,
Comments.comment_text,
Comments.comment_date,
Comments.comment_update_date,
Comments.comment_uuid,
User.name,
User.user
).join(
TaskComments.comment,
Comments.user
).first()
def delete_task(task_id):
with db.session.begin_nested():
TaskAssignee.query.filter(
TaskAssignee.task_id == task_id
).delete()
com_ids = TaskComments.query.with_entities(
TaskComments.comment_id
).filter(
TaskComments.comment_task_id == task_id
).all()
com_ids = [c.comment_id for c in com_ids]
TaskComments.query.filter(TaskComments.comment_id.in_(com_ids)).delete()
Comments.query.filter(Comments.comment_id.in_(com_ids)).delete()
CaseTasks.query.filter(
CaseTasks.id == task_id
).delete()
def delete_task_comment(task_id, comment_id):
comment = Comments.query.filter(
Comments.comment_id == comment_id,
Comments.comment_user_id == current_user.id
).first()
if not comment:
return False, "You are not allowed to delete this comment"
TaskComments.query.filter(
TaskComments.comment_task_id == task_id,
TaskComments.comment_id == comment_id
).delete()
db.session.delete(comment)
db.session.commit()
return True, "Comment deleted"
def get_tasks_cases_mapping(open_cases_only=False):
condition = Cases.close_date == None if open_cases_only else True
return CaseTasks.query.filter(
condition
).with_entities(
CaseTasks.task_case_id,
CaseTasks.task_status_id
).join(
CaseTasks.case
).all()

View File

@ -0,0 +1,200 @@
#!/usr/bin/env python3
#
# 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.
import marshmallow
from sqlalchemy import func
from typing import List
from app import db
from app.datamgmt.exceptions.ElementExceptions import ElementInUseException
from app.datamgmt.exceptions.ElementExceptions import ElementNotFoundException
from app.models import Cases
from app.models import Client
from app.models import Contact
from app.models.authorization import User
from app.schema.marshables import ContactSchema
from app.schema.marshables import CustomerSchema
def get_client_list() -> List[Client]:
client_list = Client.query.with_entities(
Client.name.label('customer_name'),
Client.client_id.label('customer_id'),
Client.client_uuid.label('customer_uuid'),
Client.description.label('customer_description'),
Client.sla.label('customer_sla')
).all()
output = [c._asdict() for c in client_list]
return output
def get_client(client_id: int) -> Client:
client = Client.query.filter(Client.client_id == client_id).first()
return client
def get_client_api(client_id: str) -> Client:
client = Client.query.with_entities(
Client.name.label('customer_name'),
Client.client_id.label('customer_id'),
Client.client_uuid.label('customer_uuid'),
Client.description.label('customer_description'),
Client.sla.label('customer_sla')
).filter(Client.client_id == client_id).first()
output = None
if client:
output = client._asdict()
return output
def get_client_cases(client_id: int):
cases_list = Cases.query.with_entities(
Cases.case_id.label('case_id'),
Cases.case_uuid.label('case_uuid'),
Cases.name.label('case_name'),
Cases.description.label('case_description'),
Cases.status_id.label('case_status'),
User.name.label('case_owner'),
Cases.open_date,
Cases.close_date
).filter(
Cases.client_id == client_id,
).join(
Cases.user
).all()
return cases_list
def create_client(data) -> Client:
client_schema = CustomerSchema()
client = client_schema.load(data)
db.session.add(client)
db.session.commit()
return client
def get_client_contacts(client_id: int) -> List[Contact]:
contacts = Contact.query.filter(
Contact.client_id == client_id
).order_by(
Contact.contact_name
).all()
return contacts
def get_client_contact(client_id: int, contact_id: int) -> Contact:
contact = Contact.query.filter(
Contact.client_id == client_id,
Contact.id == contact_id
).first()
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.')
try:
db.session.delete(contact)
db.session.commit()
except Exception as e:
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_client(client_id: int, data) -> Client:
# TODO: Possible reuse somewhere else ...
client = get_client(client_id)
if not client:
raise ElementNotFoundException('No Customer found with this uuid.')
exists = Client.query.filter(
Client.client_id != 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"
)
client_schema = CustomerSchema()
client_schema.load(data, instance=client)
db.session.commit()
return client
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.')
try:
db.session.delete(client)
db.session.commit()
except Exception as e:
raise ElementInUseException('A currently referenced customer cannot be deleted')

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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 sqlalchemy import desc
from app.models import Cases
from app.models import Client
from app.models.authorization import CaseAccessLevel
from app.models.authorization import UserCaseEffectiveAccess
def ctx_get_user_cases(user_id, max_results: int = 100):
uceas = UserCaseEffectiveAccess.query.with_entities(
Cases.case_id,
Cases.name,
Client.name.label('customer_name'),
Cases.close_date,
UserCaseEffectiveAccess.access_level
).join(
UserCaseEffectiveAccess.case,
Cases.client
).order_by(
desc(Cases.case_id)
).filter(
UserCaseEffectiveAccess.user_id == user_id
).limit(max_results).all()
results = []
for ucea in uceas:
if ucea.access_level & CaseAccessLevel.deny_all.value == CaseAccessLevel.deny_all.value:
continue
row = ucea._asdict()
if ucea.access_level == CaseAccessLevel.read_only.value:
row['access'] = '[Read-only]'
else:
row['access'] = ''
results.append(row)
return results
def ctx_search_user_cases(search, user_id, max_results: int = 100):
uceas = UserCaseEffectiveAccess.query.with_entities(
Cases.case_id,
Cases.name,
Client.name.label('customer_name'),
Cases.close_date,
UserCaseEffectiveAccess.access_level
).join(
UserCaseEffectiveAccess.case,
Cases.client
).order_by(
desc(Cases.case_id)
).filter(and_(
UserCaseEffectiveAccess.user_id == user_id,
Cases.name.ilike('%{}%'.format(search))
)
).limit(max_results).all()
results = []
for ucea in uceas:
if ucea.access_level & CaseAccessLevel.deny_all.value == CaseAccessLevel.deny_all.value:
continue
row = ucea._asdict()
if ucea.access_level == CaseAccessLevel.read_only.value:
row['access'] = '[Read-only]'
else:
row['access'] = ''
results.append(row)
return results

View File

@ -0,0 +1,185 @@
#!/usr/bin/env python3
#
# 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 flask_login import current_user
from sqlalchemy import and_
from sqlalchemy import desc
from app import db
from app.models import CaseTasks, TaskAssignee, ReviewStatus
from app.models import Cases
from app.models import GlobalTasks
from app.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,
GlobalTasks.status
).order_by(
desc(TaskStatus.status_name)
).first()
return ct
def get_tasks_status():
return TaskStatus.query.all()
def list_user_reviews():
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 == current_user.id,
ReviewStatus.status_name != 'Reviewed',
ReviewStatus.status_name != 'Not reviewed'
).all()
return ct
def list_user_tasks():
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 == current_user.id
)).all()
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(
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
).first()
return ret
def list_user_cases(show_all=False):
if show_all:
return Cases.query.filter(
Cases.owner_id == current_user.id
).all()
return Cases.query.filter(
Cases.owner_id == current_user.id,
Cases.close_date == None
).all()

View File

@ -0,0 +1,566 @@
#!/usr/bin/env python3
#
#
# IRIS Source Code
# Copyright (C) 2022 - DFIR IRIS Team
# contact@dfir-iris.org
# Created by whitekernel - 2022-05-17
#
# 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 datetime
from pathlib import Path
from flask_login import current_user
from sqlalchemy import and_
from sqlalchemy import func
from app import app
from app import db
from app.datamgmt.case.case_iocs_db import add_ioc_link
from app.models import CaseReceivedFile
from app.models import DataStoreFile
from app.models import DataStorePath
from app.models import Ioc
from app.models import IocType
from app.models import Tlp
def datastore_get_root(cid):
dsp_root = DataStorePath.query.filter(
and_(DataStorePath.path_case_id == cid,
DataStorePath.path_is_root == True
)
).first()
if dsp_root is None:
init_ds_tree(cid)
dsp_root = DataStorePath.query.filter(
and_(DataStorePath.path_case_id == cid,
DataStorePath.path_is_root == True
)
).first()
return dsp_root
def ds_list_tree(cid):
dsp_root = datastore_get_root(cid)
dsp = DataStorePath.query.filter(
and_(DataStorePath.path_case_id == cid,
DataStorePath.path_is_root == False
)
).order_by(
DataStorePath.path_parent_id
).all()
dsf = DataStoreFile.query.filter(
DataStoreFile.file_case_id == cid
).all()
dsp_root = dsp_root
droot_id = f"d-{dsp_root.path_id}"
path_tree = {
droot_id: {
"name": dsp_root.path_name,
"type": "directory",
"is_root": True,
"children": {}
}
}
droot_children = path_tree[droot_id]["children"]
files_nodes = {}
for dfile in dsf:
dfnode = dfile.__dict__
dfnode.pop('_sa_instance_state')
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({
dnode_id: dfnode
})
elif dnode_parent_id in files_nodes:
files_nodes[dnode_parent_id].update({
dnode_id: dfnode
})
else:
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}"
path_node = {
dpath_id: {
"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)
if dpath_parent_id == droot_id:
droot_children.update(path_node)
else:
datastore_iter_tree(dpath_parent_id, path_node, droot_children)
return path_tree
def init_ds_tree(cid):
dsp_root = DataStorePath.query.filter(
and_(DataStorePath.path_case_id == cid,
DataStorePath.path_is_root == True
)
).all()
if dsp_root:
return dsp_root
dsp_root = DataStorePath()
dsp_root.path_name = f'Case {cid}'
dsp_root.path_is_root = True
dsp_root.path_case_id = cid
dsp_root.path_parent_id = 0
db.session.add(dsp_root)
db.session.commit()
for path in ['Evidences', 'IOCs', 'Images']:
dsp_init = DataStorePath()
dsp_init.path_parent_id = dsp_root.path_id
dsp_init.path_case_id = cid
dsp_init.path_name = path
dsp_init.path_is_root = False
db.session.add(dsp_init)
db.session.commit()
return dsp_root
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)
return tree
if isinstance(node, dict):
datastore_iter_tree(path_parent_id, path_node, node)
return None
def datastore_add_child_node(parent_node, folder_name, cid):
try:
dsp_base = DataStorePath.query.filter(
DataStorePath.path_id == parent_node,
DataStorePath.path_case_id == cid
).first()
except Exception as e:
return True, f'Unable to request datastore for parent node : {parent_node}', None
if dsp_base is None:
return True, 'Parent node is invalid for this case', None
dsp = DataStorePath()
dsp.path_case_id = cid
dsp.path_name = folder_name
dsp.path_parent_id = parent_node
dsp.path_is_root = False
db.session.add(dsp)
db.session.commit()
return False, 'Folder added', dsp
def datastore_rename_node(parent_node, folder_name, cid):
try:
dsp_base = DataStorePath.query.filter(
DataStorePath.path_id == parent_node,
DataStorePath.path_case_id == cid
).first()
except Exception as e:
return True, f'Unable to request datastore for parent node : {parent_node}', None
if dsp_base is None:
return True, 'Parent node is invalid for this case', None
dsp_base.path_name = folder_name
db.session.commit()
return False, 'Folder renamed', dsp_base
def datastore_delete_node(node_id, cid):
try:
dsp_base = DataStorePath.query.filter(
DataStorePath.path_id == node_id,
DataStorePath.path_case_id == cid
).first()
except Exception as e:
return True, f'Unable to request datastore for parent node : {node_id}'
if dsp_base is None:
return True, 'Parent node is invalid for this case'
datastore_iter_deletion(dsp_base, cid)
return False, 'Folder and children deleted'
def datastore_iter_deletion(dsp, cid):
dsp_children = DataStorePath.query.filter(
and_(DataStorePath.path_case_id == cid,
DataStorePath.path_is_root == False,
DataStorePath.path_parent_id == dsp.path_id
)
).all()
for dsp_child in dsp_children:
datastore_iter_deletion(dsp_child, cid)
datastore_delete_files_of_path(dsp.path_id, cid)
db.session.delete(dsp)
db.session.commit()
return None
def datastore_delete_files_of_path(node_id, cid):
dsf_list = DataStoreFile.query.filter(
and_(DataStoreFile.file_parent_id == node_id,
DataStoreFile.file_case_id == cid
)
).all()
for dsf_list_item in dsf_list:
fln = Path(dsf_list_item.file_local_name)
if fln.is_file():
fln.unlink(missing_ok=True)
db.session.delete(dsf_list_item)
db.session.commit()
return
def datastore_get_path_node(node_id, cid):
return DataStorePath.query.filter(
DataStorePath.path_id == node_id,
DataStorePath.path_case_id == cid
).first()
def datastore_get_interactive_path_node(cid):
dsp = DataStorePath.query.filter(
DataStorePath.path_name == 'Notes Upload',
DataStorePath.path_case_id == cid
).first()
if not dsp:
dsp_root = datastore_get_root(cid)
dsp = DataStorePath()
dsp.path_case_id = cid
dsp.path_parent_id = dsp_root.path_id
dsp.path_name = 'Notes Upload'
dsp.path_is_root = False
db.session.add(dsp)
db.session.commit()
return dsp
def datastore_get_standard_path(datastore_file, cid):
root_path = Path(app.config['DATASTORE_PATH'])
if datastore_file.file_is_ioc:
target_path = root_path / 'IOCs'
elif datastore_file.file_is_evidence:
target_path = root_path / 'Evidences'
else:
target_path = root_path / 'Regulars'
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}"
def datastore_get_file(file_id, cid):
dsf = DataStoreFile.query.filter(
DataStoreFile.file_id == file_id,
DataStoreFile.file_case_id == cid
).first()
return dsf
def datastore_delete_file(cur_id, cid):
dsf = DataStoreFile.query.filter(
DataStoreFile.file_id == cur_id,
DataStoreFile.file_case_id == cid
).first()
if dsf is None:
return True, 'Invalid DS file ID for this case'
fln = Path(dsf.file_local_name)
if fln.is_file():
fln.unlink(missing_ok=True)
db.session.delete(dsf)
db.session.commit()
return False, f'File {cur_id} deleted'
def datastore_add_file_as_ioc(dsf, caseid):
ioc = Ioc.query.filter(
Ioc.ioc_value == dsf.file_sha256
).first()
ioc_type_id = IocType.query.filter(
IocType.type_name == 'sha256'
).first()
ioc_tlp_id = Tlp.query.filter(
Tlp.tlp_name == 'amber'
).first()
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_type_id = ioc_type_id.type_id
ioc.ioc_tlp_id = ioc_tlp_id.tlp_id
ioc.ioc_tags = "datastore"
ioc.user_id = current_user.id
db.session.add(ioc)
db.session.commit()
add_ioc_link(ioc.ioc_id, caseid)
return
def datastore_add_file_as_evidence(dsf, caseid):
crf = CaseReceivedFile.query.filter(
CaseReceivedFile.file_hash == dsf.file_sha256
).first()
if crf is None:
crf = CaseReceivedFile()
crf.file_hash = dsf.file_sha256
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
crf.file_size = dsf.file_size
crf.user_id = current_user.id
db.session.add(crf)
db.session.commit()
return
def datastore_get_local_file_path(file_id, caseid):
dsf = DataStoreFile.query.filter(
DataStoreFile.file_id == file_id,
DataStoreFile.file_case_id == caseid
).first()
if dsf is None:
return True, 'Invalid DS file ID for this case'
return False, dsf
def datastore_filter_tree(filter_d, caseid):
names = filter_d.get('name')
storage_names = filter_d.get('storage_name')
tags = filter_d.get('tag')
descriptions = filter_d.get('description')
is_ioc = filter_d.get('is_ioc')
is_evidence = filter_d.get('is_evidence')
has_password = filter_d.get('has_password')
file_id = filter_d.get('id')
file_uuid = filter_d.get('uuid')
file_sha256 = filter_d.get('sha256')
condition = (DataStoreFile.file_case_id == caseid)
if file_id:
for fid in file_id:
if fid:
fid = fid.replace('dsf-', '')
condition = and_(condition, DataStoreFile.file_id == fid)
if file_uuid:
for fuid in file_uuid:
if fuid:
fuid = fuid.replace('dsf-', '')
condition = and_(condition, DataStoreFile.file_uuid == fuid)
if file_sha256:
for fsha in file_sha256:
if fsha:
condition = and_(condition, func.lower(DataStoreFile.file_sha256) == fsha.lower())
if names:
for name in names:
condition = and_(condition,
DataStoreFile.file_original_name.ilike(f'%{name}%'))
if storage_names:
for name in storage_names:
condition = and_(condition,
DataStoreFile.file_local_name.ilike(f'%{name}%'))
if tags:
for tag in tags:
condition = and_(condition,
DataStoreFile.file_tags.ilike(f'%{tag}%'))
if descriptions:
for description in descriptions:
condition = and_(condition,
DataStoreFile.file_description.ilike(f'%{description}%'))
if is_ioc is not None:
condition = and_(condition,
(DataStoreFile.file_is_ioc == True))
if is_evidence is not None:
condition = and_(condition,
(DataStoreFile.file_is_evidence == True))
if has_password is not None:
condition = and_(condition,
(DataStoreFile.file_password != ""))
dsp_root = DataStorePath.query.filter(
and_(DataStorePath.path_case_id == caseid,
DataStorePath.path_is_root == True
)
).first()
if dsp_root is None:
init_ds_tree(caseid)
dsp_root = DataStorePath.query.filter(
and_(DataStorePath.path_case_id == caseid,
DataStorePath.path_is_root == True
)
).first()
dsp = DataStorePath.query.filter(
and_(DataStorePath.path_case_id == caseid,
DataStorePath.path_is_root == False
)
).order_by(
DataStorePath.path_parent_id
).all()
try:
dsf = DataStoreFile.query.filter(
condition
).all()
except Exception as e:
return None, str(e)
dsp_root = dsp_root
droot_id = f"d-{dsp_root.path_id}"
path_tree = {
droot_id: {
"name": dsp_root.path_name,
"type": "directory",
"is_root": True,
"children": {}
}
}
droot_children = path_tree[droot_id]["children"]
files_nodes = {}
for dfile in dsf:
dfnode = dfile.__dict__
dfnode.pop('_sa_instance_state')
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({
dnode_id: dfnode
})
elif dnode_parent_id in files_nodes:
files_nodes[dnode_parent_id].update({
dnode_id: dfnode
})
else:
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}"
path_node = {
dpath_id: {
"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)
if dpath_parent_id == droot_id:
droot_children.update(path_node)
else:
datastore_iter_tree(dpath_parent_id, path_node, droot_children)
return path_tree, 'Success'

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
#
# 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 ElementNotFoundException(Exception):
pass
class ElementInUseException(Exception):
pass

View File

@ -0,0 +1,50 @@
from flask_login import current_user
from sqlalchemy import and_
from app.models import SavedFilter
def get_filter_by_id(filter_id):
"""
Get a filter by its ID
args:
filter_id: the ID of the filter to get
returns:
SavedFilter object
"""
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 != current_user.id:
return None
return saved_filter
def list_filters_by_type(filter_type):
"""
List filters by type
args:
filter_type: the type of filter to list
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,
SavedFilter.created_by == current_user.id,
SavedFilter.filter_type == filter_type
)
)
all_filters = public_filters.union_all(private_filters_for_user).all()
return all_filters

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python3
#
# 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 app.datamgmt.case.case_rfiles_db import add_rfile
from app.models import CaseReceivedFile
class EvidenceStorage(object):
@staticmethod
def is_evidence_registered(case_id, sha256):
data = CaseReceivedFile.query.filter(
CaseReceivedFile.case_id == case_id,
CaseReceivedFile.file_hash == sha256
).first()
return True if data else False
@staticmethod
def add_evidence(case_id, filename, description, size, sha256, date_added, user_id):
evidence = CaseReceivedFile()
evidence.file_description = description
evidence.filename = filename
evidence.file_size = size
evidence.file_hash = sha256
return add_rfile(evidence, case_id, user_id)

View File

@ -0,0 +1,261 @@
#!/usr/bin/env python3
#
# 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.
import base64
import datetime
from flask_login import current_user
from app import db, app
from app.models import IrisHook
from app.models import IrisModule
from app.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
def iris_module_name_from_id(module_id):
data = IrisModule.query.filter(IrisModule.id == module_id).first()
if data:
return data.module_name
return None
def iris_module_add(module_name, module_human_name, module_description,
module_version, interface_version, has_pipeline, pipeline_args, module_config, module_type):
im = IrisModule()
im.module_name = module_name
im.module_human_name = module_human_name
im.module_description = module_description
im.module_version = module_version
im.interface_version = interface_version
im.date_added = datetime.datetime.utcnow()
im.has_pipeline = has_pipeline
im.pipeline_args = pipeline_args
im.module_config = module_config
im.added_by_id = current_user.id if current_user else User.query.first().id
im.is_active = True
im.module_type = module_type
try:
db.session.add(im)
db.session.commit()
except Exception:
return None
return im
def is_mod_configured(mod_config):
missing_params = []
for config in mod_config:
if config['mandatory'] and ("value" not in config or config["value"] == ""):
missing_params.append(config['param_name'])
return len(missing_params) == 0, missing_params
def iris_module_save_parameter(mod_id, mod_config, parameter, value, section=None):
data = IrisModule.query.filter(IrisModule.id == mod_id).first()
if data is None:
return False
index = 0
for config in mod_config:
if config['param_name'] == parameter:
if config['type'] == "bool":
if isinstance(value, str):
value = bool(value.lower() == "true")
elif isinstance(value, bool):
value = bool(value)
else:
value = False
mod_config[index]["value"] = value
data.module_config = mod_config
db.session.commit()
return True
index += 1
return False
def iris_module_enable_by_id(module_id):
data = IrisModule.query.filter(IrisModule.id == module_id).first()
if data:
data.is_active = True
db.session.commit()
return True
return False
def iris_module_disable_by_id(module_id):
data = IrisModule.query.filter(IrisModule.id == module_id).first()
if data:
data.is_active = False
db.session.commit()
return True
return False
def iris_modules_list():
data = IrisModule.query.with_entities(
IrisModule.id, IrisModule.module_human_name, IrisModule.has_pipeline, IrisModule.module_version,
IrisModule.interface_version, IrisModule.date_added, User.name, IrisModule.is_active, IrisModule.module_config
).join(User).all()
ret = []
for element in data:
dict_element = element._asdict()
mod_configured, _ = is_mod_configured(element.module_config)
if not mod_configured:
iris_module_disable_by_id(element.id)
dict_element['configured'] = False
else:
dict_element['configured'] = True
ret.append(dict_element)
return ret
def get_module_from_id(module_id):
data = IrisModule.query.filter(IrisModule.id == module_id).first()
return data
def get_module_config_from_id(module_id):
data = IrisModule.query.with_entities(
IrisModule.module_config,
IrisModule.module_human_name,
IrisModule.module_name
).filter(
IrisModule.id == module_id
).first()
return data.module_config, data.module_human_name, data.module_name
def get_module_config_from_name(module_name):
data = IrisModule.query.with_entities(
IrisModule.module_config,
IrisModule.module_human_name
).filter(
IrisModule.module_name == module_name
).first()
return data
def get_module_config_from_hname(module_name):
data = IrisModule.query.with_entities(
IrisModule.module_config
).filter(
IrisModule.module_human_name == module_name
).first()
if data:
return data[0]
else:
return None
def get_pipelines_args_from_name(module_name):
data = IrisModule.query.with_entities(
IrisModule.pipeline_args
).filter(
IrisModule.module_name == module_name
).first()
return data.pipeline_args
def delete_module_from_id(module_id):
IrisModuleHook.query.filter(
IrisModuleHook.module_id == module_id
).delete()
db.session.commit()
IrisModule.query.filter(IrisModule.id == module_id).delete()
db.session.commit()
return True
def modules_list_pipelines():
return IrisModule.query.filter(
IrisModule.has_pipeline == True,
IrisModule.is_active == True
).with_entities(
IrisModule.module_name,
IrisModule.pipeline_args
).all()
def module_list_hooks_view():
return IrisModuleHook.query.with_entities(
IrisModuleHook.id,
IrisModule.module_name,
IrisModule.is_active,
IrisHook.hook_name,
IrisHook.hook_description,
IrisModuleHook.is_manual_hook
).join(
IrisModuleHook.module,
IrisModuleHook.hook
).all()
def module_list_available_hooks():
return IrisHook.query.with_entities(
IrisHook.id,
IrisHook.hook_name,
IrisHook.hook_description
).all()
def parse_module_parameter(module_parameter):
try:
param = base64.b64decode(module_parameter).decode('utf-8')
mod_id = param.split('##')[0]
param_name = param.split('##')[1]
except Exception as e:
log.exception(e)
return None, None, None, None
mod_config, mod_name, mod_iname = get_module_config_from_id(mod_id)
parameter = None
for param in mod_config:
if param_name == param['param_name']:
parameter = param
break
if not parameter:
return None, None, None, None
return mod_config, mod_id, mod_name, mod_iname, parameter

View File

@ -0,0 +1,71 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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 import Cases
from app.models.authorization import Group
from app.models.authorization import GroupCaseAccess
from app.models.authorization import Organisation
from app.models.authorization import OrganisationCaseAccess
from app.models.authorization import User
from app.models.authorization import UserCaseAccess
def manage_ac_audit_users_db():
uca = UserCaseAccess.query.with_entities(
User.name,
User.user,
User.id,
User.uuid,
UserCaseAccess.access_level,
Cases.name,
Cases.case_id
).join(
UserCaseAccess.case,
UserCaseAccess.user
).all()
gca = GroupCaseAccess.query.with_entities(
Group.group_name,
Group.group_id,
Group.group_uuid,
GroupCaseAccess.access_level,
Cases.name,
Cases.case_id
).join(
GroupCaseAccess.case,
GroupCaseAccess.group
).all()
oca = OrganisationCaseAccess.query.with_entities(
Organisation.org_name,
Organisation.org_id,
Organisation.org_uuid,
OrganisationCaseAccess.access_level,
Cases.name,
Cases.case_id
).all()
ret = {
'users': [u._asdict() for u in uca],
'groups': [g._asdict() for g in gca],
'organisations': [o._asdict() for o in oca]
}
return ret

View File

@ -0,0 +1,292 @@
#!/usr/bin/env python3
#
# 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.
import json
import logging as logger
from sqlalchemy.orm.attributes import flag_modified
from app import db, app
from app.models import CaseAssets
from app.models import CaseReceivedFile
from app.models import CaseTasks
from app.models import Cases
from app.models import CasesEvent
from app.models import Client
from app.models import CustomAttribute
from app.models import Ioc
from app.models import Notes
log = logger.getLogger(__name__)
def update_all_attributes(object_type, previous_attribute, partial_overwrite=False, complete_overwrite=False):
obj_list = []
if object_type == 'ioc':
obj_list = Ioc.query.all()
elif object_type == 'event':
obj_list = CasesEvent.query.all()
elif object_type == 'asset':
obj_list = CaseAssets.query.all()
elif object_type == 'task':
obj_list = CaseTasks.query.all()
elif object_type == 'note':
obj_list = Notes.query.all()
elif object_type == 'evidence':
obj_list = CaseReceivedFile.query.all()
elif object_type == 'case':
obj_list = Cases.query.all()
elif object_type == 'client':
obj_list = Client.query.all()
target_attr = get_default_custom_attributes(object_type)
app.logger.info(f'Migrating {len(obj_list)} objects of type {object_type}')
for obj in obj_list:
if complete_overwrite or obj.custom_attributes is None:
app.logger.info('Achieving complete overwrite')
obj.custom_attributes = target_attr
flag_modified(obj, "custom_attributes")
db.session.commit()
continue
for tab in target_attr:
if obj.custom_attributes.get(tab) is None or partial_overwrite:
app.logger.info(f'Migrating {tab}')
flag_modified(obj, "custom_attributes")
obj.custom_attributes[tab] = target_attr[tab]
else:
for element in target_attr[tab]:
if element not in obj.custom_attributes[tab]:
app.logger.info(f'Migrating {element}')
flag_modified(obj, "custom_attributes")
obj.custom_attributes[tab][element] = target_attr[tab][element]
else:
if obj.custom_attributes[tab][element]['type'] != target_attr[tab][element]['type']:
if (obj.custom_attributes[tab][element]['value'] == target_attr[tab][element]['value']) or \
(obj.custom_attributes[tab][element]['type'] in ('input_string', 'input_text_field') and
target_attr[tab][element]['type'] in ('input_string', 'input_text_field')):
flag_modified(obj, "custom_attributes")
obj.custom_attributes[tab][element]['type'] = target_attr[tab][element]['type']
if 'mandatory' in target_attr[tab][element] \
and obj.custom_attributes[tab][element]['mandatory'] != target_attr[tab][element]['mandatory']:
flag_modified(obj, "custom_attributes")
obj.custom_attributes[tab][element]['mandatory'] = target_attr[tab][element]['mandatory']
if partial_overwrite:
for tab in previous_attribute:
if not target_attr.get(tab):
if obj.custom_attributes.get(tab):
flag_modified(obj, "custom_attributes")
obj.custom_attributes.pop(tab)
for element in previous_attribute[tab]:
if target_attr.get(tab):
if not target_attr[tab].get(element):
if obj.custom_attributes[tab].get(element):
flag_modified(obj, "custom_attributes")
obj.custom_attributes[tab].pop(element)
# Commit will only be effective if we flagged a modification, reducing load on the DB
db.session.commit()
def get_default_custom_attributes(object_type):
ca = CustomAttribute.query.filter(CustomAttribute.attribute_for == object_type).first()
return ca.attribute_content
def add_tab_attribute(obj, tab_name):
"""
Add a new custom tab to an object ID
"""
if not obj:
return False
attribute = obj.custom_attributes
if tab_name in attribute:
return True
else:
attribute[tab_name] = {}
flag_modified(obj, "custom_attributes")
db.session.commit()
return True
def add_tab_attribute_field(obj, tab_name, field_name, field_type, field_value, mandatory=None, field_options=None):
if not obj:
return False
attribute = obj.custom_attributes
if attribute is None:
attribute = {}
if tab_name not in attribute:
attribute[tab_name] = {}
attr = {
field_name: {
"mandatory": mandatory if mandatory is not None else False,
"type": field_type,
"value": field_value
}
}
if field_options:
attr[field_name]['options'] = field_options
attribute[tab_name][field_name] = attr[field_name]
obj.custom_attributes = attribute
flag_modified(obj, "custom_attributes")
db.session.commit()
return True
def merge_custom_attributes(data, obj_id, object_type, overwrite=False):
obj = None
if obj_id:
if object_type == 'ioc':
obj = Ioc.query.filter(Ioc.ioc_id == obj_id).first()
elif object_type == 'event':
obj = CasesEvent.query.filter(CasesEvent.event_id == obj_id).first()
elif object_type == 'asset':
obj = CaseAssets.query.filter(CaseAssets.asset_id == obj_id).first()
elif object_type == 'task':
obj = CaseTasks.query.filter(CaseTasks.id == obj_id).first()
elif object_type == 'note':
obj = Notes.query.filter(Notes.note_id == obj_id).first()
elif object_type == 'evidence':
obj = CaseReceivedFile.query.filter(CaseReceivedFile.id == obj_id).first()
elif object_type == 'case':
obj = Cases.query.filter(Cases.case_id == obj_id).first()
elif object_type == 'client':
obj = Client.query.filter(Client.client_id == obj_id).first()
if not obj:
return data
if overwrite:
log.warning(f'Overwriting all {object_type}')
return get_default_custom_attributes(object_type)
for tab in data:
if obj.custom_attributes.get(tab) is None:
log.error(f'Missing tab {tab} in {object_type}')
continue
for field in data[tab]:
if field not in obj.custom_attributes[tab]:
log.error(f'Missing field {field} in {object_type}')
else:
if obj.custom_attributes[tab][field]['type'] == 'html':
continue
if obj.custom_attributes[tab][field]['value'] != data[tab][field]:
flag_modified(obj, "custom_attributes")
obj.custom_attributes[tab][field]['value'] = data[tab][field]
# Commit will only be effective if we flagged a modification, reducing load on the DB
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
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]
return default_attr
def validate_attribute(attribute):
logs = []
try:
data = json.loads(attribute)
except Exception as e:
return None, [str(e)]
for tab in data:
for field in data[tab]:
if not data[tab][field].get('type'):
logs.append(f'{tab}::{field} is missing mandatory "type" tag')
continue
field_type = data[tab][field].get('type')
if field_type in ['input_string', 'input_textfield', 'input_checkbox', 'input_select',
'input_date', 'input_datetime']:
if data[tab][field].get('mandatory') is None:
logs.append(f'{tab} -> {field} of type {field_type} is missing mandatory "mandatory" tag')
elif not isinstance(data[tab][field].get('mandatory'), bool):
logs.append(f'{tab} -> {field} -> "mandatory" expects a value of type bool, '
f'but got {type(data[tab][field].get("mandatory"))}')
if data[tab][field].get('value') is None:
logs.append(f'{tab} -> {field} of type {field_type} is missing mandatory "value" tag')
if field_type == 'input_checkbox' and not isinstance(data[tab][field].get('value'), bool):
logs.append(f'{tab} -> {field} of type {field_type} expects a value of type bool, '
f'but got {type(data[tab][field]["value"])}')
if field_type in ['input_string', 'input_textfield', 'input_date', 'input_datetime']:
if not isinstance(data[tab][field].get('value'), str):
logs.append(f'{tab} -> {field} of type {field_type} expects a value of type str, '
f'but got {type(data[tab][field]["value"])}')
if field_type == 'input_select':
if data[tab][field].get('options') is None:
logs.append(f'{tab} -> {field} of type {field_type} is missing mandatory "options" tag')
continue
if not isinstance(data[tab][field].get('options'), list):
logs.append(f'{tab} -> {field} of type {field_type} expects a value of type list, '
f'but got {type(data[tab][field]["value"])}')
for opt in data[tab][field].get('options'):
if not isinstance(opt, str):
logs.append(f'{tab} -> {field} -> "options" expects a list of str, '
f'but got {type(opt)}')
elif field_type in ['raw', 'html']:
if data[tab][field].get('value') is None:
logs.append(f'{tab} -> {field} of type {field_type} is missing mandatory "value" tag')
else:
logs.append(f'{tab} -> {field}, unknown field type "{field_type}"')
return data, logs

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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 func
from typing import List
from app.models import CaseClassification
def get_case_classifications_list() -> List[dict]:
"""Get a list of case classifications
Returns:
List[dict]: List of case classifications
"""
case_classifications = CaseClassification.query.with_entities(
CaseClassification.id,
CaseClassification.name,
CaseClassification.name_expanded,
CaseClassification.description,
CaseClassification.creation_date
).all()
c_cl = [row._asdict() for row in case_classifications]
return c_cl
def get_case_classification_by_id(cur_id: int) -> CaseClassification:
"""Get a case classification
Args:
cur_id (int): case classification id
Returns:
CaseClassification: Case classification
"""
case_classification = CaseClassification.query.filter_by(id=cur_id).first()
return case_classification
def get_case_classification_by_name(cur_name: str) -> CaseClassification:
"""Get a case classification
Args:
cur_name (str): case classification name
Returns:
CaseClassification: Case classification
"""
case_classification = CaseClassification.query.filter_by(name=cur_name).first()
return case_classification
def search_classification_by_name(name: str, exact_match: bool = False) -> List[dict]:
"""Search for a case classification by name
Args:
name (str): case classification name
exact_match (bool, optional): Exact match. Defaults to False.
Returns:
List[dict]: List of case classifications
"""
if exact_match:
return CaseClassification.query.filter(func.lower(CaseClassification.name) == name.lower()).all()
return CaseClassification.query.filter(CaseClassification.name.ilike(f'%{name}%')).all()

View File

@ -0,0 +1,86 @@
#!/usr/bin/env python3
#
# 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 sqlalchemy import func
from app.models import AnalysisStatus, IocType, AssetsType, EventCategory
def search_analysis_status_by_name(name: str, exact_match: bool = False) -> AnalysisStatus:
"""
Search an analysis status by its name
args:
name: the name of the analysis status
exact_match: if True, the name must be exactly the same as the one in the database
return: the analysis status
"""
if exact_match:
return AnalysisStatus.query.filter(func.lower(AnalysisStatus.name) == name.lower()).all()
return AnalysisStatus.query.filter(AnalysisStatus.name.ilike(f'%{name}%')).all()
def search_ioc_type_by_name(name: str, exact_match: bool = False) -> IocType:
"""
Search an IOC type by its name
args:
name: the name of the IOC type
exact_match: if True, the name must be exactly the same as the one in the database
return: the IOC type
"""
if exact_match:
return IocType.query.filter(func.lower(IocType.type_name) == name.lower()).all()
return IocType.query.filter(IocType.type_name.ilike(f'%{name}%')).all()
def search_asset_type_by_name(name: str, exact_match: bool = False) -> AssetsType:
"""
Search an asset type by its name
args:
name: the name of the asset type
exact_match: if True, the name must be exactly the same as the one in the database
return: the asset type
"""
if exact_match:
return AssetsType.query.filter(func.lower(AssetsType.asset_name) == name.lower()).all()
return AssetsType.query.filter(AssetsType.asset_name.ilike(f'%{name}%')).all()
def search_event_category_by_name(name: str, exact_match: bool = False) -> AssetsType:
"""
Search an event category by its name
args:
name: the name of the event category
exact_match: if True, the name must be exactly the same as the one in the database
return: the event category
"""
if exact_match:
return EventCategory.query.filter(func.lower(EventCategory.name) == name.lower()).all()
return EventCategory.query.filter(EventCategory.name.ilike(f'%{name}%')).all()

View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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 typing import List
from app.models.cases import CaseState
from app.schema.marshables import CaseStateSchema
def get_case_states_list() -> List[dict]:
"""Get a list of case state
Returns:
List[dict]: List of case state
"""
case_state = CaseState.query.all()
return CaseStateSchema(many=True).dump(case_state)
def get_case_state_by_id(cur_id: int) -> CaseState:
"""Get a case state
Args:
cur_id (int): case state id
Returns:
CaseState: Case state
"""
case_state = CaseState.query.filter_by(state_id=cur_id).first()
return case_state
def get_case_state_by_name(cur_name: str) -> CaseState:
"""Get a case state
Args:
cur_name (str): case state name
Returns:
CaseState: Case state
"""
case_state = CaseState.query.filter_by(state_name=cur_name).first()
return case_state
def get_cases_using_state(cur_id: int) -> List[dict]:
"""Get a list of cases using a case state
Args:
cur_id (int): case state id
Returns:
List[dict]: List of cases
"""
case_state = get_case_state_by_id(cur_id)
return case_state.cases

View File

@ -0,0 +1,302 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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 datetime import datetime
from typing import List, Optional, Union
import marshmallow
from app import db
from app.datamgmt.case.case_notes_db import add_note_group, add_note
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
from app.models import CaseTemplate, Cases, Tags, NotesGroup
from app.models.authorization import User
from app.schema.marshables import CaseSchema, CaseTaskSchema, CaseGroupNoteSchema, CaseAddNoteSchema
def get_case_templates_list() -> List[dict]:
"""Get a list of case templates
Returns:
List[dict]: List of case templates
"""
case_templates = CaseTemplate.query.with_entities(
CaseTemplate.id,
CaseTemplate.name,
CaseTemplate.display_name,
CaseTemplate.description,
CaseTemplate.title_prefix,
CaseTemplate.author,
CaseTemplate.created_at,
CaseTemplate.classification,
CaseTemplate.updated_at,
User.name.label('added_by')
).join(
CaseTemplate.created_by_user
).all()
c_cl = [row._asdict() for row in case_templates]
return c_cl
def get_case_template_by_id(cur_id: int) -> CaseTemplate:
"""Get a case template
Args:
cur_id (int): case template id
Returns:
CaseTemplate: Case template
"""
case_template = CaseTemplate.query.filter_by(id=cur_id).first()
return case_template
def delete_case_template_by_id(case_template_id: int):
"""Delete a case template
Args:
case_template_id (int): case template id
"""
CaseTemplate.query.filter_by(id=case_template_id).delete()
def validate_case_template(data: dict, update: bool = False) -> Optional[str]:
try:
if not update:
# If it's not an update, we check the required fields
if "name" not in data:
return "Name is required."
if "display_name" not in data or not data["display_name"].strip():
data["display_name"] = data["name"]
# We check that name is not empty
if "name" in data and not data["name"].strip():
return "Name cannot be empty."
# We check that author length is not above 128 chars
if "author" in data and len(data["author"]) > 128:
return "Author cannot be longer than 128 characters."
# We check that author length is not above 128 chars
if "author" in data and len(data["author"]) > 128:
return "Author cannot be longer than 128 characters."
# We check that prefix length is not above 32 chars
if "title_prefix" in data and len(data["title_prefix"]) > 32:
return "Prefix cannot be longer than 32 characters."
# We check that tags, if any, are a list of strings
if "tags" in data:
if not isinstance(data["tags"], list):
return "Tags must be a list."
for tag in data["tags"]:
if not isinstance(tag, str):
return "Each tag must be a string."
# We check that tasks, if any, are a list of dictionaries with mandatory keys
if "tasks" in data:
if not isinstance(data["tasks"], list):
return "Tasks must be a list."
for task in data["tasks"]:
if not isinstance(task, dict):
return "Each task must be a dictionary."
if "title" not in task:
return "Each task must have a 'title' field."
if "tags" in task:
if not isinstance(task["tags"], list):
return "Task tags must be a list."
for tag in task["tags"]:
if not isinstance(tag, str):
return "Each tag must be a string."
# We check that note groups, if any, are a list of dictionaries with mandatory keys
if "note_groups" in data:
if not isinstance(data["note_groups"], list):
return "Note groups must be a list."
for note_group in data["note_groups"]:
if not isinstance(note_group, dict):
return "Each note group must be a dictionary."
if "title" not in note_group:
return "Each note group must have a 'title' field."
if "notes" in note_group:
if not isinstance(note_group["notes"], list):
return "Notes must be a list."
for note in note_group["notes"]:
if not isinstance(note, dict):
return "Each note must be a dictionary."
if "title" not in note:
return "Each note must have a 'title' field."
# If all checks succeeded, we return None to indicate everything is has been validated
return None
except Exception as e:
return str(e)
def case_template_pre_modifier(case_schema: CaseSchema, case_template_id: str):
case_template = get_case_template_by_id(int(case_template_id))
if not case_template:
return None
if case_template.title_prefix:
case_schema.name = case_template.title_prefix + " " + case_schema.name[0]
case_classification = get_case_classification_by_name(case_template.classification)
if case_classification:
case_schema.classification_id = case_classification.id
return case_schema
def case_template_populate_tasks(case: Cases, case_template: CaseTemplate):
logs = []
# Update case tasks
for task_template in case_template.tasks:
try:
# validate before saving
task_schema = CaseTaskSchema()
# 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
}
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)
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)
if not ctask:
logs.append("Unable to create task for internal reasons")
except marshmallow.exceptions.ValidationError as e:
logs.append(e.messages)
return logs
def case_template_populate_notes(case: Cases, note_group_template: dict, ng: NotesGroup):
logs = []
if note_group_template.get("notes"):
for note_template in note_group_template["notes"]:
# validate before saving
note_schema = CaseAddNoteSchema()
mapped_note_template = {
"group_id": ng.group_id,
"note_title": note_template["title"],
"note_content": note_template["content"] if note_template.get("content") else ""
}
mapped_note_template = call_modules_hook('on_preload_note_create', data=mapped_note_template, caseid=case.case_id)
note_schema.verify_group_id(mapped_note_template, caseid=ng.group_case_id)
note = note_schema.load(mapped_note_template)
cnote = add_note(note.get('note_title'),
datetime.utcnow(),
case.user_id,
case.case_id,
note.get('group_id'),
note_content=note.get('note_content'))
cnote = call_modules_hook('on_postload_note_create', data=cnote, caseid=case.case_id)
if not cnote:
logs.append("Unable to add note for internal reasons")
break
return logs
def case_template_populate_note_groups(case: Cases, case_template: CaseTemplate):
logs = []
# Update case tasks
for note_group_template in case_template.note_groups:
try:
# validate before saving
note_group_schema = CaseGroupNoteSchema()
# Remap case task template fields
# Set status to "To Do" which is ID 1
mapped_note_group_template = {
"group_title": note_group_template['title']
}
note_group = note_group_schema.load(mapped_note_group_template)
ng = add_note_group(group_title=note_group.group_title,
caseid=case.case_id,
userid=case.user_id,
creationdate=datetime.utcnow())
if not ng:
logs.append("Unable to add note group for internal reasons")
break
logs = case_template_populate_notes(case, note_group_template, ng)
except marshmallow.exceptions.ValidationError as e:
logs.append(e.messages)
return logs
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")
return None, logs
# Update summary, we want to append in order not to skip the initial case description
case.description += "\n" + case_template.summary
# Update case tags
for tag_str in case_template.tags:
tag = Tags(tag_title=tag_str)
tag = tag.save()
case.tags.append(tag)
# Update case tasks
logs = case_template_populate_tasks(case, case_template)
if logs:
return case, logs
# Update case note groups
logs = case_template_populate_note_groups(case, case_template)
if logs:
return case, logs
db.session.commit()
return case, logs

View File

@ -0,0 +1,375 @@
#!/usr/bin/env python3
#
# 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 pathlib import Path
from datetime import datetime
from sqlalchemy import and_
from sqlalchemy.orm import aliased, contains_eager, subqueryload
from app 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_classifications_db import get_case_classification_by_id
from app.datamgmt.manage.manage_case_state_db import get_case_state_by_name
from app.datamgmt.states import delete_case_states
from app.models import CaseAssets, CaseClassification, alert_assets_association, CaseStatus, TaskAssignee, TaskComments
from app.models import CaseEventCategory
from app.models import CaseEventsAssets
from app.models import CaseEventsIoc
from app.models import CaseReceivedFile
from app.models import CaseTasks
from app.models import Cases
from app.models import CasesEvent
from app.models import Client
from app.models import DataStoreFile
from app.models import DataStorePath
from app.models import IocAssetLink
from app.models import IocLink
from app.models import Notes
from app.models import NotesGroup
from app.models import NotesGroupLink
from app.models.alerts import Alert, AlertCaseAssociation
from app.models.authorization import CaseAccessLevel
from app.models.authorization import GroupCaseAccess
from app.models.authorization import OrganisationCaseAccess
from app.models.authorization import User
from app.models import UserActivity
from app.models.authorization import UserCaseAccess
from app.models.authorization import UserCaseEffectiveAccess
from app.models.cases import CaseProtagonist, CaseTags, CaseState
from app.schema.marshables import CaseDetailsSchema
def list_cases_id():
res = Cases.query.with_entities(
Cases.case_id
).all()
return [r.case_id for r in res]
def list_cases_dict_unrestricted():
owner_alias = aliased(User)
user_alias = aliased(User)
res = Cases.query.with_entities(
Cases.name.label('case_name'),
Cases.description.label('case_description'),
Client.name.label('client_name'),
Cases.open_date.label('case_open_date'),
Cases.close_date.label('case_close_date'),
Cases.soc_id.label('case_soc_id'),
Cases.user_id.label('opened_by_user_id'),
user_alias.user.label('opened_by'),
Cases.owner_id,
owner_alias.name.label('owner'),
Cases.case_id
).join(
Cases.client
).join(
user_alias, and_(Cases.user_id == user_alias.id)
).join(
owner_alias, and_(Cases.owner_id == owner_alias.id)
).order_by(
Cases.open_date
).all()
data = []
for row in res:
row = row._asdict()
row['case_open_date'] = row['case_open_date'].strftime("%m/%d/%Y")
row['case_close_date'] = row['case_close_date'].strftime("%m/%d/%Y") if row["case_close_date"] else ""
data.append(row)
return data
def list_cases_dict(user_id):
owner_alias = aliased(User)
user_alias = aliased(User)
res = UserCaseEffectiveAccess.query.with_entities(
Cases.name.label('case_name'),
Cases.description.label('case_description'),
Client.name.label('client_name'),
Cases.open_date.label('case_open_date'),
Cases.close_date.label('case_close_date'),
Cases.soc_id.label('case_soc_id'),
Cases.user_id.label('opened_by_user_id'),
user_alias.user.label('opened_by'),
Cases.owner_id,
owner_alias.name.label('owner'),
Cases.case_id,
Cases.case_uuid,
Cases.classification_id,
CaseClassification.name.label('classification'),
Cases.state_id,
CaseState.state_name,
UserCaseEffectiveAccess.access_level
).join(
UserCaseEffectiveAccess.case,
Cases.client,
Cases.user
).outerjoin(
Cases.classification,
Cases.state
).join(
user_alias, and_(Cases.user_id == user_alias.id)
).join(
owner_alias, and_(Cases.owner_id == owner_alias.id)
).filter(
UserCaseEffectiveAccess.user_id == user_id
).order_by(
Cases.open_date
).all()
data = []
for row in res:
if row.access_level & CaseAccessLevel.deny_all.value == CaseAccessLevel.deny_all.value:
continue
row = row._asdict()
row['case_open_date'] = row['case_open_date'].strftime("%m/%d/%Y")
row['case_close_date'] = row['case_close_date'].strftime("%m/%d/%Y") if row["case_close_date"] else ""
data.append(row)
return data
def user_list_cases_view(user_id):
res = UserCaseEffectiveAccess.query.with_entities(
UserCaseEffectiveAccess.case_id
).filter(and_(
UserCaseEffectiveAccess.user_id == user_id,
UserCaseEffectiveAccess.access_level != CaseAccessLevel.deny_all.value
)).all()
return [r.case_id for r in res]
def close_case(case_id):
res = Cases.query.filter(
Cases.case_id == case_id
).first()
if res:
res.close_date = datetime.utcnow()
res.state_id = get_case_state_by_name('Closed').state_id
db.session.commit()
return res
return None
def map_alert_resolution_to_case_status(case_status_id):
if case_status_id == CaseStatus.false_positive.value:
ares = search_alert_resolution_by_name('False Positive', exact_match=True)
elif case_status_id == CaseStatus.true_positive_with_impact.value:
ares = search_alert_resolution_by_name('True Positive With Impact', exact_match=True)
elif case_status_id == CaseStatus.true_positive_without_impact.value:
ares = search_alert_resolution_by_name('True Positive Without Impact', exact_match=True)
else:
ares = search_alert_resolution_by_name('Not Applicable', exact_match=True)
if ares:
return ares.resolution_status_id
return None
def reopen_case(case_id):
res = Cases.query.filter(
Cases.case_id == case_id
).first()
if res:
res.close_date = None
res.state_id = get_case_state_by_name('Open').state_id
db.session.commit()
return res
return None
def get_case_protagonists(case_id):
protagonists = CaseProtagonist.query.with_entities(
CaseProtagonist.role,
CaseProtagonist.name,
CaseProtagonist.contact,
User.name.label('user_name'),
User.user.label('user_login')
).filter(
CaseProtagonist.case_id == case_id
).outerjoin(
CaseProtagonist.user
).all()
return protagonists
def get_case_details_rt(case_id):
case = Cases.query.filter(Cases.case_id == case_id).first()
if case:
owner_alias = aliased(User)
user_alias = aliased(User)
review_alias = aliased(User)
res = db.session.query(Cases, Client, user_alias, owner_alias).with_entities(
Cases.name.label('case_name'),
Cases.description.label('case_description'),
Cases.open_date, Cases.close_date,
Cases.soc_id.label('case_soc_id'),
Cases.case_id,
Cases.case_uuid,
Client.name.label('customer_name'),
Cases.client_id.label('customer_id'),
Cases.user_id.label('open_by_user_id'),
user_alias.user.label('open_by_user'),
Cases.owner_id,
owner_alias.name.label('owner'),
Cases.status_id,
Cases.state_id,
CaseState.state_name,
Cases.custom_attributes,
Cases.modification_history,
Cases.initial_date,
Cases.classification_id,
CaseClassification.name.label('classification'),
Cases.reviewer_id,
review_alias.name.label('reviewer'),
).filter(and_(
Cases.case_id == case_id
)).join(
user_alias, and_(Cases.user_id == user_alias.id)
).outerjoin(
owner_alias, and_(Cases.owner_id == owner_alias.id)
).outerjoin(
review_alias, and_(Cases.reviewer_id == review_alias.id)
).join(
Cases.client,
).outerjoin(
Cases.classification,
Cases.state
).first()
if res is None:
return None
res = res._asdict()
res['case_tags'] = ",".join(get_case_tags(case_id))
res['status_name'] = CaseStatus(res['status_id']).name.replace("_", " ").title()
res['protagonists'] = [r._asdict() for r in get_case_protagonists(case_id)]
else:
res = None
return res
def delete_case(case_id):
if not Cases.query.filter(Cases.case_id == case_id).first():
return False
delete_case_states(caseid=case_id)
UserActivity.query.filter(UserActivity.case_id == case_id).delete()
CaseReceivedFile.query.filter(CaseReceivedFile.case_id == case_id).delete()
IocLink.query.filter(IocLink.case_id == case_id).delete()
CaseTags.query.filter(CaseTags.case_id == case_id).delete()
CaseProtagonist.query.filter(CaseProtagonist.case_id == case_id).delete()
AlertCaseAssociation.query.filter(AlertCaseAssociation.case_id == case_id).delete()
dsf_list = DataStoreFile.query.filter(DataStoreFile.file_case_id == case_id).all()
for dsf_list_item in dsf_list:
fln = Path(dsf_list_item.file_local_name)
if fln.is_file():
fln.unlink(missing_ok=True)
db.session.delete(dsf_list_item)
db.session.commit()
DataStorePath.query.filter(DataStorePath.path_case_id == case_id).delete()
da = CaseAssets.query.with_entities(CaseAssets.asset_id).filter(CaseAssets.case_id == case_id).all()
for asset in da:
IocAssetLink.query.filter(asset.asset_id == asset.asset_id).delete()
CaseEventsAssets.query.filter(CaseEventsAssets.case_id == case_id).delete()
CaseEventsIoc.query.filter(CaseEventsIoc.case_id == case_id).delete()
CaseAssetsAlias = aliased(CaseAssets)
# Query for CaseAssets that are not referenced in alerts and match the case_id
assets_to_delete = db.session.query(CaseAssets).filter(
and_(
CaseAssets.case_id == case_id,
~db.session.query(alert_assets_association).filter(
alert_assets_association.c.asset_id == CaseAssetsAlias.asset_id
).exists()
)
)
# Delete the assets
assets_to_delete.delete(synchronize_session='fetch')
# Get all alerts associated with assets in the case
alerts_to_update = db.session.query(CaseAssets).filter(CaseAssets.case_id == case_id)
# Update case_id for the alerts
alerts_to_update.update({CaseAssets.case_id: None}, synchronize_session='fetch')
db.session.commit()
NotesGroupLink.query.filter(NotesGroupLink.case_id == case_id).delete()
NotesGroup.query.filter(NotesGroup.group_case_id == case_id).delete()
Notes.query.filter(Notes.note_case_id == case_id).delete()
tasks = CaseTasks.query.filter(CaseTasks.task_case_id == case_id).all()
for task in tasks:
TaskAssignee.query.filter(TaskAssignee.task_id == task.id).delete()
CaseTasks.query.filter(CaseTasks.id == task.id).delete()
da = CasesEvent.query.with_entities(CasesEvent.event_id).filter(CasesEvent.case_id == case_id).all()
for event in da:
CaseEventCategory.query.filter(CaseEventCategory.event_id == event.event_id).delete()
CasesEvent.query.filter(CasesEvent.case_id == case_id).delete()
UserCaseAccess.query.filter(UserCaseAccess.case_id == case_id).delete()
UserCaseEffectiveAccess.query.filter(UserCaseEffectiveAccess.case_id == case_id).delete()
GroupCaseAccess.query.filter(GroupCaseAccess.case_id == case_id).delete()
OrganisationCaseAccess.query.filter(OrganisationCaseAccess.case_id == case_id).delete()
Cases.query.filter(Cases.case_id == case_id).delete()
db.session.commit()
return True

View File

@ -0,0 +1,63 @@
#!/usr/bin/env python3
#
# 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 sqlalchemy import func
from app import db
from app.models.alerts import Severity
def get_severities_list():
"""
Get a list of severities from the database
returns:
list: A list of severities
"""
return db.session.query(Severity).distinct().all()
def get_severity_by_id(status_id: int) -> Severity:
"""
Get a severity from the database by its ID
args:
status_id (int): The ID of the severity to retrieve
returns:
Severity: The severity object
"""
return db.session.query(Severity).filter(Severity.severity_id == status_id).first()
def search_severity_by_name(name: str, exact_match: bool = True) -> Severity:
"""
Search for a severity by its name
args:
name (str): The name of the severity to search for
exact_match (bool): Whether to search for an exact match or not
returns:
Severity: The severity object
"""
if exact_match:
return db.session.query(Severity).filter(func.lower(Severity.severity_name) == name.lower()).all()
return db.session.query(Severity).filter(Severity.severity_name.ilike(f'%{name}%')).all()

View File

@ -0,0 +1,311 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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_login import current_user
from sqlalchemy import and_
from app import db
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_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 import Cases
from app.models.authorization import Group
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 get_groups_list():
groups = Group.query.all()
return groups
def get_groups_list_hr_perms():
groups = get_groups_list()
get_membership_list = UserGroup.query.with_entities(
UserGroup.group_id,
User.user,
User.id,
User.name
).join(UserGroup.user).all()
membership_list = {}
for member in get_membership_list:
if member.group_id not in membership_list:
membership_list[member.group_id] = [{
'user': member.user,
'name': member.name,
'id': member.id
}]
else:
membership_list[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
def get_group(group_id):
group = Group.query.filter(Group.group_id == group_id).first()
return group
def get_group_by_name(group_name):
groups = Group.query.filter(Group.group_name == group_name)
return groups.first()
def get_group_with_members(group_id):
group = get_group(group_id)
if not group:
return None
get_membership_list = UserGroup.query.with_entities(
UserGroup.group_id,
User.user,
User.id,
User.name
).join(
UserGroup.user
).filter(
UserGroup.group_id == group_id
).all()
membership_list = {}
for member in get_membership_list:
if member.group_id not in membership_list:
membership_list[member.group_id] = [{
'user': member.user,
'name': member.name,
'id': member.id
}]
else:
membership_list[member.group_id].append({
'user': member.user,
'name': member.name,
'id': member.id
})
perms = ac_permission_to_list(group.group_permissions)
setattr(group, 'group_permissions_list', perms)
setattr(group, 'group_members', membership_list.get(group.group_id, []))
return group
def get_group_details(group_id):
group = get_group_with_members(group_id)
if not group:
return group
group_accesses = GroupCaseAccess.query.with_entities(
GroupCaseAccess.access_level,
GroupCaseAccess.case_id,
Cases.name.label('case_name')
).join(
GroupCaseAccess.case
).filter(
GroupCaseAccess.group_id == group_id
).all()
group_cases_access = []
for kgroup in group_accesses:
group_cases_access.append({
"access_level": kgroup.access_level,
"access_level_list": ac_access_level_to_list(kgroup.access_level),
"case_id": kgroup.case_id,
"case_name": kgroup.case_name
})
setattr(group, 'group_cases_access', group_cases_access)
return group
def update_group_members(group, members):
if not group:
return None
cur_groups = UserGroup.query.with_entities(
UserGroup.user_id
).filter(UserGroup.group_id == group.group_id).all()
set_cur_groups = set([grp[0] for grp in cur_groups])
set_members = set(int(mber) for mber in members)
users_to_add = set_members - set_cur_groups
users_to_remove = set_cur_groups - set_members
for uid in users_to_add:
user = User.query.filter(User.id == uid).first()
if user:
ug = UserGroup()
ug.group_id = group.group_id
ug.user_id = user.id
db.session.add(ug)
db.session.commit()
ac_auto_update_user_effective_access(uid)
for uid in users_to_remove:
if current_user.id == uid and ac_ldp_group_removal(uid, group.group_id):
continue
UserGroup.query.filter(
and_(UserGroup.group_id == group.group_id,
UserGroup.user_id == uid)
).delete()
db.session.commit()
ac_auto_update_user_effective_access(uid)
return group
def remove_user_from_group(group, member):
if not group:
return None
UserGroup.query.filter(
and_(UserGroup.group_id == group.group_id,
UserGroup.user_id == member.id)
).delete()
db.session.commit()
ac_auto_update_user_effective_access(member.id)
return group
def delete_group(group):
if not group:
return None
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()
def add_case_access_to_group(group, cases_list, access_level):
if not group:
return None, "Invalid group"
for case_id in cases_list:
case = get_case(case_id)
if not case:
return None, "Invalid case ID"
access_level_mask = ac_access_level_mask_from_val_list([access_level])
ocas = GroupCaseAccess.query.filter(
and_(
GroupCaseAccess.case_id == case_id,
GroupCaseAccess.group_id == group.group_id
)).all()
if ocas:
for oca in ocas:
db.session.delete(oca)
oca = GroupCaseAccess()
oca.group_id = group.group_id
oca.access_level = access_level_mask
oca.case_id = case_id
db.session.add(oca)
db.session.commit()
return group, "Updated"
def add_all_cases_access_to_group(group, access_level):
if not group:
return None, "Invalid group"
for case_id in list_cases_id():
access_level_mask = ac_access_level_mask_from_val_list([access_level])
ocas = GroupCaseAccess.query.filter(
and_(
GroupCaseAccess.case_id == case_id,
GroupCaseAccess.group_id == group.group_id
)).all()
if ocas:
for oca in ocas:
db.session.delete(oca)
oca = GroupCaseAccess()
oca.group_id = group.group_id
oca.access_level = access_level_mask
oca.case_id = case_id
db.session.add(oca)
db.session.commit()
return group, "Updated"
def remove_case_access_from_group(group_id, case_id):
if not group_id or type(group_id) is not int:
return
if not case_id or type(case_id) is not int:
return
GroupCaseAccess.query.filter(
and_(
GroupCaseAccess.case_id == case_id,
GroupCaseAccess.group_id == group_id
)).delete()
db.session.commit()
return
def remove_cases_access_from_group(group_id, cases_list):
if not group_id or type(group_id) is not int:
return False, "Invalid group"
if not cases_list or type(cases_list[0]) is not int:
return False, "Invalid cases list"
GroupCaseAccess.query.filter(
and_(
GroupCaseAccess.case_id.in_(cases_list),
GroupCaseAccess.group_id == group_id
)).delete()
db.session.commit()
return True, "Updated"

View File

@ -0,0 +1,24 @@
from app import db
from app.models import ServerSettings
from app.schema.marshables import ServerSettingsSchema
def get_srv_settings():
return ServerSettings.query.first()
def get_server_settings_as_dict():
srv_settings = ServerSettings.query.first()
if srv_settings:
sc = ServerSettingsSchema()
return sc.dump(srv_settings)
else:
return {}
def get_alembic_revision():
with db.engine.connect() as con:
version_num = con.execute("SELECT version_num FROM alembic_version").first()[0]
return version_num or None

View File

@ -0,0 +1,640 @@
#!/usr/bin/env python3
#
# 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 flask_login import current_user
from sqlalchemy import and_
from app import bc
from app import db
from app.datamgmt.case.case_db import get_case
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_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_get_detailed_effective_permissions_from_groups
from app.iris_engine.access_control.utils import ac_remove_case_access_from_user
from app.iris_engine.access_control.utils import ac_set_case_access_for_user
from app.models import Cases
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.authorization import UserCaseAccess
from app.models.authorization import UserCaseEffectiveAccess
from app.models.authorization import UserGroup
from app.models.authorization import UserOrganisation
def get_user(user_id, id_key: str = 'id'):
user = User.query.filter(getattr(User, id_key) == user_id).first()
return user
def get_active_user_by_login(username):
user = User.query.filter(
User.user == username,
User.active == True
).first()
return user
def list_users_id():
users = User.query.with_entities(User.user_id).all()
return users
def get_user_effective_permissions(user_id):
groups_perms = UserGroup.query.with_entities(
Group.group_permissions,
Group.group_name
).filter(
UserGroup.user_id == user_id
).join(
UserGroup.group
).all()
effective_permissions = ac_get_detailed_effective_permissions_from_groups(groups_perms)
return effective_permissions
def get_user_groups(user_id):
groups = UserGroup.query.with_entities(
Group.group_name,
Group.group_id,
Group.group_uuid
).filter(
UserGroup.user_id == user_id
).join(
UserGroup.group
).all()
output = []
for group in groups:
output.append(group._asdict())
return output
def update_user_groups(user_id, groups):
cur_groups = UserGroup.query.with_entities(
UserGroup.group_id
).filter(UserGroup.user_id == user_id).all()
set_cur_groups = set([grp[0] for grp in cur_groups])
set_new_groups = set(int(grp) for grp in groups)
groups_to_add = set_new_groups - set_cur_groups
groups_to_remove = set_cur_groups - set_new_groups
for group_id in groups_to_add:
user_group = UserGroup()
user_group.user_id = user_id
user_group.group_id = group_id
db.session.add(user_group)
for group_id in groups_to_remove:
if current_user.id == user_id and ac_ldp_group_removal(user_id=user_id, group_id=group_id):
continue
UserGroup.query.filter(
UserGroup.user_id == user_id,
UserGroup.group_id == group_id
).delete()
db.session.commit()
ac_auto_update_user_effective_access(user_id)
def update_user_orgs(user_id, orgs):
cur_orgs = UserOrganisation.query.with_entities(
UserOrganisation.org_id,
UserOrganisation.is_primary_org
).filter(UserOrganisation.user_id == user_id).all()
updated = False
primary_org = 0
for org in cur_orgs:
if org.is_primary_org:
primary_org = org.org_id
if primary_org == 0:
return False, 'User does not have primary organisation. Set one before managing its organisations'
set_cur_orgs = set([org.org_id for org in cur_orgs])
set_new_orgs = set(int(org) for org in orgs)
orgs_to_add = set_new_orgs - set_cur_orgs
orgs_to_remove = set_cur_orgs - set_new_orgs
for org in orgs_to_add:
user_org = UserOrganisation()
user_org.user_id = user_id
user_org.org_id = org
db.session.add(user_org)
updated = True
for org in orgs_to_remove:
if org != primary_org:
UserOrganisation.query.filter(
UserOrganisation.user_id == user_id,
UserOrganisation.org_id == org
).delete()
else:
db.session.rollback()
return False, f'Cannot delete user from primary organisation {org}. Change it before deleting.'
updated = True
db.session.commit()
ac_auto_update_user_effective_access(user_id)
return True, f'Organisations membership updated' if updated else "Nothing changed"
def change_user_primary_org(user_id, old_org_id, new_org_id):
uo_old = UserOrganisation.query.filter(
UserOrganisation.user_id == user_id,
UserOrganisation.org_id == old_org_id
).first()
uo_new = UserOrganisation.query.filter(
UserOrganisation.user_id == user_id,
UserOrganisation.org_id == new_org_id
).first()
if uo_old:
uo_old.is_primary_org = False
if not uo_new:
uo = UserOrganisation()
uo.user_id = user_id
uo.org_id = new_org_id
uo.is_primary_org = True
db.session.add(uo)
else:
uo_new.is_primary_org = True
db.session.commit()
return
def add_user_to_organisation(user_id, org_id, make_primary=False):
org_id = Organisation.query.first().org_id
uo_exists = UserOrganisation.query.filter(
UserOrganisation.user_id == user_id,
UserOrganisation.org_id == org_id
).first()
if uo_exists:
uo_exists.is_primary_org = make_primary
db.session.commit()
return True
# Check if user has a primary org already
prim_org = get_user_primary_org(user_id=user_id)
if make_primary:
prim_org.is_primary_org = False
db.session.commit()
uo = UserOrganisation()
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()
return True
def get_user_primary_org(user_id):
uo = UserOrganisation.query.filter(
and_(UserOrganisation.user_id == user_id,
UserOrganisation.is_primary_org == True)
).all()
if not uo:
return None
uoe = None
index = 0
if len(uo) > 1:
# Fix potential duplication
for u in uo:
if index == 0:
uoe = u
continue
u.is_primary_org = False
db.session.commit()
else:
uoe = uo[0]
return uoe
def add_user_to_group(user_id, group_id):
exists = UserGroup.query.filter(
UserGroup.user_id == user_id,
UserGroup.group_id == group_id
).scalar()
if exists:
return True
ug = UserGroup()
ug.user_id = user_id
ug.group_id = group_id
db.session.add(ug)
db.session.commit()
return True
def get_user_organisations(user_id):
user_org = UserOrganisation.query.with_entities(
Organisation.org_name,
Organisation.org_id,
Organisation.org_uuid,
UserOrganisation.is_primary_org
).filter(
UserOrganisation.user_id == user_id
).join(
UserOrganisation.org
).all()
output = []
for org in user_org:
output.append(org._asdict())
return output
def get_user_cases_access(user_id):
user_accesses = UserCaseAccess.query.with_entities(
UserCaseAccess.access_level,
UserCaseAccess.case_id,
Cases.name.label('case_name')
).join(
UserCaseAccess.case
).filter(
UserCaseAccess.user_id == user_id
).all()
user_cases_access = []
for kuser in user_accesses:
user_cases_access.append({
"access_level": kuser.access_level,
"access_level_list": ac_access_level_to_list(kuser.access_level),
"case_id": kuser.case_id,
"case_name": kuser.case_name
})
return user_cases_access
def get_user_cases_fast(user_id):
user_cases = UserCaseEffectiveAccess.query.with_entities(
UserCaseEffectiveAccess.case_id
).where(
UserCaseEffectiveAccess.user_id == user_id,
UserCaseEffectiveAccess.access_level != CaseAccessLevel.deny_all.value
).all()
return [c.case_id for c in user_cases]
def remove_cases_access_from_user(user_id, cases_list):
if not user_id or type(user_id) is not int:
return False, 'Invalid user id'
if not cases_list or type(cases_list[0]) is not int:
return False, "Invalid cases list"
UserCaseAccess.query.filter(
and_(
UserCaseAccess.case_id.in_(cases_list),
UserCaseAccess.user_id == user_id
)).delete()
db.session.commit()
ac_auto_update_user_effective_access(user_id)
return True, 'Cases access removed'
def remove_case_access_from_user(user_id, case_id):
if not user_id or type(user_id) is not int:
return False, 'Invalid user id'
if not case_id or type(case_id) is not int:
return False, "Invalid case id"
UserCaseAccess.query.filter(
and_(
UserCaseAccess.case_id == case_id,
UserCaseAccess.user_id == user_id
)).delete()
db.session.commit()
ac_remove_case_access_from_user(user_id, case_id)
return True, 'Case access removed'
def set_user_case_access(user_id, case_id, access_level):
if user_id is None or type(user_id) is not int:
return False, 'Invalid user id'
if case_id is None or type(case_id) is not int:
return False, "Invalid case id"
if access_level is None or type(access_level) is not int:
return False, "Invalid access level"
if CaseAccessLevel.has_value(access_level) is False:
return False, "Invalid access level"
uca = UserCaseAccess.query.filter(
UserCaseAccess.user_id == user_id,
UserCaseAccess.case_id == case_id
).all()
if len(uca) > 1:
for u in uca:
db.session.delete(u)
db.session.commit()
uca = None
if not uca:
uca = UserCaseAccess()
uca.user_id = user_id
uca.case_id = case_id
uca.access_level = access_level
db.session.add(uca)
else:
uca[0].access_level = access_level
db.session.commit()
ac_set_case_access_for_user(user_id, case_id, access_level)
return True, 'Case access set to {} for user {}'.format(access_level, user_id)
def get_user_details(user_id, include_api_key=False):
user = User.query.filter(User.id == user_id).first()
if not user:
return None
row = {}
row['user_id'] = user.id
row['user_uuid'] = user.uuid
row['user_name'] = user.name
row['user_login'] = user.user
row['user_email'] = user.email
row['user_active'] = user.active
row['user_is_service_account'] = user.is_service_account
if include_api_key:
row['user_api_key'] = user.api_key
row['user_groups'] = get_user_groups(user_id)
row['user_organisations'] = get_user_organisations(user_id)
row['user_permissions'] = get_user_effective_permissions(user_id)
row['user_cases_access'] = get_user_cases_access(user_id)
upg = get_user_primary_org(user_id)
row['user_primary_organisation_id'] = upg.org_id if upg else 0
return row
def add_case_access_to_user(user, cases_list, access_level):
if not user:
return None, "Invalid user"
for case_id in cases_list:
case = get_case(case_id)
if not case:
return None, "Invalid case ID"
access_level_mask = ac_access_level_mask_from_val_list([access_level])
ocas = UserCaseAccess.query.filter(
and_(
UserCaseAccess.case_id == case_id,
UserCaseAccess.user_id == user.id
)).all()
if ocas:
for oca in ocas:
db.session.delete(oca)
oca = UserCaseAccess()
oca.user_id = user.id
oca.access_level = access_level_mask
oca.case_id = case_id
db.session.add(oca)
db.session.commit()
ac_auto_update_user_effective_access(user.id)
return user, "Updated"
def get_user_by_username(username):
user = User.query.filter(User.user == username).first()
return user
def get_users_list():
users = User.query.all()
output = []
for user in users:
row = {}
row['user_id'] = user.id
row['user_uuid'] = user.uuid
row['user_name'] = user.name
row['user_login'] = user.user
row['user_email'] = user.email
row['user_active'] = user.active
row['user_is_service_account'] = user.is_service_account
output.append(row)
return output
def get_users_list_restricted():
users = User.query.all()
output = []
for user in users:
row = {}
row['user_id'] = user.id
row['user_uuid'] = user.uuid
row['user_name'] = user.name
row['user_login'] = user.user
row['user_active'] = user.active
output.append(row)
return output
def get_users_view_from_user_id(user_id):
organisations = get_user_organisations(user_id)
orgs_id = [uo.get('org_id') for uo in organisations]
users = UserOrganisation.query.with_entities(
User
).filter(and_(
UserOrganisation.org_id.in_(orgs_id),
UserOrganisation.user_id != user_id
)).join(
UserOrganisation.user
).all()
return users
def get_users_id_view_from_user_id(user_id):
organisations = get_user_organisations(user_id)
orgs_id = [uo.get('org_id') for uo in organisations]
users = UserOrganisation.query.with_entities(
User.id
).filter(and_(
UserOrganisation.org_id.in_(orgs_id),
UserOrganisation.user_id != user_id
)).join(
UserOrganisation.user
).all()
users = [u[0] for u in users]
return users
def get_users_list_user_view(user_id):
users = get_users_view_from_user_id(user_id)
output = []
for user in users:
row = {}
row['user_id'] = user.id
row['user_uuid'] = user.uuid
row['user_name'] = user.name
row['user_login'] = user.user
row['user_email'] = user.email
row['user_active'] = user.active
output.append(row)
return output
def get_users_list_restricted_user_view(user_id):
users = get_users_view_from_user_id(user_id)
output = []
for user in users:
row = {}
row['user_id'] = user.id
row['user_uuid'] = user.uuid
row['user_name'] = user.name
row['user_login'] = user.user
row['user_active'] = user.active
output.append(row)
return output
def get_users_list_restricted_from_case(case_id):
users = UserCaseEffectiveAccess.query.with_entities(
User.id.label('user_id'),
User.uuid.label('user_uuid'),
User.name.label('user_name'),
User.user.label('user_login'),
User.active.label('user_active'),
User.email.label('user_email'),
UserCaseEffectiveAccess.access_level.label('user_access_level')
).filter(
UserCaseEffectiveAccess.case_id == case_id
).join(
UserCaseEffectiveAccess.user
).all()
return [u._asdict() for u in users]
def create_user(user_name: str, user_login: str, user_password: str, user_email: str, user_active: bool,
user_external_id: str = None, user_is_service_account: bool = False):
if user_is_service_account is True and (user_password is None or user_password == ''):
pw_hash = None
else:
pw_hash = bc.generate_password_hash(user_password.encode('utf8')).decode('utf8')
user = User(user=user_login, name=user_name, email=user_email, password=pw_hash, active=user_active,
external_id=user_external_id, is_service_account=user_is_service_account)
user.save()
add_user_to_organisation(user.id, org_id=1)
ac_auto_update_user_effective_access(user_id=user.id)
return user
def update_user(user: User, name: str = None, email: str = None, password: str = None):
if password is not None and password != '':
pw_hash = bc.generate_password_hash(password.encode('utf8')).decode('utf8')
user.password = pw_hash
for key, value in [('name', name,), ('email', email,)]:
if value is not None:
setattr(user, key, value)
db.session.commit()
return user
def delete_user(user_id):
UserCaseAccess.query.filter(UserCaseAccess.user_id == user_id).delete()
UserOrganisation.query.filter(UserOrganisation.user_id == user_id).delete()
UserGroup.query.filter(UserGroup.user_id == user_id).delete()
UserCaseEffectiveAccess.query.filter(UserCaseEffectiveAccess.user_id == user_id).delete()
User.query.filter(User.id == user_id).delete()
db.session.commit()
def user_exists(user_name, user_email):
user = User.query.filter_by(user=user_name).first()
user_by_email = User.query.filter_by(email=user_email).first()
return user or user_by_email

View File

@ -0,0 +1,70 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# DFIR-IRIS Team
# 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 datetime
from sqlalchemy import and_
from app.datamgmt.case.case_tasks_db import get_tasks_cases_mapping
from app.datamgmt.manage.manage_cases_db import user_list_cases_view
from app.models import Cases, CaseClassification
from app.models import Client
from app.models.authorization import User
from app.models.cases import CaseState
from app.schema.marshables import CaseDetailsSchema
def get_overview_db(user_id, show_full):
"""
Get overview data from the database
"""
condition = and_(Cases.case_id.in_(user_list_cases_view(user_id)))
if not show_full:
condition = and_(condition, Cases.close_date == None)
open_cases = Cases.query.filter(
condition
).join(
Cases.owner,
Cases.client
).all()
cases_list = []
tasks_map = get_tasks_cases_mapping(open_cases_only=not show_full)
tmap = {}
for task in tasks_map:
if tmap.get(task.task_case_id) is None:
tmap[task.task_case_id] = {
'open_tasks': 0,
'closed_tasks': 0
}
if task.task_status_id in [1, 2, 3]:
tmap[task.task_case_id]['open_tasks'] += 1
elif task.task_status_id == 4:
tmap[task.task_case_id]['closed_tasks'] += 1
# open_cases_list = []
for case in open_cases:
c_case = CaseDetailsSchema().dump(case)
c_case['case_open_since_days'] = (datetime.date.today() - case.open_date).days
c_case['tasks_status'] = tmap.get(case.case_id)
cases_list.append(c_case)
return cases_list

View File

@ -0,0 +1,504 @@
#!/usr/bin/env python3
#
# 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.
import datetime
import re
from sqlalchemy import desc
from app.datamgmt.case.case_notes_db import get_notes_from_group
from app.datamgmt.case.case_tasks_db import get_tasks_with_assignees
from app.models import AnalysisStatus, CompromiseStatus, TaskAssignee, NotesGroupLink
from app.models import AssetsType
from app.models import CaseAssets
from app.models import CaseEventsAssets
from app.models import CaseEventsIoc
from app.models import CaseReceivedFile
from app.models import CaseStatus
from app.models import CaseTasks
from app.models import Cases
from app.models import CasesEvent
from app.models import Client
from app.models import Comments
from app.models import EventCategory
from app.models import Ioc
from app.models import IocAssetLink
from app.models import IocLink
from app.models import IocType
from app.models import Notes
from app.models import NotesGroup
from app.models import TaskStatus
from app.models import Tlp
from app.models.authorization import User
def export_case_json(case_id):
"""
Fully export a case a JSON
"""
export = {}
case = export_caseinfo_json(case_id)
if not case:
export['errors'] = ["Invalid case number"]
return export
case['description'] = process_md_images_links_for_report(case['description'])
export['case'] = case
export['evidences'] = export_case_evidences_json(case_id)
export['timeline'] = export_case_tm_json(case_id)
export['iocs'] = export_case_iocs_json(case_id)
export['assets'] = export_case_assets_json(case_id)
export['tasks'] = export_case_tasks_json(case_id)
export['comments'] = export_case_comments_json(case_id)
export['notes'] = export_case_notes_json(case_id)
export['export_date'] = datetime.datetime.utcnow()
return export
def export_case_json_for_report(case_id):
"""
Fully export of a case for report generation
"""
export = {}
case = export_caseinfo_json(case_id)
if not case:
export['errors'] = ["Invalid case number"]
return export
case['description'] = process_md_images_links_for_report(case['description'])
export['case'] = case
export['evidences'] = export_case_evidences_json(case_id)
export['timeline'] = export_case_tm_json(case_id)
export['iocs'] = export_case_iocs_json(case_id)
export['assets'] = export_case_assets_json(case_id)
export['tasks'] = export_case_tasks_json(case_id)
export['notes'] = export_case_notes_json(case_id)
export['comments'] = export_case_comments_json(case_id)
export['export_date'] = datetime.datetime.utcnow()
return export
def export_case_json_extended(case_id):
"""
Export a case a JSON
"""
export = {}
case = export_caseinfo_json_extended(case_id)
if not case:
export['errors'] = ["Invalid case number"]
return export
export['case'] = case
export['evidences'] = export_case_evidences_json_extended(case_id)
export['timeline'] = export_case_tm_json_extended(case_id)
export['iocs'] = export_case_iocs_json_extended(case_id)
export['assets'] = export_case_assets_json_extended(case_id)
export['tasks'] = export_case_tasks_json_extended(case_id)
export['notes'] = export_case_notes_json_extended(case_id)
export['export_date'] = datetime.datetime.utcnow()
return export
def process_md_images_links_for_report(markdown_text):
"""Process images links in markdown for better processing on the generator side
Creates proper links with FQDN and removal of scale
"""
markdown = re.sub(r'(/datastore\/file\/view\/\d+\?cid=\d+)( =[\dA-z%]*)\)',
r"http://127.0.0.1:8000:/\1)", markdown_text)
return markdown
def export_caseinfo_json_extended(case_id):
case = Cases.query.filter(
Cases.case_id == case_id
).first()
return case
def export_case_evidences_json_extended(case_id):
evidences = CaseReceivedFile.query.filter(
CaseReceivedFile.case_id == case_id
).join(CaseReceivedFile.case,
CaseReceivedFile.user).all()
return evidences
def export_case_tm_json_extended(case_id):
events = CasesEvent.query.filter(
CasesEvent.case_id == case_id
).all()
return events
def export_case_iocs_json_extended(case_id):
iocs = Ioc.query.filter(
IocLink.case_id == case_id
).all()
return iocs
def export_case_assets_json_extended(case_id):
assets = CaseAssets.query.filter(
CaseAssets.case_id == case_id
).all()
return assets
def export_case_tasks_json_extended(case_id):
tasks = CaseTasks.query.filter(
CaseTasks.task_case_id == case_id
).all()
return tasks
def export_case_notes_json_extended(case_id):
notes_groups = NotesGroup.query.filter(
NotesGroup.group_case_id == case_id
).all()
for notes_group in notes_groups:
notes_group = notes_group.__dict__
notes_group['notes'] = get_notes_from_group(notes_group['group_id'], case_id)
return notes_groups
def export_caseinfo_json(case_id):
case = Cases.query.filter(
Cases.case_id == case_id
).with_entities(
Cases.name,
Cases.open_date,
Cases.description,
Cases.soc_id,
User.name.label('opened_by'),
Client.name.label('for_customer'),
Cases.close_date,
Cases.custom_attributes,
Cases.case_id,
Cases.case_uuid,
Cases.status_id
).join(
Cases.user, Cases.client
).first()
if not case:
return None
case = case._asdict()
case['status_name'] = CaseStatus(case['status_id']).name
return case
def export_case_evidences_json(case_id):
evidences = CaseReceivedFile.query.filter(
CaseReceivedFile.case_id == case_id
).with_entities(
CaseReceivedFile.filename,
CaseReceivedFile.date_added,
CaseReceivedFile.file_hash,
User.name.label('added_by'),
CaseReceivedFile.custom_attributes,
CaseReceivedFile.file_uuid,
CaseReceivedFile.id,
CaseReceivedFile.file_size,
).order_by(
CaseReceivedFile.date_added
).join(
CaseReceivedFile.user
).all()
if evidences:
return [row._asdict() for row in evidences]
else:
return []
def export_case_notes_json(case_id):
res = Notes.query.join(
NotesGroupLink, NotesGroupLink.note_id == Notes.note_id
).join(
NotesGroup, NotesGroup.group_id == NotesGroupLink.group_id
).with_entities(
Notes.note_title,
Notes.note_content,
Notes.note_creationdate,
Notes.note_lastupdate,
Notes.custom_attributes,
Notes.note_id,
Notes.note_uuid,
NotesGroup.group_title,
NotesGroup.group_id,
NotesGroup.group_user
).filter(
Notes.note_case_id == case_id
).all()
return_notes = []
if res:
for note in res:
note = note._asdict()
note["note_content"] = process_md_images_links_for_report(note["note_content"])
return_notes.append(note)
return return_notes
def export_case_tm_json(case_id):
timeline = CasesEvent.query.with_entities(
CasesEvent.event_id,
CasesEvent.event_title,
CasesEvent.event_in_summary,
CasesEvent.event_date,
CasesEvent.event_tz,
CasesEvent.event_date_wtz,
CasesEvent.event_content,
CasesEvent.event_tags,
CasesEvent.event_source,
CasesEvent.event_raw,
CasesEvent.custom_attributes,
EventCategory.name.label('category'),
User.name.label('last_edited_by'),
CasesEvent.event_uuid,
CasesEvent.event_in_graph,
CasesEvent.event_in_summary,
CasesEvent.event_color,
CasesEvent.event_is_flagged
).filter(
CasesEvent.case_id == case_id
).order_by(
CasesEvent.event_date
).join(
CasesEvent.user
).outerjoin(
CasesEvent.category
).all()
tim = []
for row in timeline:
ras = row._asdict()
ras['assets'] = None
as_list = CaseEventsAssets.query.with_entities(
CaseAssets.asset_id,
CaseAssets.asset_name,
AssetsType.asset_name.label('type')
).filter(
CaseEventsAssets.event_id == row.event_id
).join(CaseEventsAssets.asset, CaseAssets.asset_type).all()
alki = []
for asset in as_list:
alki.append("{} ({})".format(asset.asset_name, asset.type))
ras['assets'] = alki
iocs_list = CaseEventsIoc.query.with_entities(
CaseEventsIoc.ioc_id,
Ioc.ioc_value,
Ioc.ioc_description,
Tlp.tlp_name,
IocType.type_name.label('type')
).filter(
CaseEventsIoc.event_id == row.event_id
).join(
CaseEventsIoc.ioc, Ioc.ioc_type, Ioc.tlp
).all()
ras['iocs'] = [ioc._asdict() for ioc in iocs_list]
tim.append(ras)
return tim
def export_case_iocs_json(case_id):
res = IocLink.query.with_entities(
Ioc.ioc_value,
IocType.type_name,
Ioc.ioc_tags,
Ioc.ioc_description,
Ioc.custom_attributes,
Ioc.ioc_id,
Ioc.ioc_uuid,
Tlp.tlp_name,
User.name.label('added_by'),
).filter(
IocLink.case_id == case_id
).join(
IocLink.ioc,
Ioc.ioc_type,
Ioc.tlp,
Ioc.user
).order_by(
IocType.type_name
).all()
if res:
return [row._asdict() for row in res]
return []
def export_case_tasks_json(case_id):
res = CaseTasks.query.with_entities(
CaseTasks.task_title,
TaskStatus.status_name.label('task_status'),
CaseTasks.task_tags,
CaseTasks.task_open_date,
CaseTasks.task_close_date,
CaseTasks.task_last_update,
CaseTasks.task_description,
CaseTasks.custom_attributes,
CaseTasks.task_uuid,
CaseTasks.id
).filter(
CaseTasks.task_case_id == case_id
).join(
CaseTasks.status
).all()
tasks = [c._asdict() for c in res]
task_with_assignees = []
for task in tasks:
task_id = task['id']
get_assignee_list = TaskAssignee.query.with_entities(
TaskAssignee.task_id,
User.user,
User.id,
User.name
).join(
TaskAssignee.user
).filter(
TaskAssignee.task_id == task_id
).all()
assignee_list = {}
for member in get_assignee_list:
if member.task_id not in assignee_list:
assignee_list[member.task_id] = [{
'user': member.user,
'name': member.name,
'id': member.id
}]
else:
assignee_list[member.task_id].append({
'user': member.user,
'name': member.name,
'id': member.id
})
task['task_assignees'] = assignee_list.get(task['id'], [])
task_with_assignees.append(task)
return task_with_assignees
def export_case_assets_json(case_id):
ret = []
res = CaseAssets.query.with_entities(
CaseAssets.asset_id,
CaseAssets.asset_uuid,
CaseAssets.asset_name,
CaseAssets.asset_description,
CaseAssets.asset_compromise_status_id,
AssetsType.asset_name.label("type"),
AnalysisStatus.name.label('analysis_status'),
CaseAssets.date_added,
CaseAssets.asset_domain,
CaseAssets.asset_ip,
CaseAssets.asset_info,
CaseAssets.asset_tags,
CaseAssets.custom_attributes
).filter(
CaseAssets.case_id == case_id
).join(
CaseAssets.asset_type, CaseAssets.analysis_status
).order_by(desc(CaseAssets.asset_compromise_status_id)).all()
for row in res:
row = row._asdict()
row['light_asset_description'] = row['asset_description']
ial = IocAssetLink.query.with_entities(
Ioc.ioc_value,
IocType.type_name,
Ioc.ioc_description
).filter(
IocAssetLink.asset_id == row['asset_id']
).join(
IocAssetLink.ioc,
Ioc.ioc_type
).all()
if ial:
row['asset_ioc'] = [row._asdict() for row in ial]
else:
row['asset_ioc'] = []
if row['asset_compromise_status_id'] is None:
row['asset_compromise_status_id'] = CompromiseStatus.unknown.value
status_text = CompromiseStatus.unknown.name.replace('_', ' ').title()
else:
status_text = CompromiseStatus(row['asset_compromise_status_id']).name.replace('_', ' ').title()
row['asset_compromise_status'] = status_text
ret.append(row)
return ret
def export_case_comments_json(case_id):
comments = Comments.query.with_entities(
Comments.comment_id,
Comments.comment_uuid,
Comments.comment_text,
User.name.label('comment_by'),
Comments.comment_date,
).filter(
Comments.comment_case_id == case_id
).join(
Comments.user
).order_by(
Comments.comment_date
).all()
return [row._asdict() for row in comments]

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
#
# 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 datetime import datetime
from flask_login import current_user
from sqlalchemy import and_
from app import db
from app.models import ObjectState
def update_object_state(object_name, caseid, userid=None) -> ObjectState:
"""
Expects a db commit soon after
Args:
object_name: name of the object to update
caseid: case id
userid: user id
Returns:
ObjectState object
"""
if not userid:
userid = current_user.id
os = ObjectState.query.filter(and_(
ObjectState.object_name == object_name,
ObjectState.object_case_id == caseid
)).first()
if os:
os.object_last_update = datetime.utcnow()
os.object_state = os.object_state + 1
os.object_updated_by_id = userid
os.object_case_id = caseid
else:
os = ObjectState()
os.object_name = object_name
os.object_state = 0
os.object_last_update = datetime.utcnow()
os.object_updated_by_id = userid
os.object_case_id = caseid
db.session.add(os)
return os
def get_object_state(object_name, caseid):
os = ObjectState.query.with_entities(
ObjectState.object_state,
ObjectState.object_last_update
).filter(and_(
ObjectState.object_name == object_name,
ObjectState.object_case_id == caseid
)).first()
if os:
return os._asdict()
else:
return None
def delete_case_states(caseid):
ObjectState.query.filter(
ObjectState.object_case_id == caseid
).delete()
def update_timeline_state(caseid, userid=None):
return update_object_state('timeline', caseid=caseid, userid=userid)
def get_timeline_state(caseid):
return get_object_state('timeline', caseid=caseid)
def update_tasks_state(caseid, userid=None):
return update_object_state('tasks', caseid=caseid, userid=userid)
def get_tasks_state(caseid):
return get_object_state('tasks', caseid=caseid)
def update_evidences_state(caseid, userid=None):
return update_object_state('evidences', caseid=caseid, userid=userid)
def get_evidences_state(caseid):
return get_object_state('evidences', caseid=caseid)
def update_ioc_state(caseid, userid=None):
return update_object_state('ioc', caseid=caseid, userid=userid)
def get_ioc_state(caseid):
return get_object_state('ioc', caseid=caseid)
def update_assets_state(caseid, userid=None):
return update_object_state('assets', caseid=caseid, userid=userid)
def get_assets_state(caseid):
return get_object_state('assets', caseid=caseid)
def update_notes_state(caseid, userid=None):
return update_object_state('notes', caseid=caseid, userid=userid)
def get_notes_state(caseid):
return get_object_state('notes', caseid=caseid)