This commit is contained in:
0
iris-web/source/app/datamgmt/__init__.py
Normal file
0
iris-web/source/app/datamgmt/__init__.py
Normal file
0
iris-web/source/app/datamgmt/activities/__init__.py
Normal file
0
iris-web/source/app/datamgmt/activities/__init__.py
Normal file
132
iris-web/source/app/datamgmt/activities/activities_db.py
Normal file
132
iris-web/source/app/datamgmt/activities/activities_db.py
Normal 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
|
0
iris-web/source/app/datamgmt/alerts/__init__.py
Normal file
0
iris-web/source/app/datamgmt/alerts/__init__.py
Normal file
1230
iris-web/source/app/datamgmt/alerts/alerts_db.py
Normal file
1230
iris-web/source/app/datamgmt/alerts/alerts_db.py
Normal file
File diff suppressed because it is too large
Load Diff
0
iris-web/source/app/datamgmt/case/__init__.py
Normal file
0
iris-web/source/app/datamgmt/case/__init__.py
Normal file
399
iris-web/source/app/datamgmt/case/case_assets_db.py
Normal file
399
iris-web/source/app/datamgmt/case/case_assets_db.py
Normal 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
|
13
iris-web/source/app/datamgmt/case/case_comments.py
Normal file
13
iris-web/source/app/datamgmt/case/case_comments.py
Normal 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()
|
211
iris-web/source/app/datamgmt/case/case_db.py
Normal file
211
iris-web/source/app/datamgmt/case/case_db.py
Normal 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
|
402
iris-web/source/app/datamgmt/case/case_events_db.py
Normal file
402
iris-web/source/app/datamgmt/case/case_events_db.py
Normal 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()
|
||||
|
356
iris-web/source/app/datamgmt/case/case_iocs_db.py
Normal file
356
iris-web/source/app/datamgmt/case/case_iocs_db.py
Normal 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()
|
374
iris-web/source/app/datamgmt/case/case_notes_db.py
Normal file
374
iris-web/source/app/datamgmt/case/case_notes_db.py
Normal 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"
|
174
iris-web/source/app/datamgmt/case/case_rfiles_db.py
Normal file
174
iris-web/source/app/datamgmt/case/case_rfiles_db.py
Normal 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"
|
335
iris-web/source/app/datamgmt/case/case_tasks_db.py
Normal file
335
iris-web/source/app/datamgmt/case/case_tasks_db.py
Normal 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()
|
0
iris-web/source/app/datamgmt/client/__init__.py
Normal file
0
iris-web/source/app/datamgmt/client/__init__.py
Normal file
200
iris-web/source/app/datamgmt/client/client_db.py
Normal file
200
iris-web/source/app/datamgmt/client/client_db.py
Normal 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')
|
||||
|
||||
|
0
iris-web/source/app/datamgmt/context/__init__.py
Normal file
0
iris-web/source/app/datamgmt/context/__init__.py
Normal file
91
iris-web/source/app/datamgmt/context/context_db.py
Normal file
91
iris-web/source/app/datamgmt/context/context_db.py
Normal 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
|
0
iris-web/source/app/datamgmt/dashboard/__init__.py
Normal file
0
iris-web/source/app/datamgmt/dashboard/__init__.py
Normal file
185
iris-web/source/app/datamgmt/dashboard/dashboard_db.py
Normal file
185
iris-web/source/app/datamgmt/dashboard/dashboard_db.py
Normal 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()
|
||||
|
||||
|
0
iris-web/source/app/datamgmt/datastore/__init__.py
Normal file
0
iris-web/source/app/datamgmt/datastore/__init__.py
Normal file
566
iris-web/source/app/datamgmt/datastore/datastore_db.py
Normal file
566
iris-web/source/app/datamgmt/datastore/datastore_db.py
Normal 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'
|
||||
|
26
iris-web/source/app/datamgmt/exceptions/ElementExceptions.py
Normal file
26
iris-web/source/app/datamgmt/exceptions/ElementExceptions.py
Normal 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
|
0
iris-web/source/app/datamgmt/exceptions/__init__.py
Normal file
0
iris-web/source/app/datamgmt/exceptions/__init__.py
Normal file
0
iris-web/source/app/datamgmt/filters/__init__.py
Normal file
0
iris-web/source/app/datamgmt/filters/__init__.py
Normal file
50
iris-web/source/app/datamgmt/filters/filters_db.py
Normal file
50
iris-web/source/app/datamgmt/filters/filters_db.py
Normal 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
|
43
iris-web/source/app/datamgmt/iris_engine/evidence_storage.py
Normal file
43
iris-web/source/app/datamgmt/iris_engine/evidence_storage.py
Normal 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)
|
261
iris-web/source/app/datamgmt/iris_engine/modules_db.py
Normal file
261
iris-web/source/app/datamgmt/iris_engine/modules_db.py
Normal 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
|
0
iris-web/source/app/datamgmt/manage/__init__.py
Normal file
0
iris-web/source/app/datamgmt/manage/__init__.py
Normal 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
|
||||
|
||||
|
292
iris-web/source/app/datamgmt/manage/manage_attribute_db.py
Normal file
292
iris-web/source/app/datamgmt/manage/manage_attribute_db.py
Normal 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
|
@ -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()
|
86
iris-web/source/app/datamgmt/manage/manage_case_objs.py
Normal file
86
iris-web/source/app/datamgmt/manage/manage_case_objs.py
Normal 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()
|
72
iris-web/source/app/datamgmt/manage/manage_case_state_db.py
Normal file
72
iris-web/source/app/datamgmt/manage/manage_case_state_db.py
Normal 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
|
302
iris-web/source/app/datamgmt/manage/manage_case_templates_db.py
Normal file
302
iris-web/source/app/datamgmt/manage/manage_case_templates_db.py
Normal 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
|
375
iris-web/source/app/datamgmt/manage/manage_cases_db.py
Normal file
375
iris-web/source/app/datamgmt/manage/manage_cases_db.py
Normal 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
|
63
iris-web/source/app/datamgmt/manage/manage_common.py
Normal file
63
iris-web/source/app/datamgmt/manage/manage_common.py
Normal 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()
|
311
iris-web/source/app/datamgmt/manage/manage_groups_db.py
Normal file
311
iris-web/source/app/datamgmt/manage/manage_groups_db.py
Normal 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"
|
@ -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
|
640
iris-web/source/app/datamgmt/manage/manage_users_db.py
Normal file
640
iris-web/source/app/datamgmt/manage/manage_users_db.py
Normal 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
|
||||
|
0
iris-web/source/app/datamgmt/overview/__init__.py
Normal file
0
iris-web/source/app/datamgmt/overview/__init__.py
Normal file
70
iris-web/source/app/datamgmt/overview/overview_db.py
Normal file
70
iris-web/source/app/datamgmt/overview/overview_db.py
Normal 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
|
0
iris-web/source/app/datamgmt/reporter/__init__.py
Normal file
0
iris-web/source/app/datamgmt/reporter/__init__.py
Normal file
504
iris-web/source/app/datamgmt/reporter/report_db.py
Normal file
504
iris-web/source/app/datamgmt/reporter/report_db.py
Normal 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]
|
133
iris-web/source/app/datamgmt/states.py
Normal file
133
iris-web/source/app/datamgmt/states.py
Normal 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)
|
Reference in New Issue
Block a user