| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- # IRIS Source Code
- # Copyright (C) 2024 - DFIR-IRIS
- # contact@dfir-iris.org
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU Lesser General Public
- # License as published by the Free Software Foundation; either
- # version 3 of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public License
- # along with this program; if not, write to the Free Software Foundation,
- # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- import binascii
- import marshmallow
- import traceback
- from flask import Blueprint
- from flask import request
- from flask_login import current_user
- from sqlalchemy import and_
- from sqlalchemy import desc
- from app import app
- from app import db
- from app import socket_io
- from app.blueprints.rest.endpoints import endpoint_deprecated
- from app.business.cases import cases_exists
- from app.datamgmt.case.case_db import get_review_id_from_name
- from app.datamgmt.case.case_db import case_get_desc_crc
- from app.datamgmt.case.case_db import get_case
- from app.datamgmt.manage.manage_groups_db import add_case_access_to_group
- from app.datamgmt.manage.manage_groups_db import get_group_with_members
- from app.datamgmt.manage.manage_users_db import get_user
- from app.datamgmt.manage.manage_users_db import get_users_list_restricted_from_case
- from app.datamgmt.manage.manage_users_db import set_user_case_access
- from app.business.cases import cases_export_to_json
- from app.iris_engine.access_control.utils import ac_fast_check_user_has_case_access
- from app.iris_engine.access_control.utils import ac_set_case_access_for_users
- from app.iris_engine.utils.tracker import track_activity
- from app.models.models import CaseStatus
- from app.models.models import ReviewStatusList
- from app.models.models import UserActivity
- from app.models.authorization import CaseAccessLevel
- from app.models.authorization import User
- from app.schema.marshables import TaskLogSchema
- from app.schema.marshables import CaseSchema
- from app.schema.marshables import CaseDetailsSchema
- from app.blueprints.access_controls import ac_requires_case_identifier
- from app.blueprints.access_controls import ac_api_requires
- from app.util import add_obj_history_entry
- from app.blueprints.responses import response_error
- from app.blueprints.responses import response_success
- case_rest_blueprint = Blueprint('case_rest', __name__)
- log = app.logger
- @case_rest_blueprint.route('/case/exists', methods=['GET'])
- @endpoint_deprecated('GET', '/api/v2/cases/<int:identifier>')
- @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
- @ac_api_requires()
- def case_routes_exists(caseid):
- if cases_exists(caseid):
- return response_success('Case exists')
- return response_error('Case does not exist', 404)
- @case_rest_blueprint.route('/case/summary/update', methods=['POST'])
- @ac_requires_case_identifier(CaseAccessLevel.full_access)
- @ac_api_requires()
- def desc_fetch(caseid):
- js_data = request.get_json()
- case = get_case(caseid)
- if not case:
- return response_error('Invalid case ID')
- case.description = js_data.get('case_description')
- crc = binascii.crc32(case.description.encode('utf-8'))
- db.session.commit()
- track_activity('updated summary', caseid)
- if not request.cookies.get('session'):
- # API call so we propagate the message to everyone
- data = {
- 'case_description': case.description,
- 'last_saved': current_user.user
- }
- socket_io.emit('save', data, to=f'case-{caseid}')
- return response_success('Summary updated', data=crc)
- @case_rest_blueprint.route('/case/summary/fetch', methods=['GET'])
- @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
- @ac_api_requires()
- def summary_fetch(caseid):
- desc_crc32, description = case_get_desc_crc(caseid)
- return response_success('Summary fetch', data={'case_description': description, 'crc32': desc_crc32})
- @case_rest_blueprint.route('/case/activities/list', methods=['GET'])
- @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
- @ac_api_requires()
- def activity_fetch(caseid):
- ua = UserActivity.query.with_entities(
- UserActivity.activity_date,
- User.name,
- UserActivity.activity_desc,
- UserActivity.is_from_api
- ).filter(and_(
- UserActivity.case_id == caseid,
- UserActivity.display_in_ui == True
- )).join(
- UserActivity.user
- ).order_by(
- desc(UserActivity.activity_date)
- ).limit(40).all()
- output = [a._asdict() for a in ua]
- return response_success('', data=output)
- @case_rest_blueprint.route('/case/export', methods=['GET'])
- @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
- @ac_api_requires()
- def export_case(caseid):
- return response_success('', data=cases_export_to_json(caseid))
- @case_rest_blueprint.route('/case/meta', methods=['GET'])
- @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
- @ac_api_requires()
- def meta_case(caseid):
- case_details = get_case(caseid)
- return response_success('', data=CaseDetailsSchema().dump(case_details))
- @case_rest_blueprint.route('/case/tasklog/add', methods=['POST'])
- @ac_requires_case_identifier(CaseAccessLevel.full_access)
- @ac_api_requires()
- def case_add_tasklog(caseid):
- log_schema = TaskLogSchema()
- try:
- log_data = log_schema.load(request.get_json())
- ua = track_activity(log_data.get('log_content'), caseid, user_input=True)
- except marshmallow.exceptions.ValidationError as e:
- return response_error(msg='Data error', data=e.messages)
- return response_success('Log saved', data=ua)
- @case_rest_blueprint.route('/case/users/list', methods=['GET'])
- @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
- @ac_api_requires()
- def case_get_users(caseid):
- users = get_users_list_restricted_from_case(caseid)
- return response_success(data=users)
- @case_rest_blueprint.route('/case/access/set-group', methods=['POST'])
- @ac_requires_case_identifier(CaseAccessLevel.full_access)
- @ac_api_requires()
- def group_cac_set_case(caseid):
- data = request.get_json()
- if not data:
- return response_error('Invalid request')
- if data.get('case_id') != caseid:
- return response_error('Inconsistent case ID')
- case = get_case(caseid)
- if not case:
- return response_error('Invalid case ID')
- group_id = data.get('group_id')
- access_level = data.get('access_level')
- group = get_group_with_members(group_id)
- try:
- success, logs = add_case_access_to_group(group, [data.get('case_id')], access_level)
- if success:
- success, logs = ac_set_case_access_for_users(group.group_members, caseid, access_level)
- except Exception as e:
- log.error('Error while setting case access for group: {}'.format(e))
- log.error(traceback.format_exc())
- return response_error(msg=str(e))
- if success:
- track_activity('case access set to {} for group {}'.format(data.get('access_level'), group_id), caseid)
- add_obj_history_entry(case, 'access changed to {} for group {}'.format(data.get('access_level'), group_id),
- commit=True)
- return response_success(msg=logs)
- return response_error(msg=logs)
- @case_rest_blueprint.route('/case/access/set-user', methods=['POST'])
- @ac_requires_case_identifier(CaseAccessLevel.full_access)
- @ac_api_requires()
- def user_cac_set_case(caseid):
- data = request.get_json()
- if not data:
- return response_error('Invalid request')
- if data.get('user_id') == current_user.id:
- return response_error('I can\'t let you do that, Dave')
- user = get_user(data.get('user_id'))
- if not user:
- return response_error('Invalid user ID')
- if data.get('case_id') != caseid:
- return response_error('Inconsistent case ID')
- case = get_case(caseid)
- if not case:
- return response_error('Invalid case ID')
- try:
- success, logs = set_user_case_access(user.id, data.get('case_id'), data.get('access_level'))
- track_activity('case access set to {} for user {}'.format(data.get('access_level'), user.name), caseid)
- add_obj_history_entry(case, 'access changed to {} for user {}'.format(data.get('access_level'), user.name))
- db.session.commit()
- except Exception as e:
- log.error('Error while setting case access for user: {}'.format(e))
- log.error(traceback.format_exc())
- return response_error(msg=str(e))
- if success:
- return response_success(msg=logs)
- return response_error(msg=logs)
- @case_rest_blueprint.route('/case/update-status', methods=['POST'])
- @ac_requires_case_identifier(CaseAccessLevel.full_access)
- @ac_api_requires()
- def case_update_status(caseid):
- case = get_case(caseid)
- if not case:
- return response_error('Invalid case ID')
- status = request.get_json().get('status_id')
- case_status = {item.value for item in CaseStatus}
- try:
- status = int(status)
- except ValueError:
- return response_error('Invalid status')
- except TypeError:
- return response_error('Invalid status. Expected int')
- if status not in case_status:
- return response_error('Invalid status')
- case.status_id = status
- add_obj_history_entry(case, f'status updated to {CaseStatus(status).name}')
- db.session.commit()
- return response_success('Case status updated', data=case.status_id)
- @case_rest_blueprint.route('/case/review/update', methods=['POST'])
- @ac_requires_case_identifier(CaseAccessLevel.full_access)
- @ac_api_requires()
- def case_review(caseid):
- case = get_case(caseid)
- if not case:
- return response_error('Invalid case ID')
- action = request.get_json().get('action')
- reviewer_id = request.get_json().get('reviewer_id')
- if action == 'start':
- review_name = ReviewStatusList.review_in_progress
- elif action in ('cancel', 'request'):
- review_name = ReviewStatusList.pending_review
- elif action == 'no_review':
- review_name = ReviewStatusList.no_review_required
- elif action == 'to_review':
- review_name = ReviewStatusList.not_reviewed
- elif action == 'done':
- review_name = ReviewStatusList.reviewed
- else:
- return response_error('Invalid action')
- case.review_status_id = get_review_id_from_name(review_name)
- if reviewer_id:
- try:
- reviewer_id = int(reviewer_id)
- except ValueError:
- return response_error('Invalid reviewer ID')
- if not ac_fast_check_user_has_case_access(reviewer_id, caseid, [CaseAccessLevel.full_access]):
- return response_error('Invalid reviewer ID')
- case.reviewer_id = reviewer_id
- db.session.commit()
- add_obj_history_entry(case, f'review status updated to {review_name}')
- track_activity(f'review status updated to {review_name}', caseid)
- db.session.commit()
- return response_success('Case review updated', data=CaseSchema().dump(case))
|