diff --git a/docker/webApp/Dockerfile b/docker/webApp/Dockerfile index 10b4143b3..e0a7663cb 100644 --- a/docker/webApp/Dockerfile +++ b/docker/webApp/Dockerfile @@ -48,7 +48,7 @@ RUN pip3 install -r requirements.txt ############### # BUILD IMAGE # ############### -FROM python:3.9 as iriswebapp +FROM python:3.9 AS iriswebapp ENV PYTHONUNBUFFERED=1 DOCKERIZED=1 COPY --from=compile-image /opt/venv /opt/venv diff --git a/e2e/tests/administrator/manage/customers.spec.js b/e2e/tests/administrator/manage/customers.spec.js index 5ed7ce7b1..1093b71c8 100644 --- a/e2e/tests/administrator/manage/customers.spec.js +++ b/e2e/tests/administrator/manage/customers.spec.js @@ -12,3 +12,10 @@ test('should be able to open "Add customer" modal', async ({ page }) => { await page.getByRole('button', { name: 'Add customer' }).click(); await expect(page.getByRole('heading', { name: 'Add customer' })).toBeVisible() }); + +test('should present IrisInitialClient associated cases', async ({ page }) => { + await page.getByRole('link', { name: 'IrisInitialClient' }).click(); + + await page.getByRole('button', { name: ' Cases' }).click(); + await expect(page.getByRole('gridcell', { name: '#1 - Initial Demo' })).toBeVisible(); +}); \ No newline at end of file diff --git a/source/app/__init__.py b/source/app/__init__.py index ae0a97c39..730320c64 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -65,6 +65,7 @@ class AlertsNamespace(Namespace): app = Flask(__name__, static_folder="../static") + def ac_current_user_has_permission(*permissions): """ Return True if current user has permission diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index 53ebce6ec..3b76038f0 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -18,27 +18,39 @@ import json import logging as log +import traceback import uuid from functools import wraps -from flask import request, session, render_template +import jwt +import requests + +from flask import Request +from flask import url_for +from flask import request +from flask import render_template +from flask import session from flask_login import current_user +from flask_login import login_user from flask_wtf import FlaskForm +from jwt import PyJWKClient +from requests.auth import HTTPBasicAuth from werkzeug.utils import redirect from app import TEMPLATE_PATH + +from app import app +from app import db +from app.blueprints.responses import response_error from app.datamgmt.case.case_db import get_case from app.datamgmt.manage.manage_access_control_db import user_has_client_access +from app.datamgmt.manage.manage_users_db import get_user from app.iris_engine.access_control.utils import ac_fast_check_user_has_case_access from app.iris_engine.access_control.utils import ac_get_effective_permissions_of_user +from app.iris_engine.utils.tracker import track_activity +from app.models import Cases from app.models.authorization import Permissions from app.models.authorization import CaseAccessLevel -from app.util import update_current_case -from app.util import log_exception_and_error -from app.util import response_error -from app.util import is_user_authenticated -from app.util import not_authenticated_redirection_url -from app.util import ac_api_return_access_denied def _user_has_at_least_a_required_permission(permissions: list[Permissions]): @@ -65,6 +77,11 @@ def _set_caseid_from_current_user(): return redir, caseid +def _log_exception_and_error(e): + log.exception(e) + log.error(traceback.print_exc()) + + def _get_caseid_from_request_data(request_data, no_cid_required): caseid = request_data.args.get('cid', default=None, type=int) if caseid: @@ -99,7 +116,7 @@ def _get_caseid_from_request_data(request_data, no_cid_required): redir, caseid = _set_caseid_from_current_user() return redir, caseid, True - log_exception_and_error(e) + _log_exception_and_error(e) return True, 0, False @@ -133,6 +150,18 @@ def _update_denied_case(caseid): } +def _update_current_case(caseid, restricted_access): + if session['current_case']['case_id'] != caseid: + case = get_case(caseid) + if case: + session['current_case'] = { + 'case_name': "{}".format(case.name), + 'case_info': "(#{} - {})".format(caseid, case.client.name), + 'case_id': caseid, + 'access': restricted_access + } + + def _update_session(caseid, eaccess_level): restricted_access = '' if not eaccess_level: @@ -141,7 +170,7 @@ def _update_session(caseid, eaccess_level): if CaseAccessLevel.read_only.value == eaccess_level: restricted_access = '' - update_current_case(caseid, restricted_access) + _update_current_case(caseid, restricted_access) # TODO would be nice to remove parameter no_cid_required @@ -233,6 +262,17 @@ def get_case_access_from_api(request_data, access_level): return redir, caseid, True +def not_authenticated_redirection_url(request_url: str): + redirection_mapper = { + "oidc_proxy": lambda: app.config.get("AUTHENTICATION_PROXY_LOGOUT_URL"), + "local": lambda: url_for('login.login', next=request_url), + "ldap": lambda: url_for('login.login', next=request_url), + "oidc": lambda: url_for('login.login', next=request_url,) + } + + return redirection_mapper.get(app.config.get("AUTHENTICATION_TYPE"))() + + def ac_case_requires(*access_level): def inner_wrap(f): @wraps(f) @@ -328,3 +368,147 @@ def wrap(*args, **kwargs): return wrap return inner_wrap + + +def ac_api_return_access_denied(caseid: int = None): + error_uuid = uuid.uuid4() + log.warning(f"EID {error_uuid} - Access denied with case #{caseid} for user ID {current_user.id} " + f"accessing URI {request.full_path}") + data = { + 'user_id': current_user.id, + 'case_id': caseid, + 'error_uuid': error_uuid + } + return response_error('Permission denied', data=data, status=403) + + +def ac_api_requires_client_access(): + def inner_wrap(f): + @wraps(f) + def wrap(*args, **kwargs): + client_id = kwargs.get('client_id') + if not user_has_client_access(current_user.id, client_id): + return response_error("Permission denied", status=403) + + return f(*args, **kwargs) + return wrap + return inner_wrap + + +def _authenticate_with_email(user_email): + user = get_user(user_email, id_key="email") + if not user: + log.error(f'User with email {user_email} is not registered in the IRIS') + return False + + login_user(user) + track_activity(f"User '{user.id}' successfully logged-in", ctx_less=True) + + caseid = user.ctx_case + session['permissions'] = ac_get_effective_permissions_of_user(user) + + if caseid is None: + case = Cases.query.order_by(Cases.case_id).first() + user.ctx_case = case.case_id + user.ctx_human_case = case.name + db.session.commit() + + session['current_case'] = { + 'case_name': user.ctx_human_case, + 'case_info': "", + 'case_id': user.ctx_case + } + + return True + + +def _oidc_proxy_authentication_process(incoming_request: Request): + # Get the OIDC JWT authentication token from the request header + authentication_token = incoming_request.headers.get('X-Forwarded-Access-Token', '') + + if app.config.get("AUTHENTICATION_TOKEN_VERIFY_MODE") == 'lazy': + user_email = incoming_request.headers.get('X-Email') + + if user_email: + return _authenticate_with_email(user_email.split(',')[0]) + + elif app.config.get("AUTHENTICATION_TOKEN_VERIFY_MODE") == 'introspection': + # Use the authentication server's token introspection endpoint in order to determine if the request is valid / + # authenticated. The TLS_ROOT_CA is used to validate the authentication server's certificate. + # The other solution was to skip the certificate verification, BUT as the authentication server might be + # located on another server, this check is necessary. + + introspection_body = {"token": authentication_token} + introspection = requests.post( + app.config.get("AUTHENTICATION_TOKEN_INTROSPECTION_URL"), + auth=HTTPBasicAuth(app.config.get('AUTHENTICATION_CLIENT_ID'), app.config.get('AUTHENTICATION_CLIENT_SECRET')), + data=introspection_body, + verify=app.config.get("TLS_ROOT_CA") + ) + if introspection.status_code == 200: + response_json = introspection.json() + + if response_json.get("active", False) is True: + user_email = response_json.get("sub") + return _authenticate_with_email(user_email=user_email) + + else: + log.info("USER IS NOT AUTHENTICATED") + return False + + elif app.config.get("AUTHENTICATION_TOKEN_VERIFY_MODE") == 'signature': + # Use the JWKS urls provided by the OIDC discovery to fetch the signing keys + # and check the signature of the token + try: + jwks_client = PyJWKClient(app.config.get("AUTHENTICATION_JWKS_URL")) + signing_key = jwks_client.get_signing_key_from_jwt(authentication_token) + + try: + + data = jwt.decode( + authentication_token, + signing_key.key, + algorithms=["RS256"], + audience=app.config.get("AUTHENTICATION_AUDIENCE"), + options={"verify_exp": app.config.get("AUTHENTICATION_VERIFY_TOKEN_EXP")}, + ) + + except jwt.ExpiredSignatureError: + log.error("Provided token has expired") + return False + + except Exception as e: + log.error(f"Error decoding JWT. {e.__str__()}") + return False + + # Extract the user email + user_email = data.get("sub") + + return _authenticate_with_email(user_email) + + else: + log.error("ERROR DURING TOKEN INTROSPECTION PROCESS") + return False + + +def _local_authentication_process(incoming_request: Request): + return current_user.is_authenticated + + +def is_user_authenticated(incoming_request: Request): + authentication_mapper = { + "oidc_proxy": _oidc_proxy_authentication_process, + "local": _local_authentication_process, + "ldap": _local_authentication_process, + "oidc": _local_authentication_process, + } + + return authentication_mapper.get(app.config.get("AUTHENTICATION_TYPE"))(incoming_request) + + +def is_authentication_oidc(): + return app.config.get('AUTHENTICATION_TYPE') == "oidc" + + +def is_authentication_ldap(): + return app.config.get('AUTHENTICATION_TYPE') == "ldap" diff --git a/source/app/blueprints/graphql/graphql_route.py b/source/app/blueprints/graphql/graphql_route.py index a39cbf744..32fbea474 100644 --- a/source/app/blueprints/graphql/graphql_route.py +++ b/source/app/blueprints/graphql/graphql_route.py @@ -34,8 +34,8 @@ from graphene_sqlalchemy import SQLAlchemyConnectionField from app.datamgmt.manage.manage_cases_db import build_filter_case_query -from app.util import is_user_authenticated -from app.util import response_error +from app.blueprints.access_controls import is_user_authenticated +from app.blueprints.responses import response_error from app.models.authorization import CaseAccessLevel diff --git a/source/app/blueprints/pages/alerts/alerts_routes.py b/source/app/blueprints/pages/alerts/alerts_routes.py index 499cf41b7..00d7e1005 100644 --- a/source/app/blueprints/pages/alerts/alerts_routes.py +++ b/source/app/blueprints/pages/alerts/alerts_routes.py @@ -28,7 +28,7 @@ from app.datamgmt.alerts.alerts_db import get_alert_by_id from app.datamgmt.manage.manage_access_control_db import user_has_client_access from app.models.authorization import Permissions -from app.util import response_error +from app.blueprints.responses import response_error from app.blueprints.access_controls import ac_requires alerts_blueprint = Blueprint( diff --git a/source/app/blueprints/pages/case/case_assets_routes.py b/source/app/blueprints/pages/case/case_assets_routes.py index 9ca3a93ff..5db24bb2c 100644 --- a/source/app/blueprints/pages/case/case_assets_routes.py +++ b/source/app/blueprints/pages/case/case_assets_routes.py @@ -34,7 +34,7 @@ from app.forms import ModalAddCaseAssetForm from app.models.authorization import CaseAccessLevel from app.blueprints.access_controls import ac_case_requires -from app.util import response_error +from app.blueprints.responses import response_error case_assets_blueprint = Blueprint('case_assets', __name__, diff --git a/source/app/blueprints/pages/case/case_ioc_routes.py b/source/app/blueprints/pages/case/case_ioc_routes.py index a021cc23b..dc6fb8e1c 100644 --- a/source/app/blueprints/pages/case/case_ioc_routes.py +++ b/source/app/blueprints/pages/case/case_ioc_routes.py @@ -34,7 +34,7 @@ from app.models.authorization import CaseAccessLevel from app.models.models import Ioc from app.blueprints.access_controls import ac_case_requires -from app.util import response_error +from app.blueprints.responses import response_error case_ioc_blueprint = Blueprint( 'case_ioc', diff --git a/source/app/blueprints/pages/case/case_notes_routes.py b/source/app/blueprints/pages/case/case_notes_routes.py index 90067118e..1c092821f 100644 --- a/source/app/blueprints/pages/case/case_notes_routes.py +++ b/source/app/blueprints/pages/case/case_notes_routes.py @@ -27,8 +27,7 @@ from app.datamgmt.case.case_notes_db import get_note from app.models.authorization import CaseAccessLevel from app.blueprints.access_controls import ac_case_requires -from app.util import response_error - +from app.blueprints.responses import response_error case_notes_blueprint = Blueprint('case_notes', __name__, diff --git a/source/app/blueprints/pages/case/case_rfiles_routes.py b/source/app/blueprints/pages/case/case_rfiles_routes.py index 476c3200b..1b4c90283 100644 --- a/source/app/blueprints/pages/case/case_rfiles_routes.py +++ b/source/app/blueprints/pages/case/case_rfiles_routes.py @@ -28,7 +28,7 @@ from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes from app.models.authorization import CaseAccessLevel from app.blueprints.access_controls import ac_case_requires -from app.util import response_error +from app.blueprints.responses import response_error case_rfiles_blueprint = Blueprint( 'case_rfiles', diff --git a/source/app/blueprints/pages/case/case_tasks_routes.py b/source/app/blueprints/pages/case/case_tasks_routes.py index 131a774b8..cdf4160b5 100644 --- a/source/app/blueprints/pages/case/case_tasks_routes.py +++ b/source/app/blueprints/pages/case/case_tasks_routes.py @@ -34,7 +34,7 @@ from app.models.authorization import User from app.models.models import CaseTasks from app.blueprints.access_controls import ac_case_requires -from app.util import response_error +from app.blueprints.responses import response_error case_tasks_blueprint = Blueprint('case_tasks', __name__, diff --git a/source/app/blueprints/pages/case/case_timeline_routes.py b/source/app/blueprints/pages/case/case_timeline_routes.py index 534a5a0f4..0e96f284c 100644 --- a/source/app/blueprints/pages/case/case_timeline_routes.py +++ b/source/app/blueprints/pages/case/case_timeline_routes.py @@ -37,7 +37,7 @@ from app.models.cases import Cases from app.models.cases import CasesEvent from app.blueprints.access_controls import ac_case_requires -from app.util import response_error +from app.blueprints.responses import response_error _EVENT_TAGS = ['Network', 'Server', 'ActiveDirectory', 'Computer', 'Malware', 'User Interaction'] diff --git a/source/app/blueprints/pages/dashboard/dashboard_routes.py b/source/app/blueprints/pages/dashboard/dashboard_routes.py index 2a30fb866..522841d62 100644 --- a/source/app/blueprints/pages/dashboard/dashboard_routes.py +++ b/source/app/blueprints/pages/dashboard/dashboard_routes.py @@ -34,9 +34,7 @@ from app.iris_engine.utils.tracker import track_activity from app.models.authorization import User from app.models.models import GlobalTasks -from app.blueprints.access_controls import ac_requires -from app.util import not_authenticated_redirection_url -from app.util import is_authentication_oidc +from app.blueprints.access_controls import ac_requires, is_authentication_oidc, not_authenticated_redirection_url from oic.oauth2.exception import GrantError diff --git a/source/app/blueprints/pages/datastore/datastore_routes.py b/source/app/blueprints/pages/datastore/datastore_routes.py index 2724b6170..f74b2b0ed 100644 --- a/source/app/blueprints/pages/datastore/datastore_routes.py +++ b/source/app/blueprints/pages/datastore/datastore_routes.py @@ -26,7 +26,7 @@ from app.forms import ModalDSFileForm from app.models.authorization import CaseAccessLevel from app.blueprints.access_controls import ac_case_requires -from app.util import response_error +from app.blueprints.responses import response_error datastore_blueprint = Blueprint( 'datastore', diff --git a/source/app/blueprints/pages/dim_tasks/dim_tasks.py b/source/app/blueprints/pages/dim_tasks/dim_tasks.py index 5f0538c79..f073f647d 100644 --- a/source/app/blueprints/pages/dim_tasks/dim_tasks.py +++ b/source/app/blueprints/pages/dim_tasks/dim_tasks.py @@ -27,7 +27,7 @@ from app.models.authorization import CaseAccessLevel from app.models.authorization import Permissions from app.blueprints.access_controls import ac_case_requires, ac_requires -from app.util import response_error +from app.blueprints.responses import response_error from iris_interface.IrisInterfaceStatus import IIStatus dim_tasks_blueprint = Blueprint( diff --git a/source/app/blueprints/pages/login/login_routes.py b/source/app/blueprints/pages/login/login_routes.py index f60805e64..237180e27 100644 --- a/source/app/blueprints/pages/login/login_routes.py +++ b/source/app/blueprints/pages/login/login_routes.py @@ -48,8 +48,8 @@ from app.iris_engine.utils.tracker import track_activity from app.models.cases import Cases -from app.util import is_authentication_ldap, response_error -from app.util import is_authentication_oidc +from app.blueprints.responses import response_error +from app.blueprints.access_controls import is_authentication_oidc, is_authentication_ldap from app.datamgmt.manage.manage_users_db import get_active_user_by_login, get_user from app.datamgmt.manage.manage_users_db import create_user diff --git a/source/app/blueprints/pages/manage/manage_assets_type_routes.py b/source/app/blueprints/pages/manage/manage_assets_type_routes.py index 5ba5555f6..ac142f993 100644 --- a/source/app/blueprints/pages/manage/manage_assets_type_routes.py +++ b/source/app/blueprints/pages/manage/manage_assets_type_routes.py @@ -28,7 +28,7 @@ from app.models.authorization import Permissions from app.models.models import AssetsType from app.blueprints.access_controls import ac_requires -from app.util import response_error +from app.blueprints.responses import response_error manage_assets_type_blueprint = Blueprint('manage_assets_type', __name__, diff --git a/source/app/blueprints/pages/manage/manage_attributes_routes.py b/source/app/blueprints/pages/manage/manage_attributes_routes.py index df30cb373..bfec3f154 100644 --- a/source/app/blueprints/pages/manage/manage_attributes_routes.py +++ b/source/app/blueprints/pages/manage/manage_attributes_routes.py @@ -28,7 +28,7 @@ from app.models.authorization import Permissions from app.models.models import CustomAttribute from app.blueprints.access_controls import ac_requires -from app.util import response_error +from app.blueprints.responses import response_error manage_attributes_blueprint = Blueprint('manage_attributes', __name__, template_folder='templates') diff --git a/source/app/blueprints/pages/manage/manage_case_classification_routes.py b/source/app/blueprints/pages/manage/manage_case_classification_routes.py index 88dd74c7b..c81579391 100644 --- a/source/app/blueprints/pages/manage/manage_case_classification_routes.py +++ b/source/app/blueprints/pages/manage/manage_case_classification_routes.py @@ -26,7 +26,7 @@ from app.datamgmt.manage.manage_case_classifications_db import get_case_classification_by_id from app.forms import CaseClassificationForm from app.models.authorization import Permissions -from app.util import response_error +from app.blueprints.responses import response_error from app.blueprints.access_controls import ac_requires manage_case_classification_blueprint = Blueprint('manage_case_classifications', diff --git a/source/app/blueprints/pages/manage/manage_case_state.py b/source/app/blueprints/pages/manage/manage_case_state.py index 4588637c0..aad87a5c0 100644 --- a/source/app/blueprints/pages/manage/manage_case_state.py +++ b/source/app/blueprints/pages/manage/manage_case_state.py @@ -26,7 +26,7 @@ from app.datamgmt.manage.manage_case_state_db import get_case_state_by_id from app.forms import CaseStateForm from app.models.authorization import Permissions -from app.util import response_error +from app.blueprints.responses import response_error from app.blueprints.access_controls import ac_requires manage_case_state_blueprint = Blueprint('manage_case_state', diff --git a/source/app/blueprints/pages/manage/manage_case_templates_routes.py b/source/app/blueprints/pages/manage/manage_case_templates_routes.py index 0ec674b67..94ef310e3 100644 --- a/source/app/blueprints/pages/manage/manage_case_templates_routes.py +++ b/source/app/blueprints/pages/manage/manage_case_templates_routes.py @@ -25,7 +25,7 @@ from app.models import CaseTemplate from app.models.authorization import Permissions from app.blueprints.access_controls import ac_requires -from app.util import response_error +from app.blueprints.responses import response_error manage_case_templates_blueprint = Blueprint('manage_case_templates', __name__, diff --git a/source/app/blueprints/pages/manage/manage_cases_routes.py b/source/app/blueprints/pages/manage/manage_cases_routes.py index 13540a042..9667e3640 100644 --- a/source/app/blueprints/pages/manage/manage_cases_routes.py +++ b/source/app/blueprints/pages/manage/manage_cases_routes.py @@ -39,9 +39,9 @@ from app.models.authorization import CaseAccessLevel from app.models.authorization import Permissions from app.schema.marshables import CaseDetailsSchema -from app.util import ac_api_return_access_denied +from app.blueprints.access_controls import ac_api_return_access_denied from app.blueprints.access_controls import ac_requires -from app.util import response_error +from app.blueprints.responses import response_error manage_cases_blueprint = Blueprint('manage_case', __name__, diff --git a/source/app/blueprints/pages/manage/manage_customers_routes.py b/source/app/blueprints/pages/manage/manage_customers_routes.py index 3800c82a7..695b6c27e 100644 --- a/source/app/blueprints/pages/manage/manage_customers_routes.py +++ b/source/app/blueprints/pages/manage/manage_customers_routes.py @@ -33,8 +33,8 @@ from app.schema.marshables import ContactSchema from app.blueprints.access_controls import ac_requires from app.blueprints.access_controls import ac_requires_client_access -from app.util import page_not_found -from app.util import response_error +from app.blueprints.responses import page_not_found +from app.blueprints.responses import response_error manage_customers_blueprint = Blueprint( 'manage_customers', diff --git a/source/app/blueprints/pages/manage/manage_evidence_types_route.py b/source/app/blueprints/pages/manage/manage_evidence_types_route.py index 84c74754b..a0174b43b 100644 --- a/source/app/blueprints/pages/manage/manage_evidence_types_route.py +++ b/source/app/blueprints/pages/manage/manage_evidence_types_route.py @@ -26,7 +26,7 @@ from app.datamgmt.manage.manage_evidence_types_db import get_evidence_type_by_id from app.forms import EvidenceTypeForm from app.models.authorization import Permissions -from app.util import response_error +from app.blueprints.responses import response_error from app.blueprints.access_controls import ac_requires manage_evidence_types_blueprint = Blueprint('manage_evidence_types', diff --git a/source/app/blueprints/pages/manage/manage_groups_routes.py b/source/app/blueprints/pages/manage/manage_groups_routes.py index 36881d51e..51322618e 100644 --- a/source/app/blueprints/pages/manage/manage_groups_routes.py +++ b/source/app/blueprints/pages/manage/manage_groups_routes.py @@ -29,7 +29,7 @@ from app.iris_engine.access_control.utils import ac_get_all_permissions from app.models.authorization import Permissions from app.blueprints.access_controls import ac_requires -from app.util import response_error +from app.blueprints.responses import response_error manage_groups_blueprint = Blueprint( 'manage_groups', diff --git a/source/app/blueprints/pages/manage/manage_ioc_types_routes.py b/source/app/blueprints/pages/manage/manage_ioc_types_routes.py index 496d5794e..16594705e 100644 --- a/source/app/blueprints/pages/manage/manage_ioc_types_routes.py +++ b/source/app/blueprints/pages/manage/manage_ioc_types_routes.py @@ -25,7 +25,7 @@ from app.models import IocType from app.models.authorization import Permissions from app.blueprints.access_controls import ac_requires -from app.util import response_error +from app.blueprints.responses import response_error manage_ioc_type_blueprint = Blueprint('manage_ioc_types', __name__, diff --git a/source/app/blueprints/pages/manage/manage_modules_routes.py b/source/app/blueprints/pages/manage/manage_modules_routes.py index ed1433e83..c67dbdeb4 100644 --- a/source/app/blueprints/pages/manage/manage_modules_routes.py +++ b/source/app/blueprints/pages/manage/manage_modules_routes.py @@ -29,7 +29,7 @@ from app.forms import UpdateModuleParameterForm from app.models.authorization import Permissions from app.blueprints.access_controls import ac_requires -from app.util import response_error +from app.blueprints.responses import response_error manage_modules_blueprint = Blueprint( 'manage_module', diff --git a/source/app/blueprints/pages/manage/manage_users.py b/source/app/blueprints/pages/manage/manage_users.py index 6a24cece3..419881858 100644 --- a/source/app/blueprints/pages/manage/manage_users.py +++ b/source/app/blueprints/pages/manage/manage_users.py @@ -33,7 +33,7 @@ from app.iris_engine.access_control.utils import ac_current_user_has_permission from app.models.authorization import Permissions from app.blueprints.access_controls import ac_requires -from app.util import response_error +from app.blueprints.responses import response_error manage_users_blueprint = Blueprint('manage_users', __name__, template_folder='templates') diff --git a/source/app/blueprints/responses.py b/source/app/blueprints/responses.py new file mode 100644 index 000000000..ed8676c4e --- /dev/null +++ b/source/app/blueprints/responses.py @@ -0,0 +1,99 @@ +# 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 datetime +import decimal +import json +import pickle +import uuid + +from flask import render_template +from flask import request +from sqlalchemy.orm import DeclarativeMeta + +from app import TEMPLATE_PATH +from app import app + + +# Set basic 404 +@app.errorhandler(404) +def page_not_found(e): + # note that we set the 404 status explicitly + if request.content_type and 'application/json' in request.content_type: + return response_error("Resource not found", status=404) + + return render_template('pages/error-404.html', template_folder=TEMPLATE_PATH), 404 + + +def response(status, data=None): + if data is not None: + data = json.dumps(data, cls=AlchemyEncoder) + return app.response_class(response=data, status=status, mimetype='application/json') + + +def response_error(msg, data=None, status=400): + content = { + 'status': 'error', + 'message': msg, + 'data': data if data is not None else [] + } + return response(status, data=content) + + +def response_success(msg='', data=None): + content = { + "status": "success", + "message": msg, + "data": data if data is not None else [] + } + return response(200, data=content) + + +class AlchemyEncoder(json.JSONEncoder): + + def default(self, obj): + if isinstance(obj.__class__, DeclarativeMeta): + # an SQLAlchemy class + fields = {} + for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' + and x != 'query' and x != 'query_class']: + data = obj.__getattribute__(field) + try: + json.dumps(data) # this will fail on non-encodable values, like other classes + fields[field] = data + except TypeError: + fields[field] = None + # a json-encodable dict + return fields + + if isinstance(obj, decimal.Decimal): + return str(obj) + + if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date): + return obj.isoformat() + + if isinstance(obj, uuid.UUID): + return str(obj) + + else: + if obj.__class__ == bytes: + try: + return pickle.load(obj) + except Exception: + return str(obj) + + return json.JSONEncoder.default(self, obj) diff --git a/source/app/blueprints/rest/activities_routes.py b/source/app/blueprints/rest/activities_routes.py index c8782ed95..1b8fc4c5a 100644 --- a/source/app/blueprints/rest/activities_routes.py +++ b/source/app/blueprints/rest/activities_routes.py @@ -22,7 +22,7 @@ from app.datamgmt.activities.activities_db import get_users_activities from app.models.authorization import Permissions from app.blueprints.access_controls import ac_api_requires -from app.util import response_success +from app.blueprints.responses import response_success activities_rest_blueprint = Blueprint('activities_rest', __name__) diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index efc4b886a..46e254748 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -57,9 +57,9 @@ from app.schema.marshables import CaseAssetsSchema from app.schema.marshables import IocSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error +from app.blueprints.responses import response_error from app.util import add_obj_history_entry -from app.util import response_success +from app.blueprints.responses import response_success alerts_rest_blueprint = Blueprint('alerts_rest', __name__) diff --git a/source/app/blueprints/rest/api_routes.py b/source/app/blueprints/rest/api_routes.py index a12ae5808..10e767507 100644 --- a/source/app/blueprints/rest/api_routes.py +++ b/source/app/blueprints/rest/api_routes.py @@ -20,7 +20,7 @@ from app import app from app.blueprints.access_controls import ac_api_requires -from app.util import response_success +from app.blueprints.responses import response_success rest_api_blueprint = Blueprint('rest_api', __name__) diff --git a/source/app/blueprints/rest/case/case_assets_routes.py b/source/app/blueprints/rest/case/case_assets_routes.py index 6a7c1c632..3dd5ebf72 100644 --- a/source/app/blueprints/rest/case/case_assets_routes.py +++ b/source/app/blueprints/rest/case/case_assets_routes.py @@ -26,10 +26,6 @@ from app import db from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated -from app.blueprints.rest.endpoints import response_api_deleted -from app.blueprints.rest.endpoints import response_api_success -from app.blueprints.rest.endpoints import response_api_error -from app.blueprints.rest.endpoints import response_api_created from app.business.assets import assets_delete from app.business.assets import assets_create from app.business.assets import assets_get_detailed @@ -59,11 +55,11 @@ from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseAssetsSchema from app.schema.marshables import CommentSchema -from app.blueprints.access_controls import ac_requires_case_identifier, ac_api_requires -from app.util import response_error -from app.util import response_success -from app.util import ac_api_return_access_denied - +from app.blueprints.access_controls import ac_requires_case_identifier +from app.blueprints.access_controls import ac_api_requires +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success +from app.blueprints.access_controls import ac_api_return_access_denied case_assets_rest_blueprint = Blueprint('case_assets_rest', __name__) diff --git a/source/app/blueprints/rest/case/case_evidences_routes.py b/source/app/blueprints/rest/case/case_evidences_routes.py index b90d0c8f5..330c19882 100644 --- a/source/app/blueprints/rest/case/case_evidences_routes.py +++ b/source/app/blueprints/rest/case/case_evidences_routes.py @@ -42,8 +42,8 @@ from app.schema.marshables import CommentSchema from app.blueprints.access_controls import ac_requires_case_identifier from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success case_evidences_rest_blueprint = Blueprint('case_evidences_rest', __name__) diff --git a/source/app/blueprints/rest/case/case_graphs_routes.py b/source/app/blueprints/rest/case/case_graphs_routes.py index cbd3de723..07ea68a4d 100644 --- a/source/app/blueprints/rest/case/case_graphs_routes.py +++ b/source/app/blueprints/rest/case/case_graphs_routes.py @@ -26,7 +26,7 @@ from app.models.authorization import CaseAccessLevel from app.blueprints.access_controls import ac_requires_case_identifier from app.blueprints.access_controls import ac_api_requires -from app.util import response_success +from app.blueprints.responses import response_success case_graph_rest_blueprint = Blueprint('case_graph_rest', __name__) diff --git a/source/app/blueprints/rest/case/case_ioc_routes.py b/source/app/blueprints/rest/case/case_ioc_routes.py index 159ba0932..759f6786d 100644 --- a/source/app/blueprints/rest/case/case_ioc_routes.py +++ b/source/app/blueprints/rest/case/case_ioc_routes.py @@ -27,11 +27,6 @@ from app import db from app.blueprints.rest.case_comments import case_comment_update -from app.blueprints.rest.endpoints import response_api_deleted -from app.blueprints.rest.endpoints import response_api_not_found -from app.blueprints.rest.endpoints import response_api_success -from app.blueprints.rest.endpoints import response_api_created -from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.endpoints import endpoint_deprecated from app.business.iocs import iocs_create from app.business.iocs import iocs_update @@ -40,7 +35,6 @@ from app.business.errors import BusinessProcessingError from app.business.errors import ObjectNotFoundError from app.datamgmt.case.case_iocs_db import add_comment_to_ioc -from app.datamgmt.case.case_iocs_db import get_filtered_iocs from app.datamgmt.case.case_iocs_db import add_ioc from app.datamgmt.case.case_iocs_db import delete_ioc_comment from app.datamgmt.case.case_iocs_db import get_case_ioc_comment @@ -57,12 +51,11 @@ from app.models.authorization import CaseAccessLevel from app.schema.marshables import CommentSchema from app.schema.marshables import IocSchema -from app.schema.marshables import IocSchemaForAPIV2 from app.blueprints.access_controls import ac_requires_case_identifier from app.blueprints.access_controls import ac_api_requires -from app.util import ac_api_return_access_denied -from app.util import response_error -from app.util import response_success +from app.blueprints.access_controls import ac_api_return_access_denied +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success case_ioc_rest_blueprint = Blueprint('case_ioc_rest', __name__) @@ -90,7 +83,7 @@ def case_list_ioc(caseid): ret['state'] = get_ioc_state(caseid=caseid) - return response_success("", data=ret) + return response_success('', data=ret) @case_ioc_rest_blueprint.route('/case/ioc/state', methods=['GET']) @@ -127,8 +120,8 @@ def case_upload_ioc(caseid): jsdata = request.get_json() # get IOC list from request - headers = "ioc_value,ioc_type,ioc_description,ioc_tags,ioc_tlp" - csv_lines = jsdata["CSVData"].splitlines() # unavoidable since the file is passed as a string + headers = 'ioc_value,ioc_type,ioc_description,ioc_tags,ioc_tlp' + csv_lines = jsdata['CSVData'].splitlines() # unavoidable since the file is passed as a string if csv_lines[0].lower() != headers: csv_lines.insert(0, headers) @@ -145,30 +138,32 @@ def case_upload_ioc(caseid): for e in headers.split(','): if row.get(e) is None: - errors.append(f"{e} is missing for row {index}") + errors.append(f'{e} is missing for row {index}') index += 1 continue # IOC value must not be empty - if not row.get("ioc_value"): - errors.append(f"Empty IOC value for row {index}") - track_activity("Attempted to upload an empty IOC value") + if not row.get('ioc_value'): + errors.append(f'Empty IOC value for row {index}') + track_activity('Attempted to upload an empty IOC value') index += 1 continue - row["ioc_tags"] = row["ioc_tags"].replace("|", ",") # Reformat Tags + row['ioc_tags'] = row['ioc_tags'].replace('|', ',') # Reformat Tags # Convert TLP into TLP id - if row["ioc_tlp"] in tlp_dict: - row["ioc_tlp_id"] = tlp_dict[row["ioc_tlp"]] + if row['ioc_tlp'] in tlp_dict: + row['ioc_tlp_id'] = tlp_dict[row['ioc_tlp']] else: - row["ioc_tlp_id"] = "" - row.pop("ioc_tlp", None) + row['ioc_tlp_id'] = '' + row.pop('ioc_tlp', None) type_id = get_ioc_type_id(row['ioc_type'].lower()) if not type_id: - errors.append(f"{row['ioc_value']} (invalid ioc type: {row['ioc_type']}) for row {index}") - log.error(f'Unrecognised IOC type {row["ioc_type"]}') + ioc_value = row['ioc_value'] + ioc_type = row['ioc_type'] + errors.append(f'{ioc_value} (invalid ioc type: {ioc_type}) for row {index}') + log.error(f'Unrecognised IOC type {ioc_type}') index += 1 continue @@ -182,25 +177,24 @@ def case_upload_ioc(caseid): index += 1 if not ioc: - errors.append(f"{ioc.ioc_value} (internal reasons)") - log.error(f"Unable to create IOC {ioc.ioc_value} for internal reasons") + errors.append(f'{ioc.ioc_value} (internal reasons)') + log.error(f'Unable to create IOC {ioc.ioc_value} for internal reasons') continue add_ioc(ioc, current_user.id, caseid) ioc = call_modules_hook('on_postload_ioc_create', data=ioc, caseid=caseid) ret.append(request_data) - track_activity(f"added ioc \"{ioc.ioc_value}\"", caseid=caseid) - + track_activity(f'added ioc "{ioc.ioc_value}"', caseid=caseid) if len(errors) == 0: - msg = "Successfully imported data." + msg = 'Successfully imported data.' else: - msg = "Data is imported but we got errors with the following rows:\n- " + "\n- ".join(errors) + msg = 'Data is imported but we got errors with the following rows:\n- ' + '\n- '.join(errors) return response_success(msg=msg, data=ret) except marshmallow.exceptions.ValidationError as e: - return response_error(msg="Data error", data=e.messages) + return response_error(msg='Data error', data=e.messages) @case_ioc_rest_blueprint.route('/case/ioc/delete/', methods=['POST']) @@ -221,6 +215,7 @@ def deprecated_case_delete_ioc(cur_id, caseid): except BusinessProcessingError as e: return response_error(e.get_message()) + @case_ioc_rest_blueprint.route('/case/ioc/', methods=['GET']) @endpoint_deprecated('GET', '/api/v2/iocs/') @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access) @@ -282,16 +277,16 @@ def case_comment_ioc_add(cur_id, caseid): db.session.commit() hook_data = { - "comment": comment_schema.dump(comment), - "ioc": IocSchema().dump(ioc) + 'comment': comment_schema.dump(comment), + 'ioc': IocSchema().dump(ioc) } call_modules_hook('on_postload_ioc_commented', data=hook_data, caseid=ioc.case_id) - track_activity(f"ioc \"{ioc.ioc_value}\" commented", caseid=ioc.case_id) - return response_success("Event commented", data=comment_schema.dump(comment)) + track_activity(f'ioc "{ioc.ioc_value}" commented', caseid=ioc.case_id) + return response_success('Event commented', data=comment_schema.dump(comment)) except marshmallow.exceptions.ValidationError as e: - return response_error(msg="Data error", data=e.normalized_messages()) + return response_error(msg='Data error', data=e.normalized_messages()) except ObjectNotFoundError: return response_error('Invalid ioc ID') @@ -302,7 +297,7 @@ def case_comment_ioc_add(cur_id, caseid): def case_comment_ioc_get(cur_id, com_id, caseid): comment = get_case_ioc_comment(cur_id, com_id) if not comment: - return response_error("Invalid comment ID") + return response_error('Invalid comment ID') return response_success(data=comment._asdict()) @@ -324,5 +319,5 @@ def case_comment_ioc_delete(cur_id, com_id, caseid): call_modules_hook('on_postload_ioc_comment_delete', data=com_id, caseid=caseid) - track_activity(f"comment {com_id} on ioc {cur_id} deleted", caseid=caseid) + track_activity(f'comment {com_id} on ioc {cur_id} deleted', caseid=caseid) return response_success(msg) diff --git a/source/app/blueprints/rest/case/case_notes_routes.py b/source/app/blueprints/rest/case/case_notes_routes.py index faae9d8d0..5cd6eaba8 100644 --- a/source/app/blueprints/rest/case/case_notes_routes.py +++ b/source/app/blueprints/rest/case/case_notes_routes.py @@ -54,9 +54,9 @@ from app.schema.marshables import CommentSchema from app.blueprints.access_controls import ac_requires_case_identifier from app.blueprints.access_controls import ac_api_requires -from app.util import endpoint_removed -from app.util import response_error -from app.util import response_success +from app.blueprints.rest.endpoints import endpoint_removed +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success case_notes_rest_blueprint = Blueprint('case_notes_rest', __name__) diff --git a/source/app/blueprints/rest/case/case_routes.py b/source/app/blueprints/rest/case/case_routes.py index 72b3ce595..3d1e7c727 100644 --- a/source/app/blueprints/rest/case/case_routes.py +++ b/source/app/blueprints/rest/case/case_routes.py @@ -42,8 +42,8 @@ from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access from app.business.cases import cases_create from app.business.cases import cases_delete +from app.business.cases import cases_exists from app.business.errors import BusinessProcessingError -from app.datamgmt.case.case_db import case_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 @@ -70,9 +70,9 @@ 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.util import ac_api_return_access_denied -from app.util import response_error -from app.util import response_success +from app.blueprints.access_controls import ac_api_return_access_denied +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success case_rest_blueprint = Blueprint('case_rest', __name__) @@ -83,9 +83,9 @@ @endpoint_deprecated('GET', '/api/v2/cases/') @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access) @ac_api_requires() -def case_exists_r(caseid): +def case_routes_exists(caseid): - if case_exists(caseid): + if cases_exists(caseid): return response_success('Case exists') return response_error('Case does not exist', 404) @@ -414,7 +414,7 @@ def get_cases() -> Response: cases = { 'total': filtered_cases.total, # TODO should maybe really uniform all return types of paginated list and replace field cases by field data - 'cases': CaseSchemaForAPIV2().dump(filtered_cases.items, many=True), + 'data': CaseSchemaForAPIV2().dump(filtered_cases.items, many=True), 'last_page': filtered_cases.pages, 'current_page': filtered_cases.page, 'next_page': filtered_cases.next_num if filtered_cases.has_next else None, diff --git a/source/app/blueprints/rest/case/case_tasks_routes.py b/source/app/blueprints/rest/case/case_tasks_routes.py index f0703b8e3..a77db4daa 100644 --- a/source/app/blueprints/rest/case/case_tasks_routes.py +++ b/source/app/blueprints/rest/case/case_tasks_routes.py @@ -25,13 +25,8 @@ from app import db from app.blueprints.rest.case_comments import case_comment_update -from app.blueprints.rest.endpoints import response_api_deleted -from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import endpoint_deprecated -from app.blueprints.rest.endpoints import response_api_error -from app.blueprints.rest.endpoints import response_api_created from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError from app.business.tasks import tasks_delete from app.business.tasks import tasks_create from app.business.tasks import tasks_get @@ -46,7 +41,6 @@ from app.datamgmt.case.case_tasks_db import get_tasks_with_assignees from app.datamgmt.case.case_tasks_db import update_task_status from app.datamgmt.states import get_tasks_state -from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.models.authorization import CaseAccessLevel @@ -54,9 +48,8 @@ from app.schema.marshables import CommentSchema from app.blueprints.access_controls import ac_requires_case_identifier from app.blueprints.access_controls import ac_api_requires -from app.util import ac_api_return_access_denied -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success case_tasks_rest_blueprint = Blueprint('case_tasks_rest', __name__) @@ -123,20 +116,6 @@ def deprecated_case_add_task(caseid): return response_error(e.get_message(), data=e.get_data()) -@case_tasks_rest_blueprint.route('/api/v2/cases//tasks', methods=['POST']) -@ac_api_requires() -def case_add_task(identifier): - if not ac_fast_check_current_user_has_case_access(identifier, [CaseAccessLevel.full_access]): - return ac_api_return_access_denied(caseid=identifier) - - task_schema = CaseTaskSchema() - try: - _, case = tasks_create(identifier, request.get_json()) - return response_api_created(task_schema.dump(case)) - except BusinessProcessingError as e: - return response_api_error(e.get_message()) - - @case_tasks_rest_blueprint.route('/case/tasks/', methods=['GET']) @endpoint_deprecated('GET', '/api/v2/tasks/') @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access) @@ -151,20 +130,6 @@ def deprecated_case_task_view(cur_id, caseid): return response_success(data=task_schema.dump(task)) -@case_tasks_rest_blueprint.route('/api/v2/tasks/', methods=['GET']) -@ac_api_requires() -def case_task_view(identifier): - try: - task = tasks_get(identifier) - if not ac_fast_check_current_user_has_case_access(task.task_case_id, [CaseAccessLevel.read_only, CaseAccessLevel.full_access]): - return ac_api_return_access_denied(caseid=task.task_case_id) - - task_schema = CaseTaskSchema() - return response_api_created(task_schema.dump(task)) - except ObjectNotFoundError: - return response_api_not_found() - - @case_tasks_rest_blueprint.route('/case/tasks/update/', methods=['POST']) @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() @@ -189,20 +154,6 @@ def deprecated_case_delete_task(cur_id, caseid): return response_error(e.get_message()) -@case_tasks_rest_blueprint.route('/api/v2/tasks/', methods=['DELETE']) -@ac_api_requires() -def case_delete_task(identifier): - try: - task = tasks_get(identifier) - if not ac_fast_check_current_user_has_case_access(task.task_case_id, [CaseAccessLevel.full_access]): - return ac_api_return_access_denied(caseid=identifier) - - tasks_delete(task) - return response_api_deleted() - except BusinessProcessingError as e: - return response_api_error(e.get_message()) - - @case_tasks_rest_blueprint.route('/case/tasks//comments/list', methods=['GET']) @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access) @ac_api_requires() diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index 56f45e0be..25a405a90 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -69,8 +69,8 @@ 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.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success case_timeline_rest_blueprint = Blueprint('case_timeline_rest', __name__) @@ -679,7 +679,7 @@ def event_view(cur_id, caseid): return response_success(data=output) -@case_timeline_rest_blueprint.route('/case/timeline/events/update/', methods=["POST"]) +@case_timeline_rest_blueprint.route('/case/timeline/events/update/', methods=['POST']) @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_edit_event(cur_id, caseid): diff --git a/source/app/blueprints/rest/case_comments.py b/source/app/blueprints/rest/case_comments.py index f52b9441f..dbf7bd985 100644 --- a/source/app/blueprints/rest/case_comments.py +++ b/source/app/blueprints/rest/case_comments.py @@ -21,8 +21,8 @@ from flask import request from app.schema.marshables import CommentSchema -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success from app.business.case_comments import case_comments_update from app.business.errors import BusinessProcessingError diff --git a/source/app/blueprints/rest/context_routes.py b/source/app/blueprints/rest/context_routes.py index fe69cea09..b85b84257 100644 --- a/source/app/blueprints/rest/context_routes.py +++ b/source/app/blueprints/rest/context_routes.py @@ -28,9 +28,8 @@ from app.models.authorization import Permissions from app.models.cases import Cases from app.models.models import Client -from app.blueprints.access_controls import ac_api_requires -from app.util import not_authenticated_redirection_url -from app.util import response_success +from app.blueprints.access_controls import ac_api_requires, not_authenticated_redirection_url +from app.blueprints.responses import response_success context_rest_blueprint = Blueprint('context_rest', __name__) diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index aceb2f142..a9c435148 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -42,11 +42,11 @@ from app.schema.marshables import CaseTaskSchema from app.schema.marshables import CaseDetailsSchema from app.schema.marshables import GlobalTasksSchema -from app.blueprints.access_controls import ac_requires_case_identifier +from app.blueprints.access_controls import ac_requires_case_identifier, is_authentication_oidc, \ + not_authenticated_redirection_url from app.blueprints.access_controls import ac_api_requires -from app.util import response_error, not_authenticated_redirection_url -from app.util import response_success -from app.util import is_authentication_oidc +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success from oic.oauth2.exception import GrantError diff --git a/source/app/blueprints/rest/datastore_routes.py b/source/app/blueprints/rest/datastore_routes.py index 15aa82110..25b6aa4de 100644 --- a/source/app/blueprints/rest/datastore_routes.py +++ b/source/app/blueprints/rest/datastore_routes.py @@ -49,8 +49,8 @@ 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.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success datastore_rest_blueprint = Blueprint('datastore_rest', __name__) diff --git a/source/app/blueprints/rest/dim_tasks_routes.py b/source/app/blueprints/rest/dim_tasks_routes.py index 3a8c99b15..599fc2040 100644 --- a/source/app/blueprints/rest/dim_tasks_routes.py +++ b/source/app/blueprints/rest/dim_tasks_routes.py @@ -39,8 +39,8 @@ from app.models.authorization import CaseAccessLevel from app.blueprints.access_controls import ac_requires_case_identifier from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success from iris_interface.IrisInterfaceStatus import IIStatus dim_tasks_rest_blueprint = Blueprint('dim_tasks_rest', __name__) diff --git a/source/app/blueprints/rest/endpoints.py b/source/app/blueprints/rest/endpoints.py index dc0b196c7..de35479dc 100644 --- a/source/app/blueprints/rest/endpoints.py +++ b/source/app/blueprints/rest/endpoints.py @@ -17,9 +17,9 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from functools import wraps + from app import app -from app.business.errors import BusinessProcessingError -from app.util import response +from app.blueprints.responses import response_error, response logger = app.logger @@ -60,3 +60,12 @@ def wrap(*args, **kwargs): return result return wrap return inner_wrap + + +def endpoint_removed(message, version): + def inner_wrap(f): + @wraps(f) + def wrap(*args, **kwargs): + return response_error(f"Endpoint deprecated in {version}. {message}.", status=410) + return wrap + return inner_wrap diff --git a/source/app/blueprints/rest/filters_routes.py b/source/app/blueprints/rest/filters_routes.py index 737faf9ad..87b483e5e 100644 --- a/source/app/blueprints/rest/filters_routes.py +++ b/source/app/blueprints/rest/filters_routes.py @@ -26,8 +26,8 @@ from app.iris_engine.utils.tracker import track_activity from app.schema.marshables import SavedFilterSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_success -from app.util import response_error +from app.blueprints.responses import response_success +from app.blueprints.responses import response_error saved_filters_rest_blueprint = Blueprint('saved_filters_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_access_control_routes.py b/source/app/blueprints/rest/manage/manage_access_control_routes.py index 3c964c652..9f924cdc2 100644 --- a/source/app/blueprints/rest/manage/manage_access_control_routes.py +++ b/source/app/blueprints/rest/manage/manage_access_control_routes.py @@ -25,7 +25,7 @@ from app.iris_engine.access_control.utils import ac_trace_user_effective_cases_access_2 from app.models.authorization import Permissions from app.blueprints.access_controls import ac_api_requires -from app.util import response_success +from app.blueprints.responses import response_success manage_ac_rest_blueprint = Blueprint('access_control_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_alerts_status_routes.py b/source/app/blueprints/rest/manage/manage_alerts_status_routes.py index 07a6937af..c82266013 100644 --- a/source/app/blueprints/rest/manage/manage_alerts_status_routes.py +++ b/source/app/blueprints/rest/manage/manage_alerts_status_routes.py @@ -26,8 +26,8 @@ from app.schema.marshables import AlertStatusSchema from app.schema.marshables import AlertResolutionSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_alerts_status_rest_blueprint = Blueprint('manage_alerts_status_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_analysis_status_routes.py b/source/app/blueprints/rest/manage/manage_analysis_status_routes.py index f6ed2b39c..17ac1f891 100644 --- a/source/app/blueprints/rest/manage/manage_analysis_status_routes.py +++ b/source/app/blueprints/rest/manage/manage_analysis_status_routes.py @@ -25,8 +25,8 @@ from app.models.models import AnalysisStatus from app.schema.marshables import AnalysisStatusSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_analysis_status_rest_blueprint = Blueprint('manage_analysis_status_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_assets_routes.py b/source/app/blueprints/rest/manage/manage_assets_routes.py index fd62134b7..595af98de 100644 --- a/source/app/blueprints/rest/manage/manage_assets_routes.py +++ b/source/app/blueprints/rest/manage/manage_assets_routes.py @@ -25,8 +25,8 @@ from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseAssetsSchema from app.blueprints.access_controls import ac_api_requires -from app.util import ac_api_return_access_denied -from app.util import response_success +from app.blueprints.access_controls import ac_api_return_access_denied +from app.blueprints.responses import response_success manage_assets_rest_blueprint = Blueprint('manage_assets_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_assets_type_routes.py b/source/app/blueprints/rest/manage/manage_assets_type_routes.py index ede277f75..26af87ca4 100644 --- a/source/app/blueprints/rest/manage/manage_assets_type_routes.py +++ b/source/app/blueprints/rest/manage/manage_assets_type_routes.py @@ -32,8 +32,8 @@ from app.models.models import CaseAssets from app.schema.marshables import AssetTypeSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_assets_type_rest_blueprint = Blueprint('manage_assets_type_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_attributes_routes.py b/source/app/blueprints/rest/manage/manage_attributes_routes.py index caf8bfbd4..5dcd1b8ae 100644 --- a/source/app/blueprints/rest/manage/manage_attributes_routes.py +++ b/source/app/blueprints/rest/manage/manage_attributes_routes.py @@ -25,8 +25,8 @@ from app.models.authorization import Permissions from app.models.models import CustomAttribute from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_attributes_rest_blueprint = Blueprint('manage_attributes_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_case_classifications_routes.py b/source/app/blueprints/rest/manage/manage_case_classifications_routes.py index 28d6cf93e..a63f3c26f 100644 --- a/source/app/blueprints/rest/manage/manage_case_classifications_routes.py +++ b/source/app/blueprints/rest/manage/manage_case_classifications_routes.py @@ -30,8 +30,8 @@ from app.models.authorization import Permissions from app.schema.marshables import CaseClassificationSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_case_classification_rest_blueprint = Blueprint('manage_case_classifications_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_case_state.py b/source/app/blueprints/rest/manage/manage_case_state.py index 47e434016..57b8461db 100644 --- a/source/app/blueprints/rest/manage/manage_case_state.py +++ b/source/app/blueprints/rest/manage/manage_case_state.py @@ -30,8 +30,8 @@ from app.models.authorization import Permissions from app.schema.marshables import CaseStateSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_case_state_rest_blueprint = Blueprint('manage_case_state_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_case_templates_routes.py b/source/app/blueprints/rest/manage/manage_case_templates_routes.py index 073c28053..aab98e66d 100644 --- a/source/app/blueprints/rest/manage/manage_case_templates_routes.py +++ b/source/app/blueprints/rest/manage/manage_case_templates_routes.py @@ -33,8 +33,8 @@ from app.schema.marshables import CaseTemplateSchema from app.blueprints.access_controls import ac_requires_case_identifier from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_case_templates_rest_blueprint = Blueprint('manage_case_templates_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index fc53e2171..07167dbf9 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -52,9 +52,9 @@ from app.util import add_obj_history_entry from app.blueprints.access_controls import ac_requires_case_identifier from app.blueprints.access_controls import ac_api_requires -from app.util import ac_api_return_access_denied -from app.util import response_error -from app.util import response_success +from app.blueprints.access_controls import ac_api_return_access_denied +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success from app.business.cases import cases_delete from app.business.cases import cases_update from app.business.cases import cases_create diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index 61d204418..84cd20ebb 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -43,14 +43,14 @@ from app.models.authorization import Permissions from app.schema.marshables import ContactSchema from app.schema.marshables import CustomerSchema -from app.util import ac_api_requires_client_access -from app.util import response_error -from app.util import response_success +from app.blueprints.access_controls import ac_api_requires_client_access +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_customers_rest_blueprint = Blueprint('manage_customers_rest', __name__) -@manage_customers_rest_blueprint.route('/manage/customers/list') +@manage_customers_rest_blueprint.route('/manage/customers/list', methods=['GET']) @ac_api_requires(Permissions.customers_read) def list_customers(): user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) diff --git a/source/app/blueprints/rest/manage/manage_event_categories_routes.py b/source/app/blueprints/rest/manage/manage_event_categories_routes.py index d3328f877..ec383cbb6 100644 --- a/source/app/blueprints/rest/manage/manage_event_categories_routes.py +++ b/source/app/blueprints/rest/manage/manage_event_categories_routes.py @@ -22,8 +22,8 @@ from app.models.models import EventCategory from app.schema.marshables import EventCategorySchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_event_categories_rest_blueprint = Blueprint('manage_event_categories_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_evidence_types_routes.py b/source/app/blueprints/rest/manage/manage_evidence_types_routes.py index 810331314..2bf31c48a 100644 --- a/source/app/blueprints/rest/manage/manage_evidence_types_routes.py +++ b/source/app/blueprints/rest/manage/manage_evidence_types_routes.py @@ -30,8 +30,8 @@ from app.models.authorization import Permissions from app.schema.marshables import EvidenceTypeSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_evidence_types_rest_blueprint = Blueprint('manage_evidence_types_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_groups.py b/source/app/blueprints/rest/manage/manage_groups.py index 4f1e7b1ab..e6f857bb0 100644 --- a/source/app/blueprints/rest/manage/manage_groups.py +++ b/source/app/blueprints/rest/manage/manage_groups.py @@ -44,9 +44,9 @@ from app.models.authorization import Permissions from app.schema.marshables import AuthorizationGroupSchema from app.blueprints.access_controls import ac_api_requires -from app.util import ac_api_return_access_denied -from app.util import response_error -from app.util import response_success +from app.blueprints.access_controls import ac_api_return_access_denied +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success from app.iris_engine.demo_builder import protect_demo_mode_group manage_groups_rest_blueprint = Blueprint('manage_groups_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_ioc_types_routes.py b/source/app/blueprints/rest/manage/manage_ioc_types_routes.py index c42457f46..6f5a07d9c 100644 --- a/source/app/blueprints/rest/manage/manage_ioc_types_routes.py +++ b/source/app/blueprints/rest/manage/manage_ioc_types_routes.py @@ -29,8 +29,8 @@ from app.models.authorization import Permissions from app.schema.marshables import IocTypeSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_ioc_type_rest_blueprint = Blueprint('manage_ioc_types_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_modules_routes.py b/source/app/blueprints/rest/manage/manage_modules_routes.py index c1bcd144f..90e22f6c7 100644 --- a/source/app/blueprints/rest/manage/manage_modules_routes.py +++ b/source/app/blueprints/rest/manage/manage_modules_routes.py @@ -39,8 +39,8 @@ from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success from app.schema.marshables import IrisModuleSchema manage_modules_rest_blueprint = Blueprint('manage_module_rest', __name__) @@ -216,7 +216,7 @@ def view_modules_hook(): # TODO is this endpoint still useful? -@manage_modules_rest_blueprint.route("/sitemap") +@manage_modules_rest_blueprint.route('/sitemap', methods=['GET']) @ac_api_requires() def site_map(): links = [] diff --git a/source/app/blueprints/rest/manage/manage_server_settings_routes.py b/source/app/blueprints/rest/manage/manage_server_settings_routes.py index c5201caae..3bd5f6e3f 100644 --- a/source/app/blueprints/rest/manage/manage_server_settings_routes.py +++ b/source/app/blueprints/rest/manage/manage_server_settings_routes.py @@ -31,8 +31,8 @@ from app.models.authorization import Permissions from app.schema.marshables import ServerSettingsSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success from dictdiffer import diff manage_server_settings_rest_blueprint = Blueprint('manage_server_settings_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_severities_routes.py b/source/app/blueprints/rest/manage/manage_severities_routes.py index 989d2c5cc..de0eb164a 100644 --- a/source/app/blueprints/rest/manage/manage_severities_routes.py +++ b/source/app/blueprints/rest/manage/manage_severities_routes.py @@ -24,8 +24,8 @@ from app.datamgmt.manage.manage_common import search_severity_by_name from app.schema.marshables import SeveritySchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_severities_rest_blueprint = Blueprint('manage_severities_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_tags.py b/source/app/blueprints/rest/manage/manage_tags.py index e724ad07c..14deac4b4 100644 --- a/source/app/blueprints/rest/manage/manage_tags.py +++ b/source/app/blueprints/rest/manage/manage_tags.py @@ -23,8 +23,7 @@ from app.datamgmt.manage.manage_tags_db import get_filtered_tags from app.schema.marshables import TagsSchema from app.blueprints.access_controls import ac_api_requires -from app.util import AlchemyEncoder -from app.util import response_success +from app.blueprints.responses import response_success, AlchemyEncoder manage_tags_rest_blueprint = Blueprint('manage_tags_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_task_status_routes.py b/source/app/blueprints/rest/manage/manage_task_status_routes.py index a6a9532e6..bdb261738 100644 --- a/source/app/blueprints/rest/manage/manage_task_status_routes.py +++ b/source/app/blueprints/rest/manage/manage_task_status_routes.py @@ -20,8 +20,8 @@ from app.models.models import TaskStatus from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_task_status_rest_blueprint = Blueprint('manage_task_status_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_templates_routes.py b/source/app/blueprints/rest/manage/manage_templates_routes.py index f28b19323..2c0c791cd 100644 --- a/source/app/blueprints/rest/manage/manage_templates_routes.py +++ b/source/app/blueprints/rest/manage/manage_templates_routes.py @@ -37,8 +37,8 @@ from app.models.models import Languages from app.models.models import ReportType from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success _ALLOWED_EXTENSIONS = {'md', 'html', 'doc', 'docx'} diff --git a/source/app/blueprints/rest/manage/manage_tlps_routes.py b/source/app/blueprints/rest/manage/manage_tlps_routes.py index 37cad485d..a72ec43f2 100644 --- a/source/app/blueprints/rest/manage/manage_tlps_routes.py +++ b/source/app/blueprints/rest/manage/manage_tlps_routes.py @@ -20,8 +20,8 @@ from app.models import Tlp from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success manage_tlp_type_rest_blueprint = Blueprint('manage_tlp_types_rest', __name__) diff --git a/source/app/blueprints/rest/manage/manage_users.py b/source/app/blueprints/rest/manage/manage_users.py index 1aa06d0f9..2f1d2cde3 100644 --- a/source/app/blueprints/rest/manage/manage_users.py +++ b/source/app/blueprints/rest/manage/manage_users.py @@ -47,11 +47,9 @@ from app.schema.marshables import UserFullSchema from app.blueprints.access_controls import ac_api_requires -from app.util import is_authentication_oidc, is_authentication_ldap -from app.util import ac_api_return_access_denied -from app.util import is_authentication_local -from app.util import response_error -from app.util import response_success +from app.blueprints.access_controls import ac_api_return_access_denied +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success from app.iris_engine.demo_builder import protect_demo_mode_user manage_users_rest_blueprint = Blueprint('manage_users_rest', __name__) @@ -277,44 +275,6 @@ def manage_user_cac_delete_cases(cur_id): return response_error(msg=logs) -@manage_users_rest_blueprint.route('/manage/users//case-access/delete', methods=['POST']) -@ac_api_requires(Permissions.server_administrator) -def manage_user_cac_delete_case(cur_id): - - user = get_user(cur_id) - if not user: - return response_error("Invalid user ID") - - if not request.is_json: - return response_error("Invalid request") - - data = request.get_json() - if not data: - return response_error("Invalid request") - - if not isinstance(data.get('case'), int): - return response_error("Expecting cases as int") - - try: - - success, logs = remove_case_access_from_user(user.id, data.get('case')) - db.session.commit() - - except Exception as e: - log.error("Error while removing cases access from user: {}".format(e)) - log.error(traceback.format_exc()) - return response_error(msg=str(e)) - - if success: - track_activity(f"case access for case {data.get('case')} deleted for user {user.user}", ctx_less=True) - - user = get_user_details(cur_id) - - return response_success(msg="User case access updated", data=user) - - return response_error(msg=logs) - - @manage_users_rest_blueprint.route('/manage/users/update/', methods=['POST']) @ac_api_requires(Permissions.server_administrator) def update_user_api(cur_id): diff --git a/source/app/blueprints/rest/overview_routes.py b/source/app/blueprints/rest/overview_routes.py index 9992849df..892c5abeb 100644 --- a/source/app/blueprints/rest/overview_routes.py +++ b/source/app/blueprints/rest/overview_routes.py @@ -22,7 +22,7 @@ from app.datamgmt.overview.overview_db import get_overview_db from app.blueprints.access_controls import ac_api_requires -from app.util import response_success +from app.blueprints.responses import response_success overview_rest_blueprint = Blueprint('overview_rest', __name__) diff --git a/source/app/blueprints/rest/profile_routes.py b/source/app/blueprints/rest/profile_routes.py index c2c871668..56043ae28 100644 --- a/source/app/blueprints/rest/profile_routes.py +++ b/source/app/blueprints/rest/profile_routes.py @@ -35,9 +35,9 @@ from app.schema.marshables import UserSchema from app.schema.marshables import BasicUserSchema from app.blueprints.access_controls import ac_api_requires -from app.util import response_error -from app.util import response_success -from app.util import endpoint_removed +from app.blueprints.responses import response_error +from app.blueprints.responses import response_success +from app.blueprints.rest.endpoints import endpoint_removed profile_rest_blueprint = Blueprint('profile_rest', __name__) diff --git a/source/app/blueprints/rest/reports_route.py b/source/app/blueprints/rest/reports_route.py index 9aa7bed9c..94d7b87ce 100644 --- a/source/app/blueprints/rest/reports_route.py +++ b/source/app/blueprints/rest/reports_route.py @@ -35,7 +35,7 @@ from app.util import FileRemover from app.blueprints.access_controls import ac_requires_case_identifier from app.blueprints.access_controls import ac_api_requires -from app.util import response_error +from app.blueprints.responses import response_error from app.datamgmt.case.case_db import get_case reports_rest_blueprint = Blueprint('reports_rest', __name__) diff --git a/source/app/blueprints/rest/search_routes.py b/source/app/blueprints/rest/search_routes.py index bc725ccc2..7da5e4410 100644 --- a/source/app/blueprints/rest/search_routes.py +++ b/source/app/blueprints/rest/search_routes.py @@ -30,7 +30,7 @@ from app.models.models import Notes from app.models.models import Tlp from app.blueprints.access_controls import ac_api_requires -from app.util import response_success +from app.blueprints.responses import response_success search_rest_blueprint = Blueprint('search_rest', __name__) diff --git a/source/app/blueprints/rest/v2/case/api_v2_assets_routes.py b/source/app/blueprints/rest/v2/case/api_v2_assets_routes.py index 3e0a748de..3ea6e7852 100644 --- a/source/app/blueprints/rest/v2/case/api_v2_assets_routes.py +++ b/source/app/blueprints/rest/v2/case/api_v2_assets_routes.py @@ -24,14 +24,17 @@ from app.blueprints.rest.endpoints import response_api_deleted from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.endpoints import response_api_success +from app.blueprints.rest.endpoints import response_api_not_found +from app.business.cases import cases_exists from app.business.assets import assets_create from app.business.assets import assets_delete from app.business.assets import assets_get from app.business.errors import BusinessProcessingError +from app.business.errors import ObjectNotFoundError from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseAssetsSchema -from app.util import ac_api_return_access_denied +from app.blueprints.access_controls import ac_api_return_access_denied api_v2_assets_blueprint = Blueprint('case_assets_rest_v2', __name__, @@ -41,6 +44,8 @@ @api_v2_assets_blueprint.route('/cases//assets', methods=['POST']) @ac_api_requires() def add_asset(identifier): + if not cases_exists(identifier): + return response_api_not_found() if not ac_fast_check_current_user_has_case_access(identifier, [CaseAccessLevel.full_access]): return ac_api_return_access_denied(caseid=identifier) @@ -77,5 +82,7 @@ def asset_delete(identifier): assets_delete(asset) return response_api_deleted() + except ObjectNotFoundError: + return response_api_not_found() except BusinessProcessingError as e: - return response_api_error(e.get_message()) \ No newline at end of file + return response_api_error(e.get_message()) diff --git a/source/app/blueprints/rest/v2/case/api_v2_case_tasks_routes.py b/source/app/blueprints/rest/v2/case/api_v2_case_tasks_routes.py new file mode 100644 index 000000000..5d70dc950 --- /dev/null +++ b/source/app/blueprints/rest/v2/case/api_v2_case_tasks_routes.py @@ -0,0 +1,83 @@ +# 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. + +from flask import Blueprint +from flask import request + +from app.blueprints.rest.endpoints import response_api_deleted +from app.blueprints.rest.endpoints import response_api_not_found +from app.blueprints.rest.endpoints import response_api_error +from app.blueprints.rest.endpoints import response_api_created +from app.blueprints.access_controls import ac_api_return_access_denied +from app.blueprints.access_controls import ac_api_requires +from app.schema.marshables import CaseTaskSchema +from app.business.errors import ObjectNotFoundError +from app.business.errors import BusinessProcessingError +from app.business.tasks import tasks_delete +from app.business.tasks import tasks_create +from app.business.tasks import tasks_get +from app.models.authorization import CaseAccessLevel +from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access + +api_v2_tasks_blueprint = Blueprint('case_tasks_rest_v2', + __name__, + url_prefix='/api/v2') + + +@api_v2_tasks_blueprint.route('/cases//tasks', methods=['POST']) +@ac_api_requires() +def case_add_task(identifier): + if not ac_fast_check_current_user_has_case_access(identifier, [CaseAccessLevel.full_access]): + return ac_api_return_access_denied(caseid=identifier) + + task_schema = CaseTaskSchema() + try: + _, case = tasks_create(identifier, request.get_json()) + return response_api_created(task_schema.dump(case)) + except BusinessProcessingError as e: + return response_api_error(e.get_message()) + + +@api_v2_tasks_blueprint.route('/tasks/', methods=['GET']) +@ac_api_requires() +def case_task_view(identifier): + try: + task = tasks_get(identifier) + if not ac_fast_check_current_user_has_case_access(task.task_case_id, [CaseAccessLevel.read_only, CaseAccessLevel.full_access]): + return ac_api_return_access_denied(caseid=task.task_case_id) + + task_schema = CaseTaskSchema() + return response_api_created(task_schema.dump(task)) + except ObjectNotFoundError: + return response_api_not_found() + + +@api_v2_tasks_blueprint.route('/tasks/', methods=['DELETE']) +@ac_api_requires() +def case_delete_task(identifier): + try: + task = tasks_get(identifier) + if not ac_fast_check_current_user_has_case_access(task.task_case_id, [CaseAccessLevel.full_access]): + return ac_api_return_access_denied(caseid=identifier) + + tasks_delete(task) + return response_api_deleted() + except ObjectNotFoundError: + return response_api_not_found() + except BusinessProcessingError as e: + return response_api_error(e.get_message()) diff --git a/source/app/blueprints/rest/v2/case/api_v2_ioc_routes.py b/source/app/blueprints/rest/v2/case/api_v2_ioc_routes.py index 4c8646828..a1064ae3f 100644 --- a/source/app/blueprints/rest/v2/case/api_v2_ioc_routes.py +++ b/source/app/blueprints/rest/v2/case/api_v2_ioc_routes.py @@ -35,12 +35,15 @@ from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access from app.models.authorization import CaseAccessLevel from app.schema.marshables import IocSchemaForAPIV2 -from app.util import ac_api_return_access_denied, response_success, response_error +from app.blueprints.access_controls import ac_api_return_access_denied +from app.blueprints.responses import response_success +from app.blueprints.responses import response_error api_v2_ioc_blueprint = Blueprint('case_ioc_rest_v2', __name__, url_prefix='/api/v2') + @api_v2_ioc_blueprint.route('/cases//iocs', methods=['GET']) @ac_api_requires() def list_ioc(identifier): @@ -80,8 +83,7 @@ def list_ioc(identifier): iocs = { 'total': filtered_iocs.total, - # TODO should maybe really uniform all return types of paginated list and replace field iocs by field data - 'iocs': iocs, + 'data': iocs, 'last_page': filtered_iocs.pages, 'current_page': filtered_iocs.page, 'next_page': filtered_iocs.next_num if filtered_iocs.has_next else None, @@ -118,7 +120,7 @@ def delete_case_ioc(identifier): return response_api_deleted() except ObjectNotFoundError: - raise BusinessProcessingError('Not a valid IOC for this case') + return response_api_not_found() except BusinessProcessingError as e: return response_api_error(e.get_message()) diff --git a/source/app/business/assets.py b/source/app/business/assets.py index 98d9b799a..361e7a1b9 100644 --- a/source/app/business/assets.py +++ b/source/app/business/assets.py @@ -20,6 +20,7 @@ from marshmallow.exceptions import ValidationError from app.business.errors import BusinessProcessingError +from app.business.errors import ObjectNotFoundError from app.models import CaseAssets from app.datamgmt.case.case_assets_db import get_asset from app.datamgmt.case.case_assets_db import case_assets_db_exists @@ -73,7 +74,7 @@ def assets_delete(asset: CaseAssets): def assets_get(identifier) -> CaseAssets: asset = get_asset(identifier) if not asset: - raise BusinessProcessingError('Invalid asset ID for this case') + raise ObjectNotFoundError() return asset diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 67346d329..65473905a 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -39,6 +39,7 @@ from app.iris_engine.utils.tracker import track_activity from app.iris_engine.access_control.utils import ac_set_new_case_access +from app.datamgmt.case.case_db import case_db_exists from app.datamgmt.case.case_db import save_case_tags from app.datamgmt.case.case_db import register_case_protagonists from app.datamgmt.case.case_db import get_review_id_from_name @@ -74,6 +75,10 @@ def cases_get_by_identifier(case_identifier): return get_case(case_identifier) +def cases_exists(identifier): + return case_db_exists(identifier) + + def cases_create(request_json): # TODO remove caseid doesn't seems to be useful for call_modules_hook => remove argument request_data = call_modules_hook('on_preload_case_create', request_json, None) diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index 3d9998ba7..fc3cbda8d 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -51,7 +51,7 @@ def get_case(caseid) -> Cases: return Cases.query.filter(Cases.case_id == caseid).first() -def case_exists(caseid): +def case_db_exists(caseid): return Cases.query.filter(Cases.case_id == caseid).count() diff --git a/source/app/datamgmt/manage/manage_access_control_db.py b/source/app/datamgmt/manage/manage_access_control_db.py index 7d885303f..9532375c7 100644 --- a/source/app/datamgmt/manage/manage_access_control_db.py +++ b/source/app/datamgmt/manage/manage_access_control_db.py @@ -14,6 +14,7 @@ # 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 import ac_current_user_has_permission from app.models import Cases from app.models.authorization import Group @@ -25,6 +26,9 @@ from app.models.authorization import OrganisationCaseAccess from app.models.authorization import User from app.models.authorization import UserCaseAccess +from app.datamgmt.case.case_db import case_db_exists + +from typing import Optional def manage_ac_audit_users_db(): @@ -73,7 +77,7 @@ def manage_ac_audit_users_db(): return ret -def check_ua_case_client(user_id: int, case_id: int) -> UserClient: +def check_ua_case_client(user_id: int, case_id: int) -> Optional[UserClient]: """Check if the user has access to the case, through the customer of the case (in other words, check that the customer of the case is assigned to the user) diff --git a/source/app/util.py b/source/app/util.py index 941db86ee..be706c726 100644 --- a/source/app/util.py +++ b/source/app/util.py @@ -17,174 +17,30 @@ # 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 -import decimal import hashlib -import json -import jwt import logging as log import marshmallow -import pickle import random -import requests import shutil import string -import traceback -import uuid import weakref from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hmac -from flask import Request -from flask import render_template from flask import request -from flask import session -from flask import url_for from flask_login import current_user -from flask_login import login_user -from functools import wraps -from jwt import PyJWKClient from pathlib import Path from pyunpack import Archive -from requests.auth import HTTPBasicAuth -from sqlalchemy.ext.declarative import DeclarativeMeta from sqlalchemy.orm.attributes import flag_modified -from app import TEMPLATE_PATH from app import app from app import db -from app.datamgmt.case.case_db import get_case -from app.datamgmt.manage.manage_access_control_db import user_has_client_access -from app.datamgmt.manage.manage_users_db import get_user -from app.iris_engine.access_control.utils import ac_get_effective_permissions_of_user -from app.iris_engine.utils.tracker import track_activity from app.models import Cases -def response(status, data=None): - if data is not None: - data = json.dumps(data, cls=AlchemyEncoder) - return app.response_class(response=data, status=status, mimetype='application/json') - - -def response_error(msg, data=None, status=400): - content = { - 'status': 'error', - 'message': msg, - 'data': data if data is not None else [] - } - return response(status, data=content) - - -def response_success(msg='', data=None): - content = { - "status": "success", - "message": msg, - "data": data if data is not None else [] - } - return response(200, data=content) - - -def g_db_commit(): - db.session.commit() - - -def g_db_add(obj): - if obj: - db.session.add(obj) - - -def g_db_del(obj): - if obj: - db.session.delete(obj) - - -class PgEncoder(json.JSONEncoder): - - def default(self, o): - if isinstance(o, datetime.datetime): - return DictDatetime(o) - - if isinstance(o, decimal.Decimal): - return str(o) - - return json.JSONEncoder.default(self, o) - - -class AlchemyEncoder(json.JSONEncoder): - - def default(self, obj): - if isinstance(obj.__class__, DeclarativeMeta): - # an SQLAlchemy class - fields = {} - for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' - and x != 'query' and x != 'query_class']: - data = obj.__getattribute__(field) - try: - json.dumps(data) # this will fail on non-encodable values, like other classes - fields[field] = data - except TypeError: - fields[field] = None - # a json-encodable dict - return fields - - if isinstance(obj, decimal.Decimal): - return str(obj) - - if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date): - return obj.isoformat() - - if isinstance(obj, uuid.UUID): - return str(obj) - - else: - if obj.__class__ == bytes: - try: - return pickle.load(obj) - except Exception: - return str(obj) - - return json.JSONEncoder.default(self, obj) - - -def DictDatetime(t): - dl = ['Y', 'm', 'd', 'H', 'M', 'S', 'f'] - if type(t) is datetime.datetime: - return {a: t.strftime('%{}'.format(a)) for a in dl} - elif type(t) is dict: - return datetime.datetime.strptime(''.join(t[a] for a in dl), '%Y%m%d%H%M%S%f') - - -def AlchemyFnCode(obj): - """JSON encoder function for SQLAlchemy special classes.""" - if isinstance(obj, datetime.date): - return obj.isoformat() - elif isinstance(obj, decimal.Decimal): - return float(obj) - - -def return_task(success, user, initial, logs, data, case_name, imported_files): - ret = { - 'success': success, - 'user': user, - 'initial': initial, - 'logs': logs, - 'data': data, - 'case_name': case_name, - 'imported_files': imported_files - } - return ret - - -def task_success(user=None, initial=None, logs=None, data=None, case_name=None, imported_files=None): - return return_task(True, user, initial, logs, data, case_name, imported_files) - - -def task_failure(user=None, initial=None, logs=None, data=None, case_name=None, imported_files=None): - return return_task(False, user, initial, logs, data, case_name, imported_files) - - class FileRemover(object): def __init__(self): self.weak_references = dict() # weak_ref -> filepath to remove @@ -198,223 +54,6 @@ def _do_cleanup(self, wr): shutil.rmtree(filepath, ignore_errors=True) -def log_exception_and_error(e): - log.exception(e) - log.error(traceback.print_exc()) - - -def update_current_case(caseid, restricted_access): - if session['current_case']['case_id'] != caseid: - case = get_case(caseid) - if case: - session['current_case'] = { - 'case_name': "{}".format(case.name), - 'case_info': "(#{} - {})".format(caseid, case.client.name), - 'case_id': caseid, - 'access': restricted_access - } - - -def get_urlcasename(): - caseid = request.args.get('cid', default=None, type=int) - if not caseid: - try: - caseid = current_user.ctx_case - except: - return ["", ""] - - case = Cases.query.filter(Cases.case_id == caseid).first() - - if case is None: - case_name = "CASE NOT FOUND" - case_info = "Error" - else: - case_name = "{}".format(case.name) - case_info = "(#{} - {})".format(caseid, - case.client.name) - - return [case_name, case_info, caseid] - - -def _local_authentication_process(incoming_request: Request): - return current_user.is_authenticated - - -def _authenticate_with_email(user_email): - user = get_user(user_email, id_key="email") - if not user: - log.error(f'User with email {user_email} is not registered in the IRIS') - return False - - login_user(user) - track_activity(f"User '{user.id}' successfully logged-in", ctx_less=True) - - caseid = user.ctx_case - session['permissions'] = ac_get_effective_permissions_of_user(user) - - if caseid is None: - case = Cases.query.order_by(Cases.case_id).first() - user.ctx_case = case.case_id - user.ctx_human_case = case.name - db.session.commit() - - session['current_case'] = { - 'case_name': user.ctx_human_case, - 'case_info': "", - 'case_id': user.ctx_case - } - - return True - - -def _oidc_proxy_authentication_process(incoming_request: Request): - # Get the OIDC JWT authentication token from the request header - authentication_token = incoming_request.headers.get('X-Forwarded-Access-Token', '') - - if app.config.get("AUTHENTICATION_TOKEN_VERIFY_MODE") == 'lazy': - user_email = incoming_request.headers.get('X-Email') - - if user_email: - return _authenticate_with_email(user_email.split(',')[0]) - - elif app.config.get("AUTHENTICATION_TOKEN_VERIFY_MODE") == 'introspection': - # Use the authentication server's token introspection endpoint in order to determine if the request is valid / - # authenticated. The TLS_ROOT_CA is used to validate the authentication server's certificate. - # The other solution was to skip the certificate verification, BUT as the authentication server might be - # located on another server, this check is necessary. - - introspection_body = {"token": authentication_token} - introspection = requests.post( - app.config.get("AUTHENTICATION_TOKEN_INTROSPECTION_URL"), - auth=HTTPBasicAuth(app.config.get('AUTHENTICATION_CLIENT_ID'), app.config.get('AUTHENTICATION_CLIENT_SECRET')), - data=introspection_body, - verify=app.config.get("TLS_ROOT_CA") - ) - if introspection.status_code == 200: - response_json = introspection.json() - - if response_json.get("active", False) is True: - user_email = response_json.get("sub") - return _authenticate_with_email(user_email=user_email) - - else: - log.info("USER IS NOT AUTHENTICATED") - return False - - elif app.config.get("AUTHENTICATION_TOKEN_VERIFY_MODE") == 'signature': - # Use the JWKS urls provided by the OIDC discovery to fetch the signing keys - # and check the signature of the token - try: - jwks_client = PyJWKClient(app.config.get("AUTHENTICATION_JWKS_URL")) - signing_key = jwks_client.get_signing_key_from_jwt(authentication_token) - - try: - - data = jwt.decode( - authentication_token, - signing_key.key, - algorithms=["RS256"], - audience=app.config.get("AUTHENTICATION_AUDIENCE"), - options={"verify_exp": app.config.get("AUTHENTICATION_VERIFY_TOKEN_EXP")}, - ) - - except jwt.ExpiredSignatureError: - log.error("Provided token has expired") - return False - - except Exception as e: - log.error(f"Error decoding JWT. {e.__str__()}") - return False - - # Extract the user email - user_email = data.get("sub") - - return _authenticate_with_email(user_email) - - else: - log.error("ERROR DURING TOKEN INTROSPECTION PROCESS") - return False - - -def not_authenticated_redirection_url(request_url: str): - redirection_mapper = { - "oidc_proxy": lambda: app.config.get("AUTHENTICATION_PROXY_LOGOUT_URL"), - "local": lambda: url_for('login.login', next=request_url), - "ldap": lambda: url_for('login.login', next=request_url), - "oidc": lambda: url_for('login.login', next=request_url,) - } - - return redirection_mapper.get(app.config.get("AUTHENTICATION_TYPE"))() - - -def is_user_authenticated(incoming_request: Request): - authentication_mapper = { - "oidc_proxy": _oidc_proxy_authentication_process, - "local": _local_authentication_process, - "ldap": _local_authentication_process, - "oidc": _local_authentication_process, - } - - return authentication_mapper.get(app.config.get("AUTHENTICATION_TYPE"))(incoming_request) - - -def is_authentication_local(): - return app.config.get("AUTHENTICATION_TYPE") == "local" - - -def is_authentication_ldap(): - return app.config.get('AUTHENTICATION_TYPE') == "ldap" - - -def is_authentication_oidc(): - return app.config.get('AUTHENTICATION_TYPE') == "oidc" - - -def regenerate_session(): - user_data = session.get('user_data', {}) - - session.clear() - - session['user_data'] = user_data - - session.modified = True - - -# TODO should move this method into an util file at the root of the blueprint namespace -def ac_api_return_access_denied(caseid: int = None): - error_uuid = uuid.uuid4() - log.warning(f"EID {error_uuid} - Access denied with case #{caseid} for user ID {current_user.id} " - f"accessing URI {request.full_path}") - data = { - 'user_id': current_user.id, - 'case_id': caseid, - 'error_uuid': error_uuid - } - return response_error('Permission denied', data=data, status=403) - - -def endpoint_removed(message, version): - def inner_wrap(f): - @wraps(f) - def wrap(*args, **kwargs): - return response_error(f"Endpoint deprecated in {version}. {message}.", status=410) - return wrap - return inner_wrap - - -def ac_api_requires_client_access(): - def inner_wrap(f): - @wraps(f) - def wrap(*args, **kwargs): - client_id = kwargs.get('client_id') - if not user_has_client_access(current_user.id, client_id): - return response_error("Permission denied", status=403) - - return f(*args, **kwargs) - return wrap - return inner_wrap - - def decompress_7z(filename: Path, output_dir): """ Decompress a 7z file in specified output directory @@ -468,16 +107,6 @@ def add_obj_history_entry(obj, action, commit=False): return obj -# Set basic 404 -@app.errorhandler(404) -def page_not_found(e): - # note that we set the 404 status explicitly - if request.content_type and 'application/json' in request.content_type: - return response_error("Resource not found", status=404) - - return render_template('pages/error-404.html', template_folder=TEMPLATE_PATH), 404 - - def file_sha256sum(file_path): if not Path(file_path).is_file(): diff --git a/source/app/views.py b/source/app/views.py index c8766997a..0874c7f66 100644 --- a/source/app/views.py +++ b/source/app/views.py @@ -99,6 +99,7 @@ from app.blueprints.graphql.graphql_route import graphql_blueprint from app.blueprints.rest.v2.case.api_v2_assets_routes import api_v2_assets_blueprint from app.blueprints.rest.v2.case.api_v2_ioc_routes import api_v2_ioc_blueprint +from app.blueprints.rest.v2.case.api_v2_case_tasks_routes import api_v2_tasks_blueprint from app.models.authorization import User from app.post_init import run_post_init @@ -186,6 +187,7 @@ app.register_blueprint(api_v2_ioc_blueprint) app.register_blueprint(api_v2_assets_blueprint) +app.register_blueprint(api_v2_tasks_blueprint) try: diff --git a/tests/iris.py b/tests/iris.py index dccd06e9c..c0982f5d1 100644 --- a/tests/iris.py +++ b/tests/iris.py @@ -28,6 +28,7 @@ _IRIS_PATH = Path('..') _TEST_DATA_PATH = Path('./data') _ADMINISTRATOR_USER_IDENTIFIER = 1 +_INITIAL_DEMO_CASE_IDENTIFIER = 1 class Iris: @@ -75,14 +76,17 @@ def execute_graphql_query(self, payload): def clear_database(self): cases = self.get('/api/v2/cases', query_parameters={'per_page': 1000000000}).json() - for case in cases['cases']: + for case in cases['data']: identifier = case['case_id'] + if identifier == _INITIAL_DEMO_CASE_IDENTIFIER: + continue self.delete(f'/api/v2/cases/{identifier}') groups = self.get('/manage/groups/list').json() for group in groups['data']: identifier = group['group_id'] self.create(f'/manage/groups/delete/{identifier}', {}) - customers = self.get('/manage/customers/list').json() - for customer in customers['data']: - identifier = customer['customer_id'] - self.create(f'/manage/customers/delete/{identifier}', {}) + users = self.get('/manage/users/list').json() + for user in users['data']: + identifier = user['user_id'] + self.get(f'/manage/users/deactivate/{identifier}') + self.create(f'/manage/users/delete/{identifier}', {}) diff --git a/tests/tests_rest_assets.py b/tests/tests_rest_assets.py index 02e23d570..8f9eb73d2 100644 --- a/tests/tests_rest_assets.py +++ b/tests/tests_rest_assets.py @@ -19,8 +19,7 @@ from unittest import TestCase from iris import Iris -# TODO should change None into 123456789 and maybe fix... -_IDENTIFIER_FOR_NONEXISTENT_OBJECT = None +_IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 class TestsRestAssets(TestCase): @@ -31,7 +30,6 @@ def setUp(self) -> None: def tearDown(self): self._subject.clear_database() - def test_delete_asset_should_return_204(self): case_identifier = self._subject.create_dummy_case() body = {'asset_type_id': '1', 'asset_name': 'admin_laptop_test'} diff --git a/tests/tests_rest_cases.py b/tests/tests_rest_cases.py index d689e8a9a..0126ea8a7 100644 --- a/tests/tests_rest_cases.py +++ b/tests/tests_rest_cases.py @@ -21,7 +21,7 @@ def _get_case_with_identifier(response, identifier): - for case in response['cases']: + for case in response['data']: if identifier == case['case_id']: return case raise ValueError('Case not found') @@ -69,10 +69,10 @@ def test_create_case_with_classification_id_should_set_classification_id(self): def test_create_case_should_add_a_new_case(self): response = self._subject.get('/api/v2/cases').json() - initial_case_count = len(response['cases']) + initial_case_count = len(response['data']) self._subject.create_dummy_case() response = self._subject.get('/api/v2/cases').json() - case_count = len(response['cases']) + case_count = len(response['data']) self.assertEqual(initial_case_count + 1, case_count) def test_get_case_should_return_case_data(self): @@ -129,7 +129,7 @@ def test_get_cases_should_filter_on_case_name(self): filters = {'case_name': 'test_get_cases_should_filter_on_case_name'} response = self._subject.get('/api/v2/cases', query_parameters=filters).json() identifiers = [] - for case in response['cases']: + for case in response['data']: identifiers.append(case['case_id']) self.assertIn(case_identifier, identifiers) @@ -139,7 +139,7 @@ def test_get_cases_should_filter_on_is_open(self): filters = {'is_open': 'true'} response = self._subject.get('/api/v2/cases', query_parameters=filters).json() identifiers = [] - for case in response['cases']: + for case in response['data']: identifiers.append(case['case_id']) self.assertNotIn(case_identifier, identifiers) diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index ac7c09f61..29d0c0882 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -20,6 +20,7 @@ from iris import Iris _PERMISSION_CUSTOMERS_WRITE = 0x80 +_IRIS_INITIAL_CLIENT_IDENTIFIER = 1 class TestsRestCustomers(TestCase): @@ -29,6 +30,15 @@ def setUp(self) -> None: def tearDown(self): self._subject.clear_database() + users = self._subject.get('/manage/users/list').json() + for user in users['data']: + identifier = user['user_id'] + body = {'customers_membership': [_IRIS_INITIAL_CLIENT_IDENTIFIER]} + self._subject.create(f'/manage/users/{identifier}/customers/update', body) + customers = self._subject.get('/manage/customers/list').json() + for customer in customers['data']: + identifier = customer['customer_id'] + self._subject.create(f'/manage/customers/delete/{identifier}', {}) def test_create_customer_should_return_200_when_user_has_customer_write_right(self): body = { diff --git a/tests/tests_rest_iocs.py b/tests/tests_rest_iocs.py index 519f9bce6..36a0c2bc3 100644 --- a/tests/tests_rest_iocs.py +++ b/tests/tests_rest_iocs.py @@ -19,8 +19,7 @@ from unittest import TestCase from iris import Iris -# TODO should change None into 123456789 and maybe fix... -_IDENTIFIER_FOR_NONEXISTENT_OBJECT = None +_IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 class TestsRestIocs(TestCase): @@ -35,7 +34,7 @@ def test_get_ioc_should_not_fail(self): response = self._subject.get('/case/ioc/list').json() self.assertEqual('success', response['status']) - def test_create_ioc_should_return_good_ioc_type_id(self): + def test_create_ioc_should_return_correct_ioc_type_id(self): case_identifier = self._subject.create_dummy_case() body = {'ioc_type_id': 1, 'ioc_tlp_id': 2, 'ioc_value': '8.8.8.8', 'ioc_description': 'rewrw', 'ioc_tags': ''} response = self._subject.create(f'/api/v2/cases/{case_identifier}/iocs', body).json() @@ -103,12 +102,12 @@ def test_get_iocs_should_filter_and_return_ioc_type_identifier(self): filters = {'ioc_value': 'test_get_iocs_should_filter_on_ioc_value'} response = self._subject.get(f'/api/v2/cases/{case_identifier}/iocs', query_parameters=filters).json() identifiers = [] - for ioc in response['iocs']: + for ioc in response['data']: identifiers.append(ioc['ioc_type_id']) self.assertIn(ioc_type_identifier, identifiers) def test_get_ioc_should_return_404_when_not_present(self): - response = self._subject.get(f'/api/v2/iocs/137') + response = self._subject.get(f'/api/v2/iocs/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') self.assertEqual(404, response.status_code) def test_get_ioc_should_return_200_on_success(self): @@ -125,7 +124,7 @@ def test_get_iocs_should_include_tlp_information(self): body = {'ioc_type_id': 1, 'ioc_tlp_id': tlp_identifier, 'ioc_value': '8.8.8.8', 'ioc_description': 'rewrw', 'ioc_tags': ''} self._subject.create(f'/api/v2/cases/{case_identifier}/iocs', body).json() response = self._subject.get(f'/api/v2/cases/{case_identifier}/iocs').json() - self.assertEqual(tlp_identifier, response['iocs'][0]['tlp']['tlp_id']) + self.assertEqual(tlp_identifier, response['data'][0]['tlp']['tlp_id']) def test_get_iocs_should_include_link_to_other_cases_with_same_value_type_ioc(self): case_identifier1 = self._subject.create_dummy_case() @@ -135,7 +134,7 @@ def test_get_iocs_should_include_link_to_other_cases_with_same_value_type_ioc(se body = {'ioc_type_id': 1, 'ioc_tlp_id': 1, 'ioc_value': '8.8.8.8', 'ioc_description': 'another', 'ioc_tags': ''} self._subject.create(f'/api/v2/cases/{case_identifier2}/iocs', body).json() response = self._subject.get(f'/api/v2/cases/{case_identifier2}/iocs').json() - self.assertEqual(case_identifier1, response['iocs'][0]['link'][0]['case_id']) + self.assertEqual(case_identifier1, response['data'][0]['link'][0]['case_id']) def test_create_ioc_should_include_field_link(self): case_identifier = self._subject.create_dummy_case() diff --git a/tests/tests_rest_tasks.py b/tests/tests_rest_tasks.py index 1f0184226..bb743f422 100644 --- a/tests/tests_rest_tasks.py +++ b/tests/tests_rest_tasks.py @@ -19,8 +19,7 @@ from unittest import TestCase from iris import Iris -# TODO should change None into 123456789 and maybe fix... -_IDENTIFIER_FOR_NONEXISTENT_OBJECT = None +_IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 class TestsRestTasks(TestCase): diff --git a/ui/src/pages/case.ioc.js b/ui/src/pages/case.ioc.js index d0cc9af03..12c82bc57 100644 --- a/ui/src/pages/case.ioc.js +++ b/ui/src/pages/case.ioc.js @@ -148,7 +148,7 @@ function get_case_ioc() { } Table.clear(); - Table.rows.add(data.iocs); + Table.rows.add(data.data); $('#ioc_table_wrapper').on('click', function(e){ if($('.popover').length>1) diff --git a/ui/src/pages/manage.customers.js b/ui/src/pages/manage.customers.js index 7a2031a3a..36413f91f 100644 --- a/ui/src/pages/manage.customers.js +++ b/ui/src/pages/manage.customers.js @@ -18,11 +18,13 @@ function add_customer() { has_error = ret[0].length > 0; attributes = ret[1]; - if (has_error){return false;} + if (has_error) { + return false; + } form['custom_attributes'] = attributes; - post_request_api('customers/add', JSON.stringify(form), true) + post_request_api('/manage/customers/add', JSON.stringify(form), true) .done((data) => { if(notify_auto_api(data)) { refresh_customer_table(); diff --git a/ui/src/pages/view.customers.js b/ui/src/pages/view.customers.js index 7eea74492..4e490fa01 100644 --- a/ui/src/pages/view.customers.js +++ b/ui/src/pages/view.customers.js @@ -288,7 +288,7 @@ $(document).ready(function() { // since there are no filters on this table, it should be OK json.recordsFiltered = json.total; - return json.cases; + return json.data; } } });