diff --git a/tests/data/basic.env b/.env.tests.model similarity index 100% rename from tests/data/basic.env rename to .env.tests.model diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acc75a319..57f4dceda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,12 +31,14 @@ jobs: uses: astral-sh/ruff-action@v3 with: args: check --output-format=github - - name: Check dependencies with import-linter + - name: Check import dependencies with import-linter run: | - python -m venv venv - source venv/bin/activate pip install import-linter PYTHONPATH=source lint-imports + - name: Looking for dead code with vulture + run: | + pip install vulture + vulture build-docker-db: name: Build docker db @@ -131,10 +133,8 @@ jobs: uses: actions/checkout@v4 - name: Start development server run: | - # Even though, we use --env-file option when running docker compose, this is still necessary, because the compose has a env_file attribute :( - # TODO should move basic.env file, which is in directory tests, up. It's used in several places. Maybe, rename it into dev.env - cp tests/data/basic.env .env - docker compose --file docker-compose.dev.yml --env-file tests/data/basic.env up --detach --wait + cp .env.tests.model .env + docker compose --file docker-compose.dev.yml up --detach --wait - name: Generate GraphQL documentation run: | npx spectaql@^3.0.2 source/spectaql/config.yml @@ -170,9 +170,7 @@ jobs: uses: actions/checkout@v4 - name: Start development server run: | - # Even though, we use --env-file option when running docker compose, this is still necessary, because the compose has a env_file attribute :( - # TODO should move basic.env file, which is in directory tests, up. It's used in several places. Maybe, rename it into dev.env - cp tests/data/basic.env .env + cp .env.tests.model .env docker compose --file docker-compose.dev.yml up --detach --wait - name: Inspect development server start failure if: ${{ failure() || cancelled() }} @@ -226,8 +224,7 @@ jobs: - name: Check out iris uses: actions/checkout@v4 - name: Set up .env file - # TODO should move basic.env file, which is in directory tests, up. It's used in several places. Maybe, rename it into dev.env? - run: cp tests/data/basic.env .env + run: cp .env.tests.model .env - name: Run tests working-directory: tests_database_migration run: | @@ -277,8 +274,7 @@ jobs: run: npx playwright install chromium firefox - name: Start development server run: | - # TODO should move basic.env file, which is in directory tests, up. It's used in several places. Maybe, rename it into dev.env - cp tests/data/basic.env .env + cp .env.tests.model .env docker compose --file docker-compose.dev.yml up --detach --wait - name: Run end to end tests working-directory: e2e diff --git a/.vulture.ignore b/.vulture.ignore new file mode 100644 index 000000000..9cf1360dc --- /dev/null +++ b/.vulture.ignore @@ -0,0 +1,454 @@ +########## Do not remove: used by package iris-module-interface (see https://github.com/dfir-iris/iris-module-interface, iris_interface/IrisModuleInterface.py) +EvidenceStorage +deregister_from_hook +get_mod_config_by_name +register_hook +######### Alembic variables +revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:14) +down_revision # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:15) +branch_labels # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:16) +depends_on # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:17) +downgrade # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/alembic/versions/00b43bc4e8ac_add_prevent_post_init_to_register_case_.py:28) +######### marshables +Meta # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:183) +load_instance # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:185) +include_fk # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:186) +exclude # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:271) +include_relationships # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2000) +sqla_session # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:695) +######### +ReverseProxied # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:44) +_.autoescape # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:99) +dropzone # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/__init__.py:115) +_.token_user # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/access_controls.py:361) +interfaces # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:53) +_.resolve_iocs # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:60) +_.resolve_case # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:71) +total_count # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:81) +_.resolve_total_count # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:84) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:91) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:101) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:124) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:129) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:138) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/cases.py:155) +_.resolve_cases # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:70) +_.resolve_case # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:78) +_.resolve_ioc # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:83) +ioc_create # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:92) +ioc_update # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:93) +ioc_delete # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:94) +case_create # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:96) +case_delete # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:97) +case_update # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/graphql_route.py:98) +total_count # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:50) +_.resolve_total_count # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:52) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:59) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:72) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:93) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:108) +Arguments # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:147) +_.mutate # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/iocs.py:152) +SlicedResult # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/graphql/sliced_result.py:19) +_.is_anonymous # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/iris_user.py:33) +activities_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/activities/activities_routes.py:35) +alerts_list_view_route # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/alerts/alerts_routes.py:41) +alert_comment_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/alerts/alerts_routes.py:61) +case_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:44) +add_asset_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:64) +asset_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:83) +_.render_kw # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:101) +case_comment_asset_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_assets_routes.py:116) +case_graph # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_graphs_routes.py:34) +case_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:46) +case_add_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:61) +case_view_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:76) +case_comment_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_ioc_routes.py:102) +case_notes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_notes_routes.py:37) +case_comment_note_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_notes_routes.py:55) +case_rfile # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_rfiles_routes.py:40) +case_edit_rfile_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_rfiles_routes.py:52) +case_add_rfile_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_rfiles_routes.py:68) +case_comment_evidence_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_rfiles_routes.py:77) +case_r # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_routes.py:49) +case_pipelines_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_routes.py:76) +groups_cac_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_routes.py:98) +case_md_helper # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_routes.py:110) +case_tasks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:44) +case_add_task_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:56) +case_task_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:72) +case_comment_task_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_tasks_routes.py:97) +case_timeline # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:49) +case_getgraph_page # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:61) +case_comment_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:70) +event_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:84) +case_filter_help_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:120) +case_add_event_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/case/case_timeline_routes.py:129) +add_gtask_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dashboard/dashboard_routes.py:77) +edit_gtask_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dashboard/dashboard_routes.py:93) +datastore_add_file_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:38) +datastore_add_multi_files_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:54) +datastore_filter_help_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:70) +datastore_update_file_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:79) +datastore_info_file_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/datastore/datastore_routes.py:103) +demo_landing # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/demo_landing/demo_landing.py:35) +dim_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/dim_tasks/dim_tasks.py:38) +_validate_local_login # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:77) +oidc_login # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:148) +oidc_authorise # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:172) +mfa_setup # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:274) +mfa_verify # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/login/login_routes.py:322) +manage_ac_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_access_control.py:36) +manage_ac_audit_user_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_access_control.py:47) +manage_ac_audit_users_page # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_access_control.py:59) +view_assets_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:38) +add_assets_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_assets_type_routes.py:59) +manage_attributes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_attributes_routes.py:36) +attributes_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_attributes_routes.py:47) +attributes_preview # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_attributes_routes.py:65) +update_case_classification_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:37) +add_case_classification_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_classification_routes.py:68) +update_case_state_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:37) +add_case_state_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_state.py:67) +manage_case_templates # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:36) +case_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:47) +add_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:87) +upload_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_case_templates_routes.py:127) +manage_index_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_cases_routes.py:51) +details_case_from_case_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_cases_routes.py:101) +manage_details_case # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_cases_routes.py:107) +add_case_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_cases_routes.py:113) +manage_customers # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:46) +view_customer_page # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:58) +customer_add_contact_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:76) +customer_edit_contact_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:88) +view_customer_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:110) +add_customers_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_customers_routes.py:130) +update_evidence_type_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:37) +add_evidence_type_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_evidence_types_route.py:67) +manage_groups_view_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:41) +manage_groups_add_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:60) +manage_groups_members_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:73) +manage_groups_cac_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_groups_routes.py:88) +view_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:35) +add_ioc_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_ioc_types_routes.py:55) +manage_modules_index # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:41) +add_module_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:52) +getmodule_param # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:64) +view_module # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_modules_routes.py:81) +manage_objects # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_objects_routes.py:33) +manage_settings # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_srv_settings_routes.py:38) +manage_report_templates # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_templates_routes.py:38) +add_template_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_templates_routes.py:51) +add_user_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:41) +view_user_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:55) +manage_user_group_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:80) +manage_user_customers_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:95) +manage_user_cac_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/manage/manage_users.py:112) +get_overview # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/overview/overview_routes.py:34) +user_settings # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/profile/profile_routes.py:35) +update_pwd_modal # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/profile/profile_routes.py:47) +search_file_get # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/blueprints/pages/search/search_routes.py:34) +_.note_creationdate # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/business/notes.py:46) +_.validate_config # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:52) +_.config_key_vault # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:59) +AuthenticationType # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:169) +local # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:170) +oidc_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:171) +result_backend # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:223) +broker_url # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:224) +result_extended # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:225) +result_serializer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:226) +worker_pool_restarts # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:227) +broker_connection_retry_on_startup # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:228) +CSRF_ENABLED # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:245) +SECRET_KEY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:247) +SECURITY_PASSWORD_SALT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:249) +SECURITY_LOGIN_USER_TEMPLATE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:251) +IRIS_ADM_EMAIL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:253) +IRIS_ADM_PASSWORD # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:254) +IRIS_ADM_USERNAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:255) +IRIS_ADM_API_KEY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:256) +SESSION_COOKIE_SAMESITE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:259) +SESSION_COOKIE_SECURE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:260) +SESSION_COOKIE_HTTPONLY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:261) +ACCESS_TOKEN_EXPIRES_MINUTES # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:263) +REFRESH_TOKEN_EXPIRES_DAYS # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:264) +PG_ACCOUNT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:266) +PG_PASSWD # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:267) +PGA_ACCOUNT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:268) +PGA_PASSWD # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:269) +PG_SERVER # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:270) +PG_PORT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:271) +PG_DB # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:272) +DB_RETRY_COUNT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:274) +DB_RETRY_DELAY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:275) +DEMO_DOMAIN # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:279) +DEMO_USERS_SEED # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:280) +DEMO_ADM_SEED # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:281) +MAX_CONTENT_LENGTH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:282) +IRIS_ALLOW_ORIGIN # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:288) +WTF_CSRF_TIME_LIMIT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:290) +SQLALCHEMY_TRACK_MODIFICATIONS # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:294) +SQLALCHEMY_DATABASE_URI # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:296) +SQLALCHEMY_BINDS # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:297) +SQALCHEMY_PIGGER_URI # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:301) +UPLOADED_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:308) +TEMPLATES_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:309) +UPDATES_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:311) +RELEASE_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:313) +RELEASE_SIGNATURE_KEY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:316) +PG_CLIENT_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:318) +ASSET_STORE_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:319) +DATASTORE_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:320) +ASSET_SHOW_PATH # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:321) +ORGANISATION_NAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:323) +LOGIN_BANNER_TEXT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:324) +LOGIN_PTFM_CONTACT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:325) +UPDATE_DIR_NAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:327) +DROPZONE_MAX_FILE_SIZE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:329) +DROPZONE_TIMEOUT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:331) +CELERY # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:336) +DEVELOPMENT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:339) +DEVELOPMENT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:341) +TLS_ROOT_CA # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:344) +IRIS_NEW_USERS_DEFAULT_GROUP # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:348) +AUTHENTICATION_ACCOUNT_SERVICE_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:353) +AUTHENTICATION_PROXY_LOGOUT_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:354) +AUTHENTICATION_TOKEN_INTROSPECTION_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:355) +AUTHENTICATION_JWKS_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:356) +AUTHENTICATION_CLIENT_ID # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:357) +AUTHENTICATION_CLIENT_SECRET # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:358) +AUTHENTICATION_AUDIENCE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:359) +AUTHENTICATION_VERIFY_TOKEN_EXP # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:360) +AUTHENTICATION_TOKEN_VERIFY_MODE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:362) +AUTHENTICATION_INIT_ADMINISTRATOR_EMAIL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:364) +AUTHENTICATION_APP_ADMIN_ROLE_NAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:366) +LDAP_AUTHENTICATION_TYPE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:385) +LDAP_ATTRIBUTE_DISPLAY_NAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:394) +LDAP_ATTRIBUTE_MAIL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:395) +LDAP_TLS_VERSION # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:408) +LDAP_TLS_VERSION # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:410) +LDAP_TLS_VERSION # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:412) +LDAP_CONNECT_STRING # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:415) +PRIVATE_KEY_PASSWORD # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:428) +OIDC_ISSUER_URL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:439) +OIDC_CLIENT_ID # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:440) +OIDC_CLIENT_SECRET # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:441) +OIDC_AUTH_ENDPOINT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:442) +OIDC_TOKEN_ENDPOINT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:443) +OIDC_END_SESSION_ENDPOINT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:444) +OIDC_SCOPES # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:445) +OIDC_MAPPING_USERNAME # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:446) +OIDC_MAPPING_EMAIL # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:447) +OIDC_MAPPING_USERGROUP # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:448) +OIDC_MAPPING_ROLES # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:449) +CACHE_TYPE # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:452) +CACHE_DEFAULT_TIMEOUT # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/configuration.py:453) +db_list_all_alerts # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:92) +add_alert # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:279) +_.alert_severity # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:307) +register_related_alerts # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:887) +delete_related_alert_cache # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:951) +details # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/alerts/alerts_db.py:1003) +get_assets_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_assets_db.py:130) +update_asset # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_assets_db.py:148) +get_case_summary # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_db.py:50) +case_set_desc_crc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_db.py:111) +case_name_exists # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_db.py:183) +update_ioc # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_iocs_db.py:65) +add_ioc_type # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_iocs_db.py:176) +update_note # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:124) +add_note # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:171) +_.note_creationdate # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:174) +get_groups_short # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:189) +get_groups_detail # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:224) +add_note_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:272) +_.group_user # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:276) +delete_note_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:293) +update_note_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:337) +find_pattern_in_notes # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:354) +get_case_notes_comments_count # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/case/case_notes_db.py:386) +get_case_client # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/client/client_db.py:209) +ctx_get_user_cases # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/context/context_db.py:31) +update_gtask_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:131) +update_utask_status # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/dashboard/dashboard_db.py:147) +_.is_evidence_registered # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:24) +_.add_evidence # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/evidence_storage.py:32) +_.added_by_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/modules_db.py:54) +get_module_config_from_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/modules_db.py:160) +module_list_available_hooks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/iris_engine/modules_db.py:230) +manage_ac_audit_users_db # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_access_control_db.py:36) +get_client_users # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_access_control_db.py:126) +add_tab_attribute # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_attribute_db.py:120) +add_tab_attribute_field # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_attribute_db.py:139) +_.note_creationdate # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_case_templates_db.py:232) +list_cases_dict_unrestricted # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_cases_db.py:85) +get_evidence_type_by_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_evidence_types_db.py:70) +search_evidence_type_by_name # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_evidence_types_db.py:83) +remove_case_access_from_group # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_groups_db.py:283) +list_users_id # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:69) +_.allow_alerts # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:151) +_.allow_alerts # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:175) +update_user_orgs # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:189) +change_user_primary_org # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:234) +remove_case_access_from_user # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:426) +get_users_id_view_from_user_id # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:567) +get_users_list_user_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:585) +get_users_list_restricted_user_view # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/manage/manage_users_db.py:601) +_.get_underlying_exception # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/persistence_error.py:25) +export_case_json_extended # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/reporter/report_db.py:51) +_.object_updated_by_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/states.py:49) +_.object_updated_by_id # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/datamgmt/states.py:57) +_.load_css # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/flask_dropzone/__init__.py:140) +_.load_js # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/flask_dropzone/__init__.py:163) +_.style # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/flask_dropzone/__init__.py:368) +random_filename # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/flask_dropzone/utils.py:34) +RegisterForm # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:45) +MultiCheckboxField # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:63) +widget # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:64) +option_widget # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:65) +report_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:105) +report_name_format # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:106) +AddOrganisationForm # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:128) +org_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:130) +org_url # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:131) +org_logo # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:132) +org_email # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:133) +org_nationality # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:134) +org_sector # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:135) +org_type # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:136) +case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:148) +CaseNoteForm # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/forms.py:223) +ac_get_effective_permissions_from_groups # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/access_control/utils.py:138) +check_module_compatibility # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:46) +_.wait_till_return # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:372) +_.max_retry # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/module_handler/module_handler.py:374) +on_task_init # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/tasker/tasks.py:33) +chunks # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/tasker/tasks.py:92) +notify_server_ready_to_reboot # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:70) +notify_server_has_updated # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:74) +inner_init_server_update # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:78) +updates_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:79) +get_release_assets # unused function (/home/ubuntu/code/dfir-iris/iris-web/source/app/iris_engine/updater/updater.py:117) + +alert_uuid # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:50) +alert_source_link # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:56) +alert_context # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:60) +alert_note # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:63) +severity_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:90) +resolution_status_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:106) +similarity_type # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:144) +matching_asset_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:145) +matching_ioc_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:146) +matching_asset # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:150) +matching_ioc # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/alerts.py:151) +org_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:75) +org_url # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:76) +org_logo # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:77) +org_email # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:78) +org_nationality # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:79) +org_sector # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:80) +org_type # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:81) +allow_alerts # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:191) +ctx_human_case # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:213) +has_mini_sidebar # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:218) +has_deletion_confirmation # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/authorization.py:219) +closing_note # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/cases.py:61) +reviewer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/cases.py:80) +tag_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/cases.py:170) +children # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/cases.py:204) +not_applicable # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:57) +to_be_determined # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:71) +last_update_date # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:130) +asset_enrichment # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:172) +created_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:199) +created_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:211) +created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:221) +contact_uuid # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:250) +object_updated_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:294) +updated_by # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:300) +source_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:338) +dest_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:339) +created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:367) +path_uuid # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:391) +file_date_added # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:408) +added_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:416) +data_parent # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:422) +ioc_asset_link_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:439) +asset_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:460) +note_creationdate # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:474) +group_user # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:521) +link_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:533) +kanban_data # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:547) +acquisition_date # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:559) +chain_of_custody # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:569) +user_open # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:606) +user_close # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:607) +user_update # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:608) +tag_creation_date # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:617) +_.tag_creation_date # unused attribute (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:625) +user_open # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:671) +user_close # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:672) +user_update # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:673) +https_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:698) +http_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:699) +enforce_mfa # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:709) +added_by_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:717) +retry_on_fail # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:749) +max_retry # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:750) +wait_till_return # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:752) +filter_name # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:806) +filter_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:807) +filter_data # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:808) +worker # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:835) +retries # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:836) +queue # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:837) +_._register_modules_pipelines # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/post_init.py:1459) + +user_roles_str # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:257) +user_isadmin # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:262) +user_primary_organisation_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:264) +asset_enrichment # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:687) +created_by_user_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:800) +event_comments_map # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1157) +children # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1159) +http_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1476) +https_proxy # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1477) +case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1602) +log_content # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1864) +group_permissions_list # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2050) +org_description # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2127) +has_deletion_confirmation # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2179) +case_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2399) +reviewer # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2410) +user_roles_str # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2522) +user_api_key # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2528) +user_isadmin # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2530) +user_groups # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2533) +user_customers # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2535) +user_organisations # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2537) +user_primary_organisation_id # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2538) + +######### Arguments of fields.Method/ma.Method +_.get_user_primary_organisation # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2548) +_.get_link # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:944) +_.get_event_category_id # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:1294) +_.get_status_name # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2485) +_.get_protagonists # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2488) + +######### Vulture false positive? Investigate and open issues in vulture? +POSTGRES_BIGINT_MAX # unused variable (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:106) +_.validate_string_or_list_of_dict # unused method (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:840) + +######### Dead code which needs to be removed? +CaseGraphAssets # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:321) +CaseGraphLinks # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:333) +CasesAssetsExt # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:454) +CaseKanban # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:543) +IrisReport # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/models/models.py:758) +CaseAddNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:509) +CaseGroupNoteSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:579) +AuthorizationOrganisationSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2119) +ModuleHooksSchema # unused class (/home/ubuntu/code/dfir-iris/iris-web/source/app/schema/marshables.py:2338) + diff --git a/pyproject.toml b/pyproject.toml index f4a5197dd..7e96309fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,12 @@ [tool.ruff.lint] preview = true -select = ["E101", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "PLR0402", "RET506", "TID252", "UP032", "W29"] +select = ["ARG003", "ARG005", "B00", "E101", "E20", "E225", "E23", "E24", "E3", "E4", "E7", "E9", "F", "FURB142", "FURB145", "FURB148", "PLR0402", "RET506", "RUF029", "RUF100", "TID252", "UP032", "W29", "W391"] ignore = ["E402", "E711", "E712", "E721", "E722"] +[tool.vulture] +paths = ["source/app", ".vulture.ignore"] +ignore_decorators = ["@*.route", "@app.*", "@*.post", "@*.get", "@*.put", "@*.delete", "@pre_load", "@post_load"] + [tool.importlinter] root_package = "app" include_external_packages = true @@ -24,8 +28,8 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the business layer" type = "forbidden" -source_modules = ["app.business.access_controls", "app.business.assets"] -forbidden_modules = "app.blueprints.iris_user" +source_modules = ["app.business.access_controls", "app.business.assets", "app.business.cases", "app.business.alerts"] +forbidden_modules = "app.blueprints" allow_indirect_imports = true [[tool.importlinter.contracts]] @@ -36,9 +40,44 @@ forbidden_modules = "sqlalchemy" allow_indirect_imports = true [[tool.importlinter.contracts]] -name = "Do not import API layer from the persistence layer" +name = "Do not import API layer from the persistence layer (access_controls)" +type = "forbidden" +source_modules = "app.datamgmt" +forbidden_modules = "app.blueprints.access_controls" +allow_indirect_imports = true + +[[tool.importlinter.contracts]] +name = "Do not import API layer from the persistence layer (iris_user)" type = "forbidden" -source_modules = "app.datamgmt.dashboard" +source_modules = "app.datamgmt.case.case_assets_db" forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true +[[tool.importlinter.contracts]] +name = "Do not import marshables from the persistence layer" +type = "forbidden" +source_modules = ["app.datamgmt.manage.manage_case_state_db", "app.datamgmt.manage.manage_groups_db"] +forbidden_modules = "app.schema.marshables" +allow_indirect_imports = true + +[[tool.importlinter.contracts]] +name = "Do not import marshmallow from the persistence layer" +type = "forbidden" +source_modules = "app.datamgmt.client" +forbidden_modules = "marshmallow" +allow_indirect_imports = true + +[[tool.importlinter.contracts]] +name = "Do not import the engine from the persistence layer" +type = "forbidden" +source_modules = "app.datamgmt.case" +forbidden_modules = "app.iris_engine" +allow_indirect_imports = true + +[[tool.importlinter.contracts]] +name = "Do not import the persistence layer from the models" +type = "forbidden" +source_modules = "app.models.cases" +forbidden_modules = "app.datamgmt" +allow_indirect_imports = true + diff --git a/scripts/gunicorn-cfg.py b/scripts/gunicorn-cfg.py index 9d0b61096..565826fa4 100644 --- a/scripts/gunicorn-cfg.py +++ b/scripts/gunicorn-cfg.py @@ -27,4 +27,3 @@ def worker_exit(server, worker): sys.exit(4) - diff --git a/source/app/__init__.py b/source/app/__init__.py index eec3917fd..42c86a0ae 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -16,10 +16,9 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import collections -import json import os -from flask import Flask, g +from flask import Flask +from flask import g from flask import session from flask_bcrypt import Bcrypt from flask_caching import Cache @@ -27,9 +26,8 @@ from flask_login import LoginManager from flask_marshmallow import Marshmallow -from flask_socketio import SocketIO, Namespace -from flask_sqlalchemy import SQLAlchemy -from functools import partial +from flask_socketio import SocketIO +from flask_socketio import Namespace from werkzeug.middleware.proxy_fix import ProxyFix @@ -39,6 +37,8 @@ from app.iris_engine.tasker.celery import set_celery_flask_context from app.iris_engine.access_control.oidc_handler import get_oidc_client from app.jinja_filters import register_jinja_filters +from app.models.authorization import ac_flag_match_mask +from app.db import db class ReverseProxied(object): @@ -59,12 +59,6 @@ class AlertsNamespace(Namespace): APP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TEMPLATE_PATH = os.path.join(APP_PATH, 'templates/') -SQLALCHEMY_ENGINE_OPTIONS = { - "json_deserializer": partial(json.loads, object_pairs_hook=collections.OrderedDict), - "pool_pre_ping": True -} - -db = SQLAlchemy(engine_options=SQLALCHEMY_ENGINE_OPTIONS) # flask-sqlalchemy bc = Bcrypt() # flask-bcrypt ma = Marshmallow() celery = make_celery(__name__) @@ -75,10 +69,13 @@ def ac_current_user_has_permission(*permissions): """ Return True if current user has permission """ + if 'permissions' not in session: + return False + + current_user_permissions = session['permissions'] for permission in permissions: - if ('permissions' in session and - session['permissions'] & permission.value == permission.value): + if ac_flag_match_mask(current_user_permissions, permission.value): return True return False diff --git a/source/app/alembic/versions/10a7616f3cc7_add_module_types.py b/source/app/alembic/versions/10a7616f3cc7_add_module_types.py index f274ebabf..acad25b8a 100644 --- a/source/app/alembic/versions/10a7616f3cc7_add_module_types.py +++ b/source/app/alembic/versions/10a7616f3cc7_add_module_types.py @@ -42,4 +42,3 @@ def upgrade(): def downgrade(): pass - diff --git a/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py b/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py index 7d711ae9d..f5c08402e 100644 --- a/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py +++ b/source/app/alembic/versions/4ecdfcb34f7c_add_compromise_status_to_assets.py @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. from app.alembic.alembic_utils import _table_has_column -from app.models.models import CompromiseStatus +from app.models.assets import CompromiseStatus revision = '4ecdfcb34f7c' down_revision = 'a929ef458490' diff --git a/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py b/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py index a39f7adf2..837bf8cc4 100644 --- a/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py +++ b/source/app/alembic/versions/ad4e0cd17597_add_ioctype_validation.py @@ -93,4 +93,3 @@ def upgrade(): def downgrade(): op.drop_column('ioc_type', 'type_validation_regex') op.drop_column('ioc_type', 'type_validation_expect') - diff --git a/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py b/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py index 69907fff9..d1a0a566f 100644 --- a/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py +++ b/source/app/alembic/versions/cd519d2d24df_password_policy_edition.py @@ -57,4 +57,3 @@ def upgrade(): def downgrade(): pass - diff --git a/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py b/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py index 2da92b175..a2010b580 100644 --- a/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py +++ b/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py @@ -61,4 +61,3 @@ def downgrade(): # Drop AlertSimilarity table op.drop_table('alert_similarity') - diff --git a/source/app/blueprints/access_controls.py b/source/app/blueprints/access_controls.py index f8495f564..98a565e1b 100644 --- a/source/app/blueprints/access_controls.py +++ b/source/app/blueprints/access_controls.py @@ -45,13 +45,14 @@ from app.business.auth import validate_auth_token from app.business.auth import update_session_current_case from app.datamgmt.case.case_db import get_case -from app.datamgmt.manage.manage_access_control_db import user_has_client_access +from app.business.access_controls import access_controls_user_has_customer_access from app.datamgmt.manage.manage_users_db import get_user from app.blueprints.iris_user import iris_current_user from app.business.access_controls 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.authorization import Permissions +from app.models.authorization import ac_flag_match_mask from app.models.authorization import CaseAccessLevel @@ -388,7 +389,7 @@ def inner_wrap(f): @wraps(f) def wrap(*args, **kwargs): client_id = kwargs.get('client_id') - if not user_has_client_access(iris_current_user.id, client_id): + if not ac_current_user_has_customer_access(client_id): return _ac_return_access_denied() return f(*args, **kwargs) @@ -438,7 +439,7 @@ def inner_wrap(f): @wraps(f) def wrap(*args, **kwargs): client_id = kwargs.get('client_id') - if not user_has_client_access(iris_current_user.id, client_id): + if not ac_current_user_has_customer_access(client_id): return response_error("Permission denied", status=403) return f(*args, **kwargs) @@ -582,3 +583,14 @@ def is_authentication_ldap(): def ac_fast_check_current_user_has_case_access(cid, access_level): return ac_fast_check_user_has_case_access(iris_current_user.id, cid, access_level) + + +def ac_current_user_has_permission(permission): + """ + Return True if current user has permission + """ + return ac_flag_match_mask(session['permissions'], permission.value) + + +def ac_current_user_has_customer_access(customer_identifier): + return access_controls_user_has_customer_access(iris_current_user, session['permissions'], customer_identifier) diff --git a/source/app/blueprints/graphql/cases.py b/source/app/blueprints/graphql/cases.py index 83d569f7e..255b7e718 100644 --- a/source/app/blueprints/graphql/cases.py +++ b/source/app/blueprints/graphql/cases.py @@ -27,6 +27,7 @@ from graphene import Float from graphene import String +from app.blueprints.access_controls import ac_current_user_has_customer_access from app.models.cases import Cases from app.models.authorization import Permissions from app.models.authorization import CaseAccessLevel @@ -36,14 +37,12 @@ from app.business.cases import cases_delete from app.business.cases import cases_update from app.business.cases import cases_get_by_identifier -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.blueprints.graphql.permissions import permissions_check_current_user_has_some_permission from app.blueprints.graphql.permissions import permissions_check_current_user_has_some_case_access from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook from app.schema.marshables import CaseSchema from app.blueprints.iris_user import iris_current_user -from app.datamgmt.manage.manage_access_control_db import user_has_client_access - from app.blueprints.graphql.iocs import IOCConnection @@ -111,11 +110,11 @@ def mutate(root, info, name, description, client_id, soc_id=None, classification if classification_id: request['classification_id'] = classification_id - request_data = call_deprecated_on_preload_modules_hook('case_create', request, None) + request_data = call_deprecated_on_preload_modules_hook('case_create', request) schema = CaseSchema() case = schema.load(request_data) case_template_id = request_data.pop('case_template_id', None) - result = cases_create(case, case_template_id) + result = cases_create(iris_current_user, case, case_template_id) return CaseCreate(case=result) @@ -185,7 +184,7 @@ def mutate(root, info, case_id, name=None, soc_id=None, classification_id=None, # If user tries to update the customer, check if the user has access to the new customer if request.get('case_customer') and request.get('case_customer') != case.client_id: - if not user_has_client_access(iris_current_user.id, request.get('case_customer')): + if not ac_current_user_has_customer_access(request.get('case_customer')): raise BusinessProcessingError('Invalid customer ID. Permission denied.') if 'case_name' in request: diff --git a/source/app/blueprints/graphql/sliced_result.py b/source/app/blueprints/graphql/sliced_result.py index dc5e8ab78..bdd3933e0 100644 --- a/source/app/blueprints/graphql/sliced_result.py +++ b/source/app/blueprints/graphql/sliced_result.py @@ -29,4 +29,3 @@ def __getitem__(self, index: slice) -> any: def __len__(self) -> int: return self._total - diff --git a/source/app/blueprints/pages/alerts/alerts_routes.py b/source/app/blueprints/pages/alerts/alerts_routes.py index 1689eeb06..315df4a8f 100644 --- a/source/app/blueprints/pages/alerts/alerts_routes.py +++ b/source/app/blueprints/pages/alerts/alerts_routes.py @@ -25,11 +25,9 @@ from werkzeug import Response 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.blueprints.responses import response_error -from app.blueprints.access_controls import ac_requires -from app.blueprints.iris_user import iris_current_user +from app.blueprints.access_controls import ac_requires, ac_current_user_has_customer_access alerts_blueprint = Blueprint( 'alerts', @@ -78,7 +76,7 @@ def alert_comment_modal(cur_id, caseid, url_redir): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) return render_template("modal_conversation.html", element_id=cur_id, element_type='alerts', diff --git a/source/app/blueprints/pages/case/case_ioc_routes.py b/source/app/blueprints/pages/case/case_ioc_routes.py index 88290be23..1c4013985 100644 --- a/source/app/blueprints/pages/case/case_ioc_routes.py +++ b/source/app/blueprints/pages/case/case_ioc_routes.py @@ -22,7 +22,7 @@ from flask import url_for from app.business.iocs import iocs_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.assets_type import get_assets_types from app.datamgmt.case.case_db import get_case from app.datamgmt.case.case_iocs_db import get_case_iocs_comments_count diff --git a/source/app/blueprints/pages/case/case_routes.py b/source/app/blueprints/pages/case/case_routes.py index 4c4b1f051..706700994 100644 --- a/source/app/blueprints/pages/case/case_routes.py +++ b/source/app/blueprints/pages/case/case_routes.py @@ -32,7 +32,7 @@ from app.forms import PipelinesCaseForm from app.iris_engine.access_control.utils import ac_get_all_access_level from app.iris_engine.module_handler.module_handler import list_available_pipelines -from app.models.models import CaseStatus +from app.models.cases import CaseStatus from app.models.authorization import CaseAccessLevel from app.blueprints.access_controls import ac_case_requires diff --git a/source/app/blueprints/pages/dashboard/dashboard_routes.py b/source/app/blueprints/pages/dashboard/dashboard_routes.py index 7887726fa..16079ad93 100644 --- a/source/app/blueprints/pages/dashboard/dashboard_routes.py +++ b/source/app/blueprints/pages/dashboard/dashboard_routes.py @@ -24,7 +24,7 @@ from app import app from app.blueprints.iris_user import iris_current_user -from app.datamgmt.dashboard.dashboard_db import get_tasks_status +from app.datamgmt.case.case_tasks_db import get_tasks_status from app.forms import CaseGlobalTaskForm from app.iris_engine.access_control.utils import ac_get_user_case_counts from app.models.authorization import User diff --git a/source/app/blueprints/pages/login/login_routes.py b/source/app/blueprints/pages/login/login_routes.py index e7ab3fb83..45991a093 100644 --- a/source/app/blueprints/pages/login/login_routes.py +++ b/source/app/blueprints/pages/login/login_routes.py @@ -34,7 +34,7 @@ from app import app from app import bc -from app import db +from app.db import db from app import oidc_client from app.blueprints.access_controls import is_authentication_oidc from app.blueprints.access_controls import is_authentication_ldap 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 ac142f993..185527940 100644 --- a/source/app/blueprints/pages/manage/manage_assets_type_routes.py +++ b/source/app/blueprints/pages/manage/manage_assets_type_routes.py @@ -26,7 +26,7 @@ from app import app from app.forms import AddAssetForm from app.models.authorization import Permissions -from app.models.models import AssetsType +from app.models.assets import AssetsType from app.blueprints.access_controls import ac_requires from app.blueprints.responses import response_error diff --git a/source/app/blueprints/pages/manage/manage_cases_routes.py b/source/app/blueprints/pages/manage/manage_cases_routes.py index c5b3a691d..45de533a8 100644 --- a/source/app/blueprints/pages/manage/manage_cases_routes.py +++ b/source/app/blueprints/pages/manage/manage_cases_routes.py @@ -34,13 +34,15 @@ from app.datamgmt.manage.manage_cases_db import get_case_protagonists from app.datamgmt.manage.manage_common import get_severities_list from app.forms import AddCaseForm -from app.iris_engine.access_control.utils import ac_current_user_has_permission from app.models.authorization import CaseAccessLevel from app.models.authorization import Permissions from app.schema.marshables import CaseDetailsSchema -from app.blueprints.access_controls import ac_api_return_access_denied, ac_fast_check_current_user_has_case_access +from app.blueprints.access_controls import ac_api_return_access_denied +from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access +from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.access_controls import ac_requires from app.blueprints.responses import response_error +from app.schema.marshables import CaseStateSchema manage_cases_blueprint = Blueprint('manage_case', __name__, @@ -81,18 +83,18 @@ def _details_case(cur_id: int, caseid: int, url_redir: bool) -> Union[str, Respo case_classifications = get_case_classifications_list() case_states = get_case_states_list() + dumped_case_states = CaseStateSchema(many=True).dump(case_states) user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) - customers = get_client_list(current_user_id=iris_current_user.id, - is_server_administrator=user_is_server_administrator) + customers = get_client_list(iris_current_user.id, user_is_server_administrator) severities = get_severities_list() protagonists = [r._asdict() for r in get_case_protagonists(cur_id)] form = FlaskForm() - return render_template("modal_case_info_from_case.html", data=res, form=form, protagonists=protagonists, - case_classifications=case_classifications, case_states=case_states, customers=customers, + return render_template('modal_case_info_from_case.html', data=res, form=form, protagonists=protagonists, + case_classifications=case_classifications, case_states=dumped_case_states, customers=customers, severities=severities) @@ -117,9 +119,7 @@ def add_case_modal(caseid: int, url_redir: bool): form = AddCaseForm() # Show only clients that the user has access to - client_list = get_client_list(current_user_id=iris_current_user.id, - is_server_administrator=ac_current_user_has_permission( - Permissions.server_administrator)) + client_list = get_client_list(iris_current_user.id, ac_current_user_has_permission(Permissions.server_administrator)) form.case_customer_id.choices = [(c['customer_id'], c['customer_name']) for c in client_list] diff --git a/source/app/blueprints/pages/manage/manage_customers_routes.py b/source/app/blueprints/pages/manage/manage_customers_routes.py index 695b6c27e..03464f052 100644 --- a/source/app/blueprints/pages/manage/manage_customers_routes.py +++ b/source/app/blueprints/pages/manage/manage_customers_routes.py @@ -22,7 +22,7 @@ from flask import url_for from flask_wtf import FlaskForm -from app.datamgmt.client.client_db import get_client +from app.datamgmt.client.client_db import get_customer from app.datamgmt.client.client_db import get_client_api from app.datamgmt.client.client_db import get_client_contact from app.datamgmt.client.client_db import get_client_contacts @@ -92,7 +92,7 @@ def customer_edit_contact_modal(client_id, contact_id, caseid, url_redir): if url_redir: return redirect(url_for('manage_customers.manage_customers', cid=caseid)) - contact = get_client_contact(client_id, contact_id) + contact = get_client_contact(contact_id) if not contact: return response_error(f"Invalid Contact ID {contact_id}") @@ -115,7 +115,7 @@ def view_customer_modal(client_id, caseid, url_redir): return redirect(url_for('manage_customers.manage_customers', cid=caseid)) form = AddCustomerForm() - customer = get_client(client_id) + customer = get_customer(client_id) if not customer: return response_error("Invalid Customer ID") diff --git a/source/app/blueprints/pages/manage/manage_users.py b/source/app/blueprints/pages/manage/manage_users.py index f73103bf3..4d18141f1 100644 --- a/source/app/blueprints/pages/manage/manage_users.py +++ b/source/app/blueprints/pages/manage/manage_users.py @@ -30,9 +30,8 @@ from app.datamgmt.manage.manage_users_db import get_user_effective_permissions from app.forms import AddUserForm from app.iris_engine.access_control.utils import ac_get_all_access_level -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.blueprints.access_controls import ac_requires, ac_current_user_has_permission from app.blueprints.responses import response_error manage_users_blueprint = Blueprint('manage_users', __name__, template_folder='templates') @@ -103,8 +102,7 @@ def manage_user_customers_modal(cur_id, caseid, url_redir): return response_error("Invalid user ID") user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) - groups = get_client_list(current_user_id=iris_current_user.id, - is_server_administrator=user_is_server_administrator) + groups = get_client_list(iris_current_user.id, user_is_server_administrator) return render_template("modal_manage_user_customers.html", groups=groups, user=user) diff --git a/source/app/blueprints/rest/activities_routes.py b/source/app/blueprints/rest/activities_routes.py index 1b8fc4c5a..b9ed70a71 100644 --- a/source/app/blueprints/rest/activities_routes.py +++ b/source/app/blueprints/rest/activities_routes.py @@ -27,7 +27,7 @@ activities_rest_blueprint = Blueprint('activities_rest', __name__) -@activities_rest_blueprint.route('/activities/list', methods=['GET']) +@activities_rest_blueprint.get('/activities/list') @ac_api_requires(Permissions.activities_read, Permissions.all_activities_read) def list_activities(): # Get User activities from database diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 87ee0e236..7d7276828 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -24,7 +24,7 @@ from typing import List from werkzeug import Response -from app import db +from app.db import db from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.rest.parsing import parse_comma_separated_identifiers from app.blueprints.rest.case_comments import case_comment_update @@ -45,7 +45,6 @@ from app.datamgmt.alerts.alerts_db import create_case_from_alerts from app.datamgmt.case.case_db import get_case from app.datamgmt.manage.manage_access_control_db import check_ua_case_client -from app.datamgmt.manage.manage_access_control_db import user_has_client_access from app.iris_engine.access_control.utils import ac_set_new_case_access from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity @@ -57,10 +56,12 @@ from app.schema.marshables import IocSchema from app.schema.marshables import CommentSchema from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_customer_access +from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.responses import response_error from app.util import add_obj_history_entry from app.blueprints.responses import response_success -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.alerts import alerts_create alerts_rest_blueprint = Blueprint('alerts_rest', __name__) @@ -137,31 +138,35 @@ def alerts_list_route() -> Response: fields = None try: + user_identifier_filter = iris_current_user.id + if ac_current_user_has_permission(Permissions.server_administrator): + user_identifier_filter = None + filtered_alerts = get_filtered_alerts( - start_date=request.args.get('creation_start_date'), - end_date=request.args.get('creation_end_date'), - source_start_date=request.args.get('source_start_date'), - source_end_date=request.args.get('source_end_date'), - source_reference=request.args.get('source_reference'), - title=request.args.get('alert_title'), - description=request.args.get('alert_description'), - status=request.args.get('alert_status_id', type=int), - severity=request.args.get('alert_severity_id', type=int), - owner=request.args.get('alert_owner_id', type=int), - source=request.args.get('alert_source'), - tags=request.args.get('alert_tags'), - classification=request.args.get('alert_classification_id', type=int), - client=request.args.get('alert_customer_id'), - case_id=request.args.get('case_id', type=int), - alert_ids=alert_ids, - page=page, - per_page=per_page, - sort=request.args.get('sort', 'desc', type=str), - custom_conditions=request.args.get('custom_conditions'), - assets=alert_assets, - iocs=alert_iocs, - resolution_status=request.args.get('alert_resolution_id', type=int), - current_user_id=iris_current_user.id + request.args.get('creation_start_date'), + request.args.get('creation_end_date'), + request.args.get('source_start_date'), + request.args.get('source_end_date'), + request.args.get('alert_title'), + request.args.get('alert_description'), + request.args.get('alert_status_id', type=int), + request.args.get('alert_severity_id', type=int), + request.args.get('alert_owner_id', type=int), + request.args.get('alert_source'), + request.args.get('alert_tags'), + request.args.get('case_id', type=int), + request.args.get('alert_customer_id', type=int), + request.args.get('alert_classification_id', type=int), + alert_ids, + alert_assets, + alert_iocs, + request.args.get('alert_resolution_id', type=int), + page, + per_page, + request.args.get('sort', 'desc', type=str), + user_identifier_filter, + request.args.get('source_reference'), + request.args.get('custom_conditions') ) except Exception as e: @@ -210,7 +215,7 @@ def alerts_add_route() -> Response: alert = _load(request_data) result = alerts_create(alert, iocs, assets) - if not user_has_client_access(iris_current_user.id, result.alert_customer_id): + if not ac_current_user_has_customer_access(result.alert_customer_id): return response_error('User not entitled to create alerts for the client') alert_schema = AlertSchema() return response_success('Alert added', data=alert_schema.dump(result)) @@ -244,7 +249,7 @@ def alerts_get_route(alert_id) -> Response: # Return the alert as JSON if alert is None: return response_error('Alert not found') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('Alert not found') alert_dump = alert_schema.dump(alert) @@ -276,7 +281,7 @@ def alerts_similarities_route(alert_id) -> Response: # Return the alert as JSON if alert is None: return response_error('Alert not found') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('Alert not found') open_alerts = request.args.get('open-alerts', 'false').lower() == 'true' @@ -320,7 +325,7 @@ def alerts_update_route(alert_id) -> Response: alert = get_alert_by_id(alert_id) if not alert: return response_error('Alert not found') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) alert_schema = AlertSchema() @@ -364,13 +369,13 @@ def alerts_update_route(alert_id) -> Response: # Save the changes db.session.commit() - updated_alert = call_modules_hook('on_postload_alert_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_update', updated_alert) if do_resolution_hook: - updated_alert = call_modules_hook('on_postload_alert_resolution_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_resolution_update', updated_alert) if do_status_hook: - updated_alert = call_modules_hook('on_postload_alert_status_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_status_update', updated_alert) if activity_data: activity_data_as_string = ','.join(activity_data) @@ -436,10 +441,10 @@ def alerts_batch_update_route() -> Response: activity_data.append(f"\"{key}\"") # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to update alerts for the client', status=403) - if getattr(alert, 'alert_owner_id') is None: + if alert.alert_owner_id is None: updates['alert_owner_id'] = iris_current_user.id if data.get('alert_owner_id') == "-1" or data.get('alert_owner_id') == -1: @@ -450,7 +455,7 @@ def alerts_batch_update_route() -> Response: db.session.commit() - alert = call_modules_hook('on_postload_alert_update', data=alert) + alert = call_modules_hook('on_postload_alert_update', alert) if activity_data: track_activity(f"updated alert #{alert_id}: {','.join(activity_data)}", ctx_less=True) @@ -496,7 +501,7 @@ def alerts_batch_delete_route() -> Response: if not alert: return response_error(f'Alert with ID {alert_id} not found') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to delete alerts for the client', status=403) success, logs = delete_alerts(alert_ids) @@ -504,7 +509,7 @@ def alerts_batch_delete_route() -> Response: if not success: return response_error(logs) - alert = call_modules_hook('on_postload_alert_delete', data={"alert_ids": alert_ids}) + alert = call_modules_hook('on_postload_alert_delete', {"alert_ids": alert_ids}) track_activity(f"deleted alerts #{','.join(str(alert_id) for alert_id in alert_ids)}", ctx_less=True) @@ -533,7 +538,7 @@ def alerts_delete_route(alert_id) -> Response: try: # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to delete alerts for the client', status=403) # Delete the case association @@ -546,7 +551,7 @@ def alerts_delete_route(alert_id) -> Response: db.session.delete(alert) db.session.commit() - alert = call_modules_hook('on_postload_alert_delete', data=alert_id) + alert = call_modules_hook('on_postload_alert_delete', alert_id) track_activity(f"delete alert #{alert_id}", ctx_less=True) @@ -591,7 +596,7 @@ def alerts_escalate_route(alert_id) -> Response: try: # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to escalate alerts for the client', status=403) # Escalate the alert to a case @@ -606,9 +611,9 @@ def alerts_escalate_route(alert_id) -> Response: if not case: return response_error('Failed to create case from alert') - ac_set_new_case_access(None, case.case_id, case.client_id) + ac_set_new_case_access(iris_current_user, case.case_id, case.client_id) - case = call_modules_hook('on_postload_case_create', data=case) + case = call_modules_hook('on_postload_case_create', case) add_obj_history_entry(case, 'created') track_activity(f"new case {case.name} created from alert", @@ -616,7 +621,7 @@ def alerts_escalate_route(alert_id) -> Response: add_obj_history_entry(alert, f"Alert escalated to case #{case.case_id}") - alert = call_modules_hook('on_postload_alert_escalate', data=alert) + alert = call_modules_hook('on_postload_alert_escalate', alert) # Return the updated alert as JSON return response_success(data=CaseSchema().dump(case)) @@ -666,7 +671,7 @@ def alerts_merge_route(alert_id) -> Response: case_tags = data.get('case_tags') try: # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to merge alerts for the client', status=403) # Check if the user has access to the case @@ -682,7 +687,7 @@ def alerts_merge_route(alert_id) -> Response: iocs_list=iocs_import_list, assets_list=assets_import_list, note=note, import_as_event=import_as_event, case_tags=case_tags) - alert = call_modules_hook('on_postload_alert_merge', data=alert, caseid=target_case_id) + alert = call_modules_hook('on_postload_alert_merge', alert, caseid=target_case_id) track_activity(f"merge alert #{alert_id} into existing case #{target_case_id}", caseid=target_case_id) add_obj_history_entry(alert, f"Alert merged into existing case #{target_case_id}") @@ -726,7 +731,7 @@ def alerts_unmerge_route(alert_id) -> Response: try: # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to unmerge alerts for the client', status=403) # Check if the user has access to the case @@ -742,7 +747,7 @@ def alerts_unmerge_route(alert_id) -> Response: track_activity(f"unmerge alert #{alert_id} from case #{target_case_id}", caseid=target_case_id) add_obj_history_entry(alert, f"Alert unmerged from case #{target_case_id}") - alert = call_modules_hook('on_postload_alert_unmerge', data=alert) + alert = call_modules_hook('on_postload_alert_unmerge', alert) # Return the updated case as JSON return response_success(data=AlertSchema().dump(alert), msg=message) @@ -799,7 +804,7 @@ def alerts_batch_merge_route() -> Response: continue # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to merge alerts for the client', status=403) alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id @@ -811,7 +816,7 @@ def alerts_batch_merge_route() -> Response: add_obj_history_entry(alert, f"Alert merged into existing case #{target_case_id}") - alert = call_modules_hook('on_postload_alert_merge', data=alert) + alert = call_modules_hook('on_postload_alert_merge', alert) if note: case.description += f"\n\n### Escalation note\n\n{note}\n\n" if case.description else f"\n\n{note}\n\n" @@ -867,26 +872,25 @@ def alerts_batch_escalate_route() -> Response: continue # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to escalate alerts for the client', status=403) alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id db.session.commit() - alert = call_modules_hook('on_postload_alert_escalate', data=alert) + alert = call_modules_hook('on_postload_alert_escalate', alert) alerts_list.append(alert) # Merge alerts in the case - case = create_case_from_alerts(alerts_list, iocs_list=iocs_import_list, assets_list=assets_import_list, - note=note, import_as_event=import_as_event, case_tags=case_tags, - case_title=case_title, template_id=case_template_id) + case = create_case_from_alerts(alerts_list, iocs_import_list, assets_import_list, case_title, + note, import_as_event, case_tags, case_template_id) if not case: return response_error('Failed to create case from alert') - ac_set_new_case_access(None, case.case_id, case.client_id) + ac_set_new_case_access(iris_current_user, case.case_id, case.client_id) - case = call_modules_hook('on_postload_case_create', data=case) + case = call_modules_hook('on_postload_case_create', case) add_obj_history_entry(case, 'created') track_activity(f"new case {case.name} created from alerts", @@ -923,7 +927,7 @@ def alert_comments_get(alert_id): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) alert_comments = get_alert_comments(alert_id) @@ -952,14 +956,14 @@ def alert_comment_delete(alert_id, com_id): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) success, msg = delete_alert_comment(comment_id=com_id, alert_id=alert_id) if not success: return response_error(msg) - call_modules_hook('on_postload_alert_comment_delete', data=com_id) + call_modules_hook('on_postload_alert_comment_delete', com_id) track_activity(f"comment {com_id} on alert {alert_id} deleted", ctx_less=True) @@ -986,7 +990,7 @@ def alert_comment_get(alert_id, com_id): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) comment = get_alert_comment(alert_id, com_id) @@ -1014,7 +1018,7 @@ def alert_comment_edit(alert_id, com_id): if not alert: return response_error('Invalid alert ID') - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) return case_comment_update(com_id, 'events', None) @@ -1040,7 +1044,7 @@ def case_comment_add(alert_id): if not alert: return response_error('Invalid alert ID') # Check if the user has access to the client - if not user_has_client_access(iris_current_user.id, alert.alert_customer_id): + if not ac_current_user_has_customer_access(alert.alert_customer_id): return response_error('User not entitled to read alerts for the client', status=403) comment_schema = CommentSchema() @@ -1061,7 +1065,7 @@ def case_comment_add(alert_id): "comment": comment_schema.dump(comment), "alert": AlertSchema().dump(alert) } - call_modules_hook('on_postload_alert_commented', data=hook_data) + call_modules_hook('on_postload_alert_commented', hook_data) track_activity(f"alert \"{alert.alert_id}\" commented", ctx_less=True) return response_success("Alert commented", data=comment_schema.dump(comment)) diff --git a/source/app/blueprints/rest/api_v2_routes.py b/source/app/blueprints/rest/api_v2_routes.py index 3fa9c7c5b..467658d61 100644 --- a/source/app/blueprints/rest/api_v2_routes.py +++ b/source/app/blueprints/rest/api_v2_routes.py @@ -26,11 +26,13 @@ from app.blueprints.rest.v2.auth import auth_blueprint from app.blueprints.rest.v2.cases import cases_blueprint from app.blueprints.rest.v2.dashboard import dashboard_blueprint +from app.blueprints.rest.v2.global_tasks import global_tasks_blueprint from app.blueprints.rest.v2.iocs import iocs_blueprint from app.blueprints.rest.v2.manage import manage_v2_blueprint from app.blueprints.rest.v2.tags import tags_blueprint from app.blueprints.rest.v2.tasks import tasks_blueprint from app.blueprints.rest.v2.profile import profile_blueprint +from app.blueprints.rest.v2.alerts_filters import alerts_filters_blueprint # Create root /api/v2 blueprint @@ -40,6 +42,7 @@ rest_v2_blueprint.register_blueprint(cases_blueprint) rest_v2_blueprint.register_blueprint(auth_blueprint) rest_v2_blueprint.register_blueprint(tasks_blueprint) +rest_v2_blueprint.register_blueprint(global_tasks_blueprint) rest_v2_blueprint.register_blueprint(iocs_blueprint) rest_v2_blueprint.register_blueprint(assets_blueprint) rest_v2_blueprint.register_blueprint(events_blueprint) @@ -50,3 +53,4 @@ rest_v2_blueprint.register_blueprint(manage_v2_blueprint) rest_v2_blueprint.register_blueprint(tags_blueprint) rest_v2_blueprint.register_blueprint(profile_blueprint) +rest_v2_blueprint.register_blueprint(alerts_filters_blueprint) diff --git a/source/app/blueprints/rest/case/case_assets_routes.py b/source/app/blueprints/rest/case/case_assets_routes.py index 97c8ea87c..1cb93b1a1 100644 --- a/source/app/blueprints/rest/case/case_assets_routes.py +++ b/source/app/blueprints/rest/case/case_assets_routes.py @@ -22,7 +22,7 @@ from flask import request from marshmallow import ValidationError -from app import db +from app.db import db from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.business.assets import assets_delete @@ -30,7 +30,7 @@ from app.business.assets import assets_get from app.business.assets import assets_update from app.blueprints.iris_user import iris_current_user -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.datamgmt.case.case_assets_db import get_raw_assets from app.datamgmt.case.case_assets_db import get_linked_iocs_finfo_from_asset from app.datamgmt.case.case_assets_db import add_comment_to_asset @@ -44,12 +44,13 @@ from app.datamgmt.case.case_assets_db import get_case_asset_comments from app.datamgmt.case.case_assets_db import get_similar_assets from app.datamgmt.case.case_db import get_case_client_id +from app.datamgmt.comments import get_comment from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes from app.datamgmt.manage.manage_users_db import get_user_cases_fast from app.datamgmt.states import get_assets_state from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity -from app.models.models import AnalysisStatus +from app.models.assets import AnalysisStatus from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseAssetsSchema from app.schema.marshables import CommentSchema @@ -166,7 +167,7 @@ def case_assets_state(caseid): def deprecated_add_asset(caseid): asset_schema = CaseAssetsSchema() try: - request_data = call_modules_hook('on_preload_asset_create', data=request.get_json(), caseid=caseid) + request_data = call_modules_hook('on_preload_asset_create', request.get_json(), caseid=caseid) ioc_links = request_data.get('ioc_links') asset = asset_schema.load(request_data) created_asset = assets_create(iris_current_user, caseid, asset, ioc_links) @@ -243,7 +244,7 @@ def case_upload_asset(caseid): row['analysis_status_id'] = analysis_status_id - request_data = call_modules_hook('on_preload_asset_create', data=row, caseid=caseid) + request_data = call_modules_hook('on_preload_asset_create', row, caseid=caseid) add_asset_schema.is_unique_for_cid(caseid, request_data) asset_sc = add_asset_schema.load(request_data) @@ -253,7 +254,7 @@ def case_upload_asset(caseid): user_id=iris_current_user.id ) - asset = call_modules_hook('on_postload_asset_create', data=asset, caseid=caseid) + asset = call_modules_hook('on_postload_asset_create', asset, caseid=caseid) if not asset: errors.append('Unable to add asset for internal reason') @@ -305,7 +306,7 @@ def asset_update(cur_id, caseid): if not asset: return response_error("Invalid asset ID for this case") - request_data = call_modules_hook('on_preload_asset_update', data=request.get_json(), caseid=caseid) + request_data = call_modules_hook('on_preload_asset_update', request.get_json(), caseid=caseid) request_data['asset_id'] = asset.asset_id schema = CaseAssetsSchema() updated_asset = schema.load(request_data, instance=asset, partial=True) @@ -375,7 +376,7 @@ def case_comment_asset_add(cur_id, caseid): "comment": comment_schema.dump(comment), "asset": CaseAssetsSchema().dump(asset) } - call_modules_hook('on_postload_asset_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_asset_commented', hook_data, caseid=caseid) track_activity(f"asset \"{asset.asset_name}\" commented", caseid=caseid) return response_success("Asset commented", data=comment_schema.dump(comment)) @@ -407,11 +408,13 @@ def case_comment_asset_edit(cur_id, com_id, caseid): @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_comment_asset_delete(cur_id, com_id, caseid): - success, msg = delete_asset_comment(cur_id, com_id) - if not success: - return response_error(msg) + comment = get_comment(iris_current_user, com_id) + if not comment: + return response_error('You are not allowed to delete this comment') + + delete_asset_comment(cur_id, comment) - call_modules_hook('on_postload_asset_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_asset_comment_delete', com_id, caseid=caseid) track_activity(f'comment {com_id} on asset {cur_id} deleted', caseid=caseid) - return response_success(msg) + return response_success('Comment deleted') diff --git a/source/app/blueprints/rest/case/case_evidences_routes.py b/source/app/blueprints/rest/case/case_evidences_routes.py index 6932a3322..2f3dadf6d 100644 --- a/source/app/blueprints/rest/case/case_evidences_routes.py +++ b/source/app/blueprints/rest/case/case_evidences_routes.py @@ -22,7 +22,7 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user @@ -45,7 +45,7 @@ from app.business.evidences import evidences_create from app.business.evidences import evidences_delete from app.business.evidences import evidences_update -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook @@ -189,7 +189,7 @@ def case_comment_evidence_add(cur_id, caseid): "comment": comment_schema.dump(comment), "evidence": CaseEvidenceSchema().dump(evidence) } - call_modules_hook('on_postload_evidence_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_evidence_commented', hook_data, caseid=caseid) track_activity(f"evidence \"{evidence.filename}\" commented", caseid=caseid) return response_success("Evidence commented", data=comment_schema.dump(comment)) @@ -225,7 +225,7 @@ def case_comment_evidence_delete(cur_id, com_id, caseid): if not success: return response_error(msg) - call_modules_hook('on_postload_evidence_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_evidence_comment_delete', com_id, caseid=caseid) track_activity(f"comment {com_id} on evidence {cur_id} deleted", caseid=caseid) return response_success(msg) diff --git a/source/app/blueprints/rest/case/case_ioc_routes.py b/source/app/blueprints/rest/case/case_ioc_routes.py index 7041c10a6..663f6bccd 100644 --- a/source/app/blueprints/rest/case/case_ioc_routes.py +++ b/source/app/blueprints/rest/case/case_ioc_routes.py @@ -25,7 +25,7 @@ from flask import request from marshmallow import ValidationError -from app import db +from app.db import db from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user @@ -33,8 +33,8 @@ from app.business.iocs import iocs_update from app.business.iocs import iocs_delete from app.business.iocs import iocs_get -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.case_iocs_db import add_comment_to_ioc from app.datamgmt.case.case_iocs_db import add_ioc from app.datamgmt.case.case_iocs_db import delete_ioc_comment @@ -51,12 +51,14 @@ from app.models.authorization import CaseAccessLevel from app.schema.marshables import CommentSchema from app.schema.marshables import IocSchema -from app.blueprints.access_controls import ac_requires_case_identifier, ac_fast_check_current_user_has_case_access +from app.blueprints.access_controls import ac_requires_case_identifier +from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access from app.blueprints.access_controls import ac_api_requires 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.module_handler.module_handler import call_deprecated_on_preload_modules_hook +from app.iris_engine.access_control.utils import ac_get_fast_user_cases_access case_ioc_rest_blueprint = Blueprint('case_ioc_rest', __name__) @@ -74,7 +76,8 @@ def case_list_ioc(caseid): out = ioc._asdict() # Get links of the IoCs seen in other cases - ial = get_ioc_links(ioc.ioc_id) + user_search_limitations = ac_get_fast_user_cases_access(iris_current_user.id) + ial = get_ioc_links(ioc.ioc_id, user_search_limitations) out['link'] = [row._asdict() for row in ial] # Legacy, must be changed next version @@ -176,7 +179,7 @@ def case_upload_ioc(caseid): row['ioc_type_id'] = type_id.type_id row.pop('ioc_type', None) - request_data = call_modules_hook('on_preload_ioc_create', data=row, caseid=caseid) + request_data = call_modules_hook('on_preload_ioc_create', row, caseid=caseid) ioc = add_ioc_schema.load(request_data) ioc.custom_attributes = get_default_custom_attributes('ioc') @@ -188,7 +191,7 @@ def case_upload_ioc(caseid): continue add_ioc(ioc, iris_current_user.id, caseid) - ioc = call_modules_hook('on_postload_ioc_create', data=ioc, caseid=caseid) + ioc = call_modules_hook('on_postload_ioc_create', ioc, caseid=caseid) ret.append(request_data) track_activity(f'added ioc "{ioc.ioc_value}"', caseid=caseid) @@ -301,7 +304,7 @@ def case_comment_ioc_add(cur_id, caseid): 'comment': comment_schema.dump(comment), 'ioc': IocSchema().dump(ioc) } - call_modules_hook('on_postload_ioc_commented', data=hook_data, caseid=ioc.case_id) + call_modules_hook('on_postload_ioc_commented', hook_data, caseid=ioc.case_id) track_activity(f'ioc "{ioc.ioc_value}" commented', caseid=ioc.case_id) return response_success('IOC commented', data=comment_schema.dump(comment)) @@ -339,7 +342,7 @@ def case_comment_ioc_delete(cur_id, com_id, caseid): if not success: return response_error(msg) - call_modules_hook('on_postload_ioc_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_ioc_comment_delete', com_id, 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 ff5483124..e20b22852 100644 --- a/source/app/blueprints/rest/case/case_notes_routes.py +++ b/source/app/blueprints/rest/case/case_notes_routes.py @@ -21,12 +21,12 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app import app from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.notes import notes_create from app.business.notes import notes_list_revisions from app.business.notes import notes_get_revision @@ -138,7 +138,7 @@ def case_note_save(cur_id, caseid): try: note = notes_get(cur_id) - request_data = call_modules_hook('on_preload_note_update', data=request.get_json(), caseid=note.note_case_id) + request_data = call_modules_hook('on_preload_note_update', request.get_json(), caseid=note.note_case_id) request_data['note_id'] = note.note_id addnote_schema.load(request_data, partial=True, instance=note) @@ -207,7 +207,7 @@ def case_note_add(caseid): try: - request_data = call_modules_hook('on_preload_note_create', data=request.get_json(), caseid=caseid) + request_data = call_modules_hook('on_preload_note_create', request.get_json(), caseid=caseid) note_schema = CaseNoteSchema() note_schema.verify_directory_id(request_data, caseid=caseid) @@ -426,7 +426,7 @@ def case_comment_note_add(cur_id, caseid): "comment": comment_schema.dump(comment), "note": CaseNoteSchema().dump(note) } - call_modules_hook('on_postload_note_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_note_commented', hook_data, caseid=caseid) track_activity(f"note \"{note.note_title}\" commented", caseid=caseid) return response_success("Note commented", data=comment_schema.dump(comment)) @@ -462,7 +462,7 @@ def case_comment_note_delete(cur_id, com_id, caseid): if not success: return response_error(msg) - call_modules_hook('on_postload_note_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_note_comment_delete', com_id, caseid=caseid) track_activity(f"comment {com_id} on note {cur_id} deleted", caseid=caseid) return response_success(msg) diff --git a/source/app/blueprints/rest/case/case_routes.py b/source/app/blueprints/rest/case/case_routes.py index 32267fdd5..d3a141205 100644 --- a/source/app/blueprints/rest/case/case_routes.py +++ b/source/app/blueprints/rest/case/case_routes.py @@ -24,7 +24,7 @@ from flask import request from app import app -from app import db +from app.db import db from app import socket_io from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user @@ -41,8 +41,7 @@ from app.business.cases import cases_export_to_json from app.iris_engine.access_control.utils import ac_set_case_access_for_users from app.iris_engine.utils.tracker import track_activity -from app.models.models import CaseStatus -from app.models.models import ReviewStatusList +from app.models.cases import CaseStatus, ReviewStatusList from app.models.authorization import CaseAccessLevel from app.schema.marshables import TaskLogSchema from app.schema.marshables import CaseSchema diff --git a/source/app/blueprints/rest/case/case_tasks_routes.py b/source/app/blueprints/rest/case/case_tasks_routes.py index 15917e3d5..cefb7d8f2 100644 --- a/source/app/blueprints/rest/case/case_tasks_routes.py +++ b/source/app/blueprints/rest/case/case_tasks_routes.py @@ -22,11 +22,11 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.tasks import tasks_delete from app.business.tasks import tasks_create from app.business.tasks import tasks_get @@ -230,7 +230,7 @@ def case_comment_task_add(cur_id: int, caseid: int): "comment": comment_schema.dump(comment), "task": CaseTaskSchema().dump(task) } - call_modules_hook('on_postload_task_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_task_commented', hook_data, caseid=caseid) track_activity(f"task \"{task.task_title}\" commented", caseid=caseid) return response_success("Task commented", data=comment_schema.dump(comment)) @@ -270,7 +270,7 @@ def case_comment_task_delete(cur_id: int, com_id: int, caseid: int): if not success: return response_error(msg) - call_modules_hook('on_postload_task_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_task_comment_delete', com_id, caseid=caseid) track_activity(f"comment {com_id} on task {cur_id} deleted", caseid=caseid) return response_success(msg) diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index a5ef0a5f9..0159b4cab 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -26,7 +26,7 @@ from flask import request from sqlalchemy import and_ -from app import db +from app.db import db from app import app from app.blueprints.rest.case_comments import case_comment_update from app.blueprints.rest.endpoints import endpoint_deprecated @@ -56,12 +56,10 @@ from app.iris_engine.utils.collab import collab_notify from app.iris_engine.utils.common import parse_bf_date_format from app.iris_engine.utils.tracker import track_activity -from app.models.models import CompromiseStatus +from app.models.assets import CompromiseStatus, AssetsType, CaseAssets from app.models.authorization import CaseAccessLevel from app.models.authorization import User from app.models.cases import CasesEvent -from app.models.models import AssetsType -from app.models.models import CaseAssets from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc from app.models.models import EventCategory @@ -73,7 +71,7 @@ from app.util import add_obj_history_entry from app.blueprints.responses import response_error from app.blueprints.responses import response_success -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.events import events_create from app.business.events import events_update from app.business.events import events_delete @@ -102,7 +100,7 @@ def case_comment_delete(cur_id, com_id, caseid): if not success: return response_error(msg) - call_modules_hook('on_postload_event_comment_delete', data=com_id, caseid=caseid) + call_modules_hook('on_postload_event_comment_delete', com_id, caseid=caseid) track_activity(f"comment {com_id} on event {cur_id} deleted", caseid=caseid) return response_success(msg) @@ -157,7 +155,7 @@ def case_comment_add(cur_id, caseid): "comment": comment_schema.dump(comment), "event": EventSchema().dump(event) } - call_modules_hook('on_postload_event_commented', data=hook_data, caseid=caseid) + call_modules_hook('on_postload_event_commented', hook_data, caseid=caseid) track_activity(f"event \"{event.event_title}\" commented", caseid=caseid) return response_success("Event commented", data=comment_schema.dump(comment)) @@ -627,7 +625,7 @@ def _extract_timeline(assets: str | None, assets_id: str | None, caseid, categor @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_delete_event(cur_id, caseid): - call_modules_hook('on_preload_event_delete', data=cur_id, caseid=caseid) + call_modules_hook('on_preload_event_delete', cur_id, caseid=caseid) event = get_case_event(cur_id) if not event: @@ -750,7 +748,7 @@ def case_add_event(caseid): @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_duplicate_event(cur_id, caseid): - call_modules_hook('on_preload_event_duplicate', data=cur_id, caseid=caseid) + call_modules_hook('on_preload_event_duplicate', cur_id, caseid=caseid) try: event_schema = EventSchema() @@ -795,7 +793,7 @@ def case_duplicate_event(cur_id, caseid): if not success: return response_error('Error while saving linked iocs', data=log) - event = call_modules_hook('on_postload_event_create', data=event, caseid=caseid) + event = call_modules_hook('on_postload_event_create', event, caseid=caseid) track_activity(f"added event \"{event.event_title}\"", caseid=caseid) return response_success("Event duplicated", data=event_schema.dump(event)) @@ -950,7 +948,7 @@ def case_events_upload_csv(caseid): continue line += 1 - request_data = call_modules_hook('on_preload_event_create', data=row, caseid=caseid) + request_data = call_modules_hook('on_preload_event_create', row, caseid=caseid) event = event_schema.load(request_data) event.event_date, event.event_date_wtz = event_schema.validate_date(request_data.get(u'event_date'), request_data.get(u'event_tz')) @@ -978,7 +976,7 @@ def case_events_upload_csv(caseid): setattr(event, 'event_category_id', request_data.get('event_category_id')) - event = call_modules_hook('on_postload_event_create', data=event, caseid=caseid) + event = call_modules_hook('on_postload_event_create', event, caseid=caseid) track_activity(f"added event {event.event_id}", caseid=caseid) diff --git a/source/app/blueprints/rest/case_comments.py b/source/app/blueprints/rest/case_comments.py index d67ea1aac..cdaf325e8 100644 --- a/source/app/blueprints/rest/case_comments.py +++ b/source/app/blueprints/rest/case_comments.py @@ -24,7 +24,7 @@ from app.blueprints.responses import response_error from app.blueprints.responses import response_success from app.business.comments import comments_update_for_case -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.blueprints.iris_user import iris_current_user diff --git a/source/app/blueprints/rest/context_routes.py b/source/app/blueprints/rest/context_routes.py index beb454560..df1b7c18c 100644 --- a/source/app/blueprints/rest/context_routes.py +++ b/source/app/blueprints/rest/context_routes.py @@ -22,12 +22,12 @@ from app import app from app import cache -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.context.context_db import ctx_search_user_cases from app.models.authorization import Permissions from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.blueprints.access_controls import ac_api_requires, not_authenticated_redirection_url from app.blueprints.responses import response_success from app.blueprints.rest.endpoints import endpoint_deprecated diff --git a/source/app/blueprints/rest/dashboard_routes.py b/source/app/blueprints/rest/dashboard_routes.py index 0e4355bbc..b2b135729 100644 --- a/source/app/blueprints/rest/dashboard_routes.py +++ b/source/app/blueprints/rest/dashboard_routes.py @@ -27,18 +27,18 @@ from flask import redirect from flask_login import logout_user -from app import db +from app.db import db from app import app from app import oidc_client from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user -from app.datamgmt.dashboard.dashboard_db import get_global_task -from app.datamgmt.dashboard.dashboard_db import list_user_cases -from app.datamgmt.dashboard.dashboard_db import list_user_reviews -from app.datamgmt.dashboard.dashboard_db import get_tasks_status -from app.datamgmt.dashboard.dashboard_db import list_global_tasks -from app.datamgmt.dashboard.dashboard_db import list_user_tasks +from app.datamgmt.case.case_db import list_user_reviews +from app.datamgmt.case.case_db import list_user_cases +from app.datamgmt.case.case_tasks_db import get_tasks_status +from app.datamgmt.global_tasks import list_global_tasks +from app.datamgmt.global_tasks import get_global_task +from app.datamgmt.case.case_tasks_db import list_user_tasks from app.forms import CaseGlobalTaskForm from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity @@ -153,13 +153,14 @@ def get_gtasks(): @dashboard_rest_blueprint.route('/global/tasks/', methods=['GET']) +@endpoint_deprecated('GET', '/api/v2/global-tasks/{identifier}') @ac_api_requires() def view_gtask(cur_id): - task = get_global_task(task_id=cur_id) + task = get_global_task(cur_id) if not task: return response_error(f'Global task ID {cur_id} not found') - return response_success("", data=task._asdict()) + return response_success('', data=task._asdict()) @dashboard_rest_blueprint.route('/user/tasks/status/update', methods=['POST']) @@ -200,6 +201,7 @@ def utask_statusupdate(caseid): @dashboard_rest_blueprint.route('/global/tasks/add', methods=['POST']) +@endpoint_deprecated('POST', '/api/v2/global-tasks') @ac_api_requires() @ac_requires_case_identifier() def add_gtask(caseid): @@ -207,8 +209,7 @@ def add_gtask(caseid): gtask_schema = GlobalTasksSchema() - request_data = call_modules_hook( - 'on_preload_global_task_create', data=request.get_json(), caseid=caseid) + request_data = call_modules_hook('on_preload_global_task_create', request.get_json(), caseid=caseid) gtask = gtask_schema.load(request_data) @@ -228,8 +229,7 @@ def add_gtask(caseid): except Exception as e: return response_error(msg="Data error", data=e.__str__()) - gtask = call_modules_hook( - 'on_postload_global_task_create', data=gtask, caseid=caseid) + gtask = call_modules_hook('on_postload_global_task_create', gtask, caseid=caseid) track_activity(f"created new global task \'{gtask.task_title}\'", caseid=caseid) return response_success('Task added', data=gtask_schema.dump(gtask)) @@ -252,8 +252,7 @@ def edit_gtask(cur_id, caseid): try: gtask_schema = GlobalTasksSchema() - request_data = call_modules_hook('on_preload_global_task_update', data=request.get_json(), - caseid=caseid) + request_data = call_modules_hook('on_preload_global_task_update', request.get_json(), caseid=caseid) gtask = gtask_schema.load(request_data, instance=task) gtask.task_userid_update = iris_current_user.id @@ -273,11 +272,11 @@ def edit_gtask(cur_id, caseid): @dashboard_rest_blueprint.route('/global/tasks/delete/', methods=['POST']) +@endpoint_deprecated('DELETE', '/api/v2/global-tasks/{identifier}') @ac_api_requires() @ac_requires_case_identifier() def gtask_delete(cur_id, caseid): - call_modules_hook('on_preload_global_task_delete', - data=cur_id, caseid=caseid) + call_modules_hook('on_preload_global_task_delete', cur_id, caseid=caseid) if not cur_id: return response_error("Missing parameter") @@ -289,8 +288,7 @@ def gtask_delete(cur_id, caseid): GlobalTasks.query.filter(GlobalTasks.id == cur_id).delete() db.session.commit() - call_modules_hook('on_postload_global_task_delete', - data=request.get_json(), caseid=caseid) + call_modules_hook('on_postload_global_task_delete', request.get_json(), caseid=caseid) track_activity(f"deleted global task ID {cur_id}", caseid=caseid) return response_success("Task deleted") diff --git a/source/app/blueprints/rest/datastore_routes.py b/source/app/blueprints/rest/datastore_routes.py index f2609d87f..b6128d865 100644 --- a/source/app/blueprints/rest/datastore_routes.py +++ b/source/app/blueprints/rest/datastore_routes.py @@ -27,7 +27,7 @@ from flask import send_file from pathlib import Path -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.datastore.datastore_db import datastore_add_child_node from app.datamgmt.datastore.datastore_db import datastore_add_file_as_evidence @@ -137,7 +137,7 @@ def datastore_update_file(cur_id: int, caseid: int): msg_added_as = '' if dsf.file_is_ioc: - datastore_add_file_as_ioc(dsf, caseid) + datastore_add_file_as_ioc(dsf) msg_added_as += 'and added in IOC' if dsf.file_is_evidence: @@ -262,7 +262,7 @@ def datastore_add_file(cur_id: int, caseid: int): msg_added_as = '' if dsf_sc.file_is_ioc: - datastore_add_file_as_ioc(dsf_sc, caseid) + datastore_add_file_as_ioc(dsf_sc) msg_added_as += 'and added in IOC' if dsf_sc.file_is_evidence: diff --git a/source/app/blueprints/rest/dim_tasks_routes.py b/source/app/blueprints/rest/dim_tasks_routes.py index 4e3f6c73c..4d275a844 100644 --- a/source/app/blueprints/rest/dim_tasks_routes.py +++ b/source/app/blueprints/rest/dim_tasks_routes.py @@ -23,7 +23,7 @@ from app.models.models import IrisHook from app.models.models import IrisModule from app.models.models import IrisModuleHook -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.models import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases @@ -139,8 +139,7 @@ def dim_hooks_call(caseid): index += 1 if len(obj_targets) > 0: - call_modules_hook(hook_name=hook_name, hook_ui_name=hook_ui_name, data=obj_targets, - caseid=caseid, module_name=module_name) + call_modules_hook(hook_name, obj_targets, caseid=caseid, hook_ui_name=hook_ui_name, module_name=module_name) if len(logs) > 0: return response_error(f"Errors encountered during processing of data. Queued task with {index} objects", diff --git a/source/app/blueprints/rest/filters_routes.py b/source/app/blueprints/rest/filters_routes.py index 0971a1b49..51d09b7cc 100644 --- a/source/app/blueprints/rest/filters_routes.py +++ b/source/app/blueprints/rest/filters_routes.py @@ -19,20 +19,22 @@ from flask import Blueprint, request from werkzeug import Response -from app import db -from app.blueprints.iris_user import iris_current_user +from app.db import db from app.datamgmt.filters.filters_db import get_filter_by_id from app.datamgmt.filters.filters_db import list_filters_by_type from app.iris_engine.utils.tracker import track_activity from app.schema.marshables import SavedFilterSchema +from app.blueprints.iris_user import iris_current_user from app.blueprints.access_controls import ac_api_requires from app.blueprints.responses import response_success from app.blueprints.responses import response_error +from app.blueprints.rest.endpoints import endpoint_deprecated saved_filters_rest_blueprint = Blueprint('saved_filters_rest', __name__) @saved_filters_rest_blueprint.route('/filters/add', methods=['POST']) +@endpoint_deprecated('POST', '/api/v2/alerts-filters') @ac_api_requires() def filters_add_route() -> Response: """ @@ -66,6 +68,7 @@ def filters_add_route() -> Response: @saved_filters_rest_blueprint.route('/filters/update/', methods=['POST']) +@endpoint_deprecated('PUT', '/api/v2/alerts-filters/{identifier}') @ac_api_requires() def filters_update_route(filter_id) -> Response: """ @@ -99,6 +102,7 @@ def filters_update_route(filter_id) -> Response: @saved_filters_rest_blueprint.route('/filters/delete/', methods=['POST']) +@endpoint_deprecated('DELETE', '/api/v2/alerts-filters/{identifier}') @ac_api_requires() def filters_delete_route(filter_id) -> Response: """ @@ -128,6 +132,7 @@ def filters_delete_route(filter_id) -> Response: @saved_filters_rest_blueprint.route('/filters/', methods=['GET']) +@endpoint_deprecated('GET', '/api/v2/alerts-filters/{identifier}') @ac_api_requires() def filters_get_route(filter_id) -> Response: """ 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 17ac1f891..e729476c1 100644 --- a/source/app/blueprints/rest/manage/manage_analysis_status_routes.py +++ b/source/app/blueprints/rest/manage/manage_analysis_status_routes.py @@ -22,7 +22,7 @@ from app.datamgmt.case.case_assets_db import get_compromise_status_dict from app.datamgmt.case.case_assets_db import get_case_outcome_status_dict from app.datamgmt.manage.manage_case_objs import search_analysis_status_by_name -from app.models.models import AnalysisStatus +from app.models.assets import AnalysisStatus from app.schema.marshables import AnalysisStatusSchema from app.blueprints.access_controls import ac_api_requires from app.blueprints.responses import response_error 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 3fa82461e..7c9f445e6 100644 --- a/source/app/blueprints/rest/manage/manage_assets_type_routes.py +++ b/source/app/blueprints/rest/manage/manage_assets_type_routes.py @@ -24,12 +24,11 @@ from flask import request from app import app -from app import db +from app.db import db from app.datamgmt.manage.manage_case_objs import search_asset_type_by_name from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions -from app.models.models import AssetsType -from app.models.models import CaseAssets +from app.models.assets import AssetsType, CaseAssets from app.schema.marshables import AssetTypeSchema from app.blueprints.access_controls import ac_api_requires from app.blueprints.responses import response_error diff --git a/source/app/blueprints/rest/manage/manage_attributes_routes.py b/source/app/blueprints/rest/manage/manage_attributes_routes.py index 5dcd1b8ae..8f7e755b4 100644 --- a/source/app/blueprints/rest/manage/manage_attributes_routes.py +++ b/source/app/blueprints/rest/manage/manage_attributes_routes.py @@ -19,7 +19,7 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app.datamgmt.manage.manage_attribute_db import update_all_attributes from app.datamgmt.manage.manage_attribute_db import validate_attribute from app.models.authorization import Permissions 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 a63f3c26f..d7b61070b 100644 --- a/source/app/blueprints/rest/manage/manage_case_classifications_routes.py +++ b/source/app/blueprints/rest/manage/manage_case_classifications_routes.py @@ -22,7 +22,7 @@ from flask import Response from flask import request -from app import db +from app.db import db from app.datamgmt.manage.manage_case_classifications_db import get_case_classifications_list from app.datamgmt.manage.manage_case_classifications_db import get_case_classification_by_id from app.datamgmt.manage.manage_case_classifications_db import search_classification_by_name diff --git a/source/app/blueprints/rest/manage/manage_case_state.py b/source/app/blueprints/rest/manage/manage_case_state.py index 57b8461db..50570fc4a 100644 --- a/source/app/blueprints/rest/manage/manage_case_state.py +++ b/source/app/blueprints/rest/manage/manage_case_state.py @@ -22,7 +22,7 @@ from flask import Response from flask import request -from app import db +from app.db import db from app.datamgmt.manage.manage_case_state_db import get_case_states_list from app.datamgmt.manage.manage_case_state_db import get_case_state_by_id from app.datamgmt.manage.manage_case_state_db import get_cases_using_state @@ -45,7 +45,8 @@ def list_case_state() -> Response: Flask Response object """ - l_cl = get_case_states_list() + case_states = get_case_states_list() + l_cl = CaseStateSchema(many=True).dump(case_states) return response_success("", data=l_cl) 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 0c27de5a8..4567b9bd8 100644 --- a/source/app/blueprints/rest/manage/manage_case_templates_routes.py +++ b/source/app/blueprints/rest/manage/manage_case_templates_routes.py @@ -21,7 +21,7 @@ from flask import request from marshmallow import ValidationError -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_case_templates_db import get_case_templates_list from app.datamgmt.manage.manage_case_templates_db import get_case_template_by_id diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index 96b13a798..a68de7505 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -26,7 +26,7 @@ from werkzeug.utils import secure_filename from marshmallow import ValidationError -from app import db +from app.db import db from app.blueprints.rest.parsing import parse_comma_separated_identifiers from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user @@ -50,7 +50,9 @@ from app.schema.marshables import CaseSchema from app.schema.marshables import CaseDetailsSchema from app.util import add_obj_history_entry -from app.blueprints.access_controls import ac_requires_case_identifier, ac_fast_check_current_user_has_case_access +from app.blueprints.access_controls import ac_requires_case_identifier +from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access +from app.blueprints.access_controls import ac_current_user_has_customer_access from app.blueprints.access_controls import ac_api_requires from app.blueprints.access_controls import ac_api_return_access_denied from app.blueprints.responses import response_error @@ -60,9 +62,8 @@ from app.business.cases import cases_update from app.business.cases import cases_create from app.business.cases import cases_get_by_identifier -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook -from app.datamgmt.manage.manage_access_control_db import user_has_client_access manage_cases_rest_blueprint = Blueprint('manage_case_rest', __name__) @@ -187,7 +188,7 @@ def api_reopen_case(identifier): db.session.add(alert) - case = call_modules_hook('on_postload_case_update', data=case, caseid=identifier) + case = call_modules_hook('on_postload_case_update', case, caseid=identifier) add_obj_history_entry(case, 'case reopen') track_activity(f"reopen case ID {identifier}", caseid=identifier) @@ -221,18 +222,18 @@ def api_case_close(identifier): for alert in case.alerts: if alert.alert_status_id != close_status.status_id: alert.alert_status_id = close_status.status_id - alert = call_modules_hook('on_postload_alert_update', data=alert, caseid=identifier) + alert = call_modules_hook('on_postload_alert_update', alert, caseid=identifier) if alert.alert_resolution_status_id != case_status_id_mapped: alert.alert_resolution_status_id = case_status_id_mapped - alert = call_modules_hook('on_postload_alert_resolution_update', data=alert, caseid=identifier) + alert = call_modules_hook('on_postload_alert_resolution_update', alert, caseid=identifier) track_activity(f'closing alert ID {alert.alert_id} due to case #{identifier} being closed', caseid=identifier, ctx_less=False) db.session.add(alert) - case = call_modules_hook('on_postload_case_update', data=case, caseid=identifier) + case = call_modules_hook('on_postload_case_update', case, caseid=identifier) add_obj_history_entry(case, 'case closed') track_activity(f'closed case ID {identifier}', caseid=identifier, ctx_less=False) @@ -248,10 +249,10 @@ def api_add_case(): case_schema = CaseSchema() try: - request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json(), None) + request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json()) case = case_schema.load(request_data) case_template_id = request_data.pop('case_template_id', None) - result = cases_create(case, case_template_id) + result = cases_create(iris_current_user, case, case_template_id) return response_success('Case created', data=case_schema.dump(result)) except ValidationError as e: raise response_error('Data error', e.messages) @@ -281,7 +282,7 @@ def update_case_info(identifier): request_data = request.get_json() # If user tries to update the customer, check if the user has access to the new customer if request_data.get('case_customer') and request_data.get('case_customer') != case.client_id: - if not user_has_client_access(iris_current_user.id, request_data.get('case_customer')): + if not ac_current_user_has_customer_access(request_data.get('case_customer')): raise BusinessProcessingError('Invalid customer ID. Permission denied.') if 'case_name' in request_data: diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index a0678ea95..1ef924d4b 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -25,19 +25,20 @@ from app import ac_current_user_has_permission from app.blueprints.access_controls import ac_api_requires from app.blueprints.iris_user import iris_current_user -from app.datamgmt.client.client_db import create_client -from app.datamgmt.client.client_db import create_contact +from app.business.customers import customers_get, customers_update +from app.business.customers_contacts import customers_contacts_get +from app.datamgmt.db_operations import db_create +from app.models.errors import ObjectNotFoundError +from app.models.errors import ElementInUseError from app.datamgmt.client.client_db import delete_client from app.datamgmt.client.client_db import delete_contact -from app.datamgmt.client.client_db import get_client +from app.datamgmt.client.client_db import get_customer from app.datamgmt.client.client_db import get_client_api from app.datamgmt.client.client_db import get_client_cases from app.datamgmt.client.client_db import get_client_contacts from app.datamgmt.client.client_db import get_client_list -from app.datamgmt.client.client_db import update_client +from app.datamgmt.client.client_db import get_client_contact from app.datamgmt.client.client_db import update_contact -from app.datamgmt.exceptions.ElementExceptions import ElementInUseException -from app.datamgmt.exceptions.ElementExceptions import ElementNotFoundException from app.datamgmt.manage.manage_users_db import add_user_to_customer from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions @@ -47,21 +48,23 @@ from app.blueprints.responses import response_error from app.blueprints.responses import response_success from app.blueprints.rest.endpoints import endpoint_deprecated +from app.business.customers import customers_exists_another_with_same_name manage_customers_rest_blueprint = Blueprint('manage_customers_rest', __name__) @manage_customers_rest_blueprint.route('/manage/customers/list', methods=['GET']) +@endpoint_deprecated('GET', '/api/v2/manage/customers') @ac_api_requires(Permissions.customers_read) def list_customers(): user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) - client_list = get_client_list(current_user_id=iris_current_user.id, - is_server_administrator=user_is_server_administrator) + client_list = get_client_list(iris_current_user.id, user_is_server_administrator) return response_success("", data=client_list) @manage_customers_rest_blueprint.route('/manage/customers/', methods=['GET']) +@endpoint_deprecated('GET', '/api/v2/manage/customers/{identifier}') @ac_api_requires(Permissions.customers_read) @ac_api_requires_client_access() def view_customer(client_id): @@ -79,14 +82,19 @@ def view_customer(client_id): def customer_update_contact(client_id, contact_id): if not request.is_json: - return response_error("Invalid request") + return response_error('Invalid request') - if not get_client(client_id): - return response_error(f"Invalid Customer ID {client_id}") + if not get_customer(client_id): + return response_error(f'Invalid Customer ID {client_id}') try: + data = request.json + contact = get_client_contact(contact_id) + data['client_id'] = client_id + contact_schema = ContactSchema() + contact_schema.load(data, instance=contact) - contact = update_contact(request.json, contact_id, client_id) + update_contact() except ValidationError as e: return response_error(msg='Error update contact', data=e.messages) @@ -95,11 +103,11 @@ def customer_update_contact(client_id, contact_id): print(traceback.format_exc()) return response_error(f'An error occurred during contact update. {e}') - track_activity(f"Updated contact {contact.contact_name}", ctx_less=True) + track_activity(f'Updated contact {contact.contact_name}', ctx_less=True) # Return the customer contact_schema = ContactSchema() - return response_success("Added successfully", data=contact_schema.dump(contact)) + return response_success('Added successfully', data=contact_schema.dump(contact)) @manage_customers_rest_blueprint.route('/manage/customers//contacts/add', methods=['POST']) @@ -110,12 +118,16 @@ def customer_add_contact(client_id): if not request.is_json: return response_error("Invalid request") - if not get_client(client_id): + if not get_customer(client_id): return response_error(f"Invalid Customer ID {client_id}") + contact_schema = ContactSchema() try: + data = request.json + data['client_id'] = client_id + contact = contact_schema.load(data) - contact = create_contact(request.json, client_id) + db_create(contact) except ValidationError as e: return response_error(msg='Error adding contact', data=e.messages) @@ -127,7 +139,6 @@ def customer_add_contact(client_id): track_activity(f"Added contact {contact.contact_name}", ctx_less=True) # Return the customer - contact_schema = ContactSchema() return response_success("Added successfully", data=contact_schema.dump(contact)) @@ -216,27 +227,33 @@ def get_customer_case_stats(client_id): @manage_customers_rest_blueprint.route('/manage/customers/update/', methods=['POST']) +@endpoint_deprecated('PUT', '/api/v2/manage/customers/{identifier}') @ac_api_requires(Permissions.customers_write) @ac_api_requires_client_access() def view_customers(client_id): if not request.is_json: return response_error("Invalid request") + client_schema = CustomerSchema() try: - client = update_client(client_id, request.json) + customer = get_customer(client_id) + if not customer: + raise response_error('Invalid Customer ID') - except ElementNotFoundException: - return response_error('Invalid Customer ID') + data = request.json + if customers_exists_another_with_same_name(client_id, data.get('customer_name')): + raise ValidationError('Customer already exists', field_name='customer_name') + client_schema.load(data, instance=customer) + customers_update() except ValidationError as e: - return response_error("", data=e.messages) + return response_error('', data=e.messages) except Exception as e: print(traceback.format_exc()) return response_error(f'An error occurred during Customer update. {e}') - client_schema = CustomerSchema() - return response_success("Customer updated", client_schema.dump(client)) + return response_success('Customer updated', client_schema.dump(customer)) @manage_customers_rest_blueprint.route('/manage/customers/add', methods=['POST']) @@ -250,7 +267,7 @@ def add_customers(): try: customer = customer_schema.load(request.json) - create_client(customer) + db_create(customer) except ValidationError as e: return response_error(msg='Error adding customer', data=e.messages) except Exception as e: @@ -267,17 +284,17 @@ def add_customers(): @manage_customers_rest_blueprint.route('/manage/customers/delete/', methods=['POST']) +@endpoint_deprecated('DELETE', '/api/v2/manage/customers/{identifier}') @ac_api_requires(Permissions.customers_write) @ac_api_requires_client_access() def delete_customers(client_id): try: - - delete_client(client_id) - - except ElementNotFoundException: + customer = customers_get(client_id) + delete_client(customer) + except ObjectNotFoundError: return response_error('Invalid Customer ID') - except ElementInUseException: + except ElementInUseError: return response_error('Cannot delete a referenced customer') except Exception: @@ -293,13 +310,14 @@ def delete_customers(client_id): @ac_api_requires_client_access() def delete_contact_route(client_id, contact_id): try: + contact = customers_contacts_get(contact_id) - delete_contact(contact_id) + delete_contact(contact) - except ElementNotFoundException: + except ObjectNotFoundError: return response_error('Invalid contact ID') - except ElementInUseException: + except ElementInUseError: return response_error('Cannot delete a referenced contact') except Exception: 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 2bf31c48a..a64fa4099 100644 --- a/source/app/blueprints/rest/manage/manage_evidence_types_routes.py +++ b/source/app/blueprints/rest/manage/manage_evidence_types_routes.py @@ -22,7 +22,7 @@ from flask import Response from flask import request -from app import db +from app.db import db from app.datamgmt.manage.manage_evidence_types_db import get_evidence_types_list from app.datamgmt.manage.manage_evidence_types_db import get_evidence_type_by_id from app.datamgmt.manage.manage_evidence_types_db import verify_evidence_type_in_use diff --git a/source/app/blueprints/rest/manage/manage_groups.py b/source/app/blueprints/rest/manage/manage_groups.py index 477bfb354..0e005ba2d 100644 --- a/source/app/blueprints/rest/manage/manage_groups.py +++ b/source/app/blueprints/rest/manage/manage_groups.py @@ -22,21 +22,22 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app import app from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_groups_db import add_all_cases_access_to_group +from app.datamgmt.manage.manage_groups_db import get_groups_list +from app.datamgmt.manage.manage_groups_db import get_users_by_group_identifiers from app.datamgmt.manage.manage_groups_db import add_case_access_to_group from app.datamgmt.manage.manage_groups_db import delete_group from app.datamgmt.manage.manage_groups_db import get_group from app.datamgmt.manage.manage_groups_db import get_group_details from app.datamgmt.manage.manage_groups_db import get_group_with_members -from app.datamgmt.manage.manage_groups_db import get_groups_list_hr_perms from app.datamgmt.manage.manage_groups_db import remove_cases_access_from_group from app.datamgmt.manage.manage_groups_db import remove_user_from_group from app.datamgmt.manage.manage_groups_db import update_group_members from app.datamgmt.manage.manage_users_db import get_user -from app.iris_engine.access_control.utils import ac_ldp_group_removal +from app.iris_engine.access_control.utils import ac_ldp_group_removal, ac_permission_to_list from app.iris_engine.access_control.utils import ac_ldp_group_update from app.iris_engine.access_control.utils import ac_recompute_effective_ac_from_users_list from app.models.authorization import Permissions, ac_flag_match_mask @@ -59,7 +60,12 @@ @manage_groups_rest_blueprint.route('/manage/groups/list', methods=['GET']) @ac_api_requires(Permissions.server_administrator) def manage_groups_index(): - groups = get_groups_list_hr_perms() + groups = get_groups_list() + groups = AuthorizationGroupSchema().dump(groups, many=True) + group_to_users = get_users_by_group_identifiers() + for group in groups: + group['group_permissions_list'] = ac_permission_to_list(group['group_permissions']) + group['group_members'] = group_to_users.get(group['group_id'], []) return response_success('', data=groups) 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 9e46e46bc..3f275fd89 100644 --- a/source/app/blueprints/rest/manage/manage_ioc_types_routes.py +++ b/source/app/blueprints/rest/manage/manage_ioc_types_routes.py @@ -20,7 +20,7 @@ from flask import Blueprint from flask import request -from app import db +from app.db import db from app.datamgmt.case.case_iocs_db import get_ioc_types_list from app.datamgmt.manage.manage_case_objs import search_ioc_type_by_name from app.iris_engine.utils.tracker import track_activity 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 3bd5f6e3f..adcb7d1f5 100644 --- a/source/app/blueprints/rest/manage/manage_server_settings_routes.py +++ b/source/app/blueprints/rest/manage/manage_server_settings_routes.py @@ -22,7 +22,7 @@ from app import app from app import celery -from app import db +from app.db import db from app.datamgmt.manage.manage_srv_settings_db import get_srv_settings from app.iris_engine.backup.backup import backup_iris_db from app.iris_engine.updater.updater import remove_periodic_update_checks diff --git a/source/app/blueprints/rest/manage/manage_templates_routes.py b/source/app/blueprints/rest/manage/manage_templates_routes.py index 9471fc69e..467ce34a8 100644 --- a/source/app/blueprints/rest/manage/manage_templates_routes.py +++ b/source/app/blueprints/rest/manage/manage_templates_routes.py @@ -28,7 +28,7 @@ from werkzeug.utils import secure_filename from app import app -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.iris_engine.utils.tracker import track_activity from app.models.authorization import Permissions diff --git a/source/app/blueprints/rest/manage/manage_users.py b/source/app/blueprints/rest/manage/manage_users.py index a32be8e20..240f36769 100644 --- a/source/app/blueprints/rest/manage/manage_users.py +++ b/source/app/blueprints/rest/manage/manage_users.py @@ -23,7 +23,7 @@ from flask import request from app import app -from app import db +from app.db import db from app.blueprints.rest.parsing import parse_comma_separated_identifiers from app.blueprints.rest.endpoints import endpoint_deprecated from app.blueprints.iris_user import iris_current_user diff --git a/source/app/blueprints/rest/parsing.py b/source/app/blueprints/rest/parsing.py index 65675d17f..2f8c3e8f6 100644 --- a/source/app/blueprints/rest/parsing.py +++ b/source/app/blueprints/rest/parsing.py @@ -51,4 +51,3 @@ def parse_pagination_parameters(request: Request, default_order_by=None, default direction = arguments.get('sort_dir', default_direction, type=str) return PaginationParameters(page, per_page, order_by, direction) - diff --git a/source/app/blueprints/rest/profile_routes.py b/source/app/blueprints/rest/profile_routes.py index d85f9f927..ac33637c5 100644 --- a/source/app/blueprints/rest/profile_routes.py +++ b/source/app/blueprints/rest/profile_routes.py @@ -22,12 +22,11 @@ from flask import request from flask import session -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_users_db import get_user from app.datamgmt.manage.manage_users_db import get_user_primary_org from app.datamgmt.manage.manage_users_db import update_user -from app.iris_engine.access_control.utils import ac_current_user_has_permission from app.iris_engine.access_control.utils import ac_get_effective_permissions_of_user from app.iris_engine.access_control.utils import ac_recompute_effective_ac from app.iris_engine.utils.tracker import track_activity @@ -36,6 +35,7 @@ from app.schema.marshables import UserSchema from app.schema.marshables import BasicUserSchema from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.responses import response_error from app.blueprints.responses import response_success from app.blueprints.rest.endpoints import endpoint_removed diff --git a/source/app/blueprints/rest/reports_route.py b/source/app/blueprints/rest/reports_route.py index 3451b943c..53fdd1115 100644 --- a/source/app/blueprints/rest/reports_route.py +++ b/source/app/blueprints/rest/reports_route.py @@ -22,8 +22,8 @@ from flask import request from flask import send_file -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.business.reports.reports import generate_investigation_report, generate_activities_report from app.models.authorization import CaseAccessLevel diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index afd2eea6d..3c8223cc6 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -17,11 +17,14 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from flask import Blueprint +from flask import session from flask import request from flask import Response from marshmallow.exceptions import ValidationError from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_customer_access +from app.blueprints.access_controls import ac_current_user_has_permission from app.blueprints.rest.endpoints import response_api_success from app.blueprints.rest.endpoints import response_api_paginated from app.blueprints.rest.endpoints import response_api_error @@ -41,9 +44,8 @@ from app.business.alerts import alerts_update from app.business.alerts import alerts_delete from app.business.alerts import related_alerts_get -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError -from app.business.access_controls import access_controls_user_has_customer_access +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError class AlertsOperations: @@ -92,8 +94,11 @@ def search(self): else: fields = None + user_identifier_filter = iris_current_user.id + if ac_current_user_has_permission(Permissions.server_administrator): + user_identifier_filter = None + filtered_alerts = alerts_search( - iris_current_user.id, request.args.get('creation_start_date'), request.args.get('creation_end_date'), request.args.get('source_start_date'), @@ -114,6 +119,7 @@ def search(self): request.args.get('alert_resolution_id', type=int), request.args.get('source_reference'), request.args.get('custom_conditions'), + user_identifier_filter, page, per_page, request.args.get('sort') @@ -147,7 +153,7 @@ def create(self): alert = self._schema.load(request_data) result = alerts_create(alert, iocs, assets) - if not access_controls_user_has_customer_access(iris_current_user, result.alert_customer_id): + if not ac_current_user_has_customer_access(result.alert_customer_id): return response_api_error('User not entitled to create alerts for the client') return response_api_created(self._schema.dump(result)) @@ -157,10 +163,10 @@ def create(self): except BusinessProcessingError as e: return response_api_error(e.get_message(), data=e.get_data()) - def get(self, identifier): + def read(self, identifier): try: - alert = alerts_get(iris_current_user, identifier) + alert = alerts_get(iris_current_user, session['permissions'], identifier) return response_api_success(self._schema.dump(alert)) except ObjectNotFoundError: @@ -169,7 +175,7 @@ def get(self, identifier): def get_related_alerts(self, identifier): try: - alert = alerts_get(iris_current_user, identifier) + alert = alerts_get(iris_current_user, session['permissions'], identifier) open_alerts = request.args.get('open-alerts', 'false').lower() == 'true' open_cases = request.args.get('open-cases', 'false').lower() == 'true' @@ -192,7 +198,7 @@ def get_related_alerts(self, identifier): def update(self, identifier): try: - alert = alerts_get(iris_current_user, identifier) + alert = alerts_get(iris_current_user, session['permissions'], identifier) request_data = request.get_json() updated_alert = self._schema.load(request_data, instance=alert, partial=True) activity_data = [] @@ -226,7 +232,7 @@ def update(self, identifier): def delete(self, identifier): try: - alert = alerts_get(iris_current_user, identifier) + alert = alerts_get(iris_current_user, session['permissions'], identifier) alerts_delete(alert) return response_api_deleted() @@ -255,7 +261,7 @@ def create_alert(): @alerts_blueprint.get('/') @ac_api_requires(Permissions.alerts_read) def get_alert(identifier): - return alerts_operations.get(identifier) + return alerts_operations.read(identifier) @alerts_blueprint.put('/') diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py new file mode 100644 index 000000000..f2ef77796 --- /dev/null +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -0,0 +1,132 @@ +# 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 marshmallow import ValidationError + +from app.blueprints.access_controls import ac_api_requires +from app.blueprints.rest.endpoints import response_api_created +from app.blueprints.rest.endpoints import response_api_error +from app.blueprints.rest.endpoints import response_api_success +from app.blueprints.rest.endpoints import response_api_not_found +from app.blueprints.rest.endpoints import response_api_deleted +from app.blueprints.iris_user import iris_current_user + + +from app.schema.marshables import SavedFilterSchema +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.business.alerts_filters import alert_filter_add +from app.business.alerts_filters import alert_filter_get +from app.business.alerts_filters import alert_filter_update +from app.business.alerts_filters import alert_filter_delete + + +class AlertsFiltersOperations: + + def __init__(self): + self._schema = SavedFilterSchema() + + def _load(self, request_data, **kwargs): + return self._schema.load(request_data, **kwargs) + + def create(self): + request_data = request.get_json() + request_data ['created_by'] = iris_current_user.id + + try: + new_saved_filter = self._load(request_data) + alert_filter_add(new_saved_filter) + return response_api_created(self._schema.dump(new_saved_filter)) + + except ValidationError as e: + return response_api_error('Data error', e.messages) + + except BusinessProcessingError as e: + return response_api_error(e.get_message(), data=e.get_data()) + + def get(self, identifier): + try: + saved_filter = alert_filter_get(identifier) + return response_api_success(self._schema.dump(saved_filter)) + + except ObjectNotFoundError: + return response_api_not_found() + + except BusinessProcessingError as e: + return response_api_error(e.get_message(), data=e.get_data()) + + def put(self, identifier): + request_data = request.get_json() + + try: + saved_filter = alert_filter_get(identifier) + new_saved_filter = self._load(request_data, instance=saved_filter, partial=True) + alert_filter_update() + return response_api_success(self._schema.dump(new_saved_filter)) + + except ValidationError as e: + return response_api_error('Data error', data=e.messages) + + except ObjectNotFoundError: + return response_api_not_found() + + except BusinessProcessingError as e: + return response_api_error(e.get_message(), data=e.get_data()) + + @staticmethod + def delete(identifier): + try: + saved_filter = alert_filter_get(identifier) + alert_filter_delete(saved_filter) + return response_api_deleted() + + except ObjectNotFoundError: + return response_api_not_found() + + except BusinessProcessingError as e: + return response_api_error(e.get_message(), data=e.get_data()) + + +alerts_filters_blueprint = Blueprint('alerts_filters_rest_v2', __name__, url_prefix='/alerts-filters') +alerts_filters_operations = AlertsFiltersOperations() + + +@alerts_filters_blueprint.post('') +@ac_api_requires() +def create_alert_filter(): + return alerts_filters_operations.create() + + +@alerts_filters_blueprint.get('/') +@ac_api_requires() +def get_alert_filter(identifier): + return alerts_filters_operations.get(identifier) + + +@alerts_filters_blueprint.put('/') +@ac_api_requires() +def update_alert_filter(identifier): + return alerts_filters_operations.put(identifier) + + +@alerts_filters_blueprint.delete('/') +@ac_api_requires() +def delete_alert_filter(identifier): + return alerts_filters_operations.delete(identifier) diff --git a/source/app/blueprints/rest/v2/alerts_routes/comments.py b/source/app/blueprints/rest/v2/alerts_routes/comments.py index b7e98c7f9..e62c52778 100644 --- a/source/app/blueprints/rest/v2/alerts_routes/comments.py +++ b/source/app/blueprints/rest/v2/alerts_routes/comments.py @@ -17,6 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from flask import Blueprint +from flask import session from flask import request from marshmallow.exceptions import ValidationError @@ -39,7 +40,7 @@ from app.business.alerts import alerts_get from app.business.alerts import alerts_exists from app.blueprints.iris_user import iris_current_user -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError class CommentsOperations: @@ -50,7 +51,7 @@ def __init__(self): def search(self, alert_identifier): pagination_parameters = parse_pagination_parameters(request) try: - comments = comments_get_filtered_by_alert(iris_current_user, alert_identifier, pagination_parameters) + comments = comments_get_filtered_by_alert(iris_current_user, session['permissions'], alert_identifier, pagination_parameters) return response_api_paginated(self._schema, comments) except ObjectNotFoundError: return response_api_not_found() @@ -58,7 +59,7 @@ def search(self, alert_identifier): def create(self, alert_identifier): try: comment = self._schema.load(request.get_json()) - comments_create_for_alert(iris_current_user, comment, alert_identifier) + comments_create_for_alert(iris_current_user, session['permissions'], comment, alert_identifier) result = self._schema.dump(comment) return response_api_created(result) except ValidationError as e: @@ -68,7 +69,7 @@ def create(self, alert_identifier): def read(self, alert_identifier, identifier): try: - alert = alerts_get(iris_current_user, alert_identifier) + alert = alerts_get(iris_current_user, session['permissions'], alert_identifier) comment = comments_get_for_alert(alert, identifier) result = self._schema.dump(comment) return response_api_success(result) @@ -78,13 +79,13 @@ def read(self, alert_identifier, identifier): return response_api_not_found() def update(self, alert_identifier, identifier): - if not alerts_exists(iris_current_user, alert_identifier): + if not alerts_exists(iris_current_user, session['permissions'], alert_identifier): return response_api_not_found() return case_comment_update(identifier, 'events', None) def delete(self, alert_identifier, identifier): try: - alert = alerts_get(iris_current_user, alert_identifier) + alert = alerts_get(iris_current_user, session['permissions'], alert_identifier) comment = comments_get_for_alert(alert, identifier) if comment.comment_user_id != iris_current_user.id: return ac_api_return_access_denied() diff --git a/source/app/blueprints/rest/v2/assets.py b/source/app/blueprints/rest/v2/assets.py index d92b24ab5..5ffc14de2 100644 --- a/source/app/blueprints/rest/v2/assets.py +++ b/source/app/blueprints/rest/v2/assets.py @@ -26,8 +26,8 @@ from app.blueprints.rest.v2.assets_routes.comments import assets_comments_blueprint 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.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseAssetsSchema from app.blueprints.access_controls import ac_api_return_access_denied diff --git a/source/app/blueprints/rest/v2/assets_routes/comments.py b/source/app/blueprints/rest/v2/assets_routes/comments.py index c9014b2e8..a52942571 100644 --- a/source/app/blueprints/rest/v2/assets_routes/comments.py +++ b/source/app/blueprints/rest/v2/assets_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_delete_for_asset from app.blueprints.rest.case_comments import case_comment_update from app.business.assets import assets_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.blueprints.iris_user import iris_current_user from app.models.authorization import CaseAccessLevel diff --git a/source/app/blueprints/rest/v2/auth.py b/source/app/blueprints/rest/v2/auth.py index cc36e0529..43f86270e 100644 --- a/source/app/blueprints/rest/v2/auth.py +++ b/source/app/blueprints/rest/v2/auth.py @@ -26,10 +26,10 @@ from oic.oauth2.exception import GrantError from app import app -from app import db +from app.db import db from app import oidc_client from app.blueprints.iris_user import iris_current_user -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.logger import logger from app.blueprints.access_controls import is_authentication_ldap from app.blueprints.access_controls import is_authentication_oidc diff --git a/source/app/blueprints/rest/v2/case_routes/assets.py b/source/app/blueprints/rest/v2/case_routes/assets.py index a8263ffa6..a13e21895 100644 --- a/source/app/blueprints/rest/v2/case_routes/assets.py +++ b/source/app/blueprints/rest/v2/case_routes/assets.py @@ -36,8 +36,8 @@ from app.business.assets import assets_get from app.business.assets import assets_update from app.business.assets import assets_delete -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseAssetsSchema diff --git a/source/app/blueprints/rest/v2/case_routes/events.py b/source/app/blueprints/rest/v2/case_routes/events.py index 5de3d9d62..f47fc4a15 100644 --- a/source/app/blueprints/rest/v2/case_routes/events.py +++ b/source/app/blueprints/rest/v2/case_routes/events.py @@ -20,7 +20,8 @@ from flask import request from marshmallow.exceptions import ValidationError -from app.blueprints.access_controls import ac_api_requires, ac_fast_check_current_user_has_case_access +from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access from app.blueprints.rest.endpoints import response_api_created from app.blueprints.rest.endpoints import response_api_success from app.blueprints.rest.endpoints import response_api_deleted @@ -33,8 +34,8 @@ from app.business.events import events_delete from app.models.cases import CasesEvent from app.schema.marshables import EventSchema -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.cases import cases_exists from app.iris_engine.utils.collab import notify from app.models.authorization import CaseAccessLevel @@ -82,7 +83,7 @@ def create(self, case_identifier): except ValidationError as e: return response_api_error('Data error', data=e.normalized_messages()) - def get(self, case_identifier, identifier): + def read(self, case_identifier, identifier): if not cases_exists(case_identifier): return response_api_not_found() @@ -163,7 +164,7 @@ def create_event(case_identifier): @case_events_blueprint.get('/') @ac_api_requires() def get_event(case_identifier, identifier): - return events.get(case_identifier, identifier) + return events.read(case_identifier, identifier) @case_events_blueprint.put('/') diff --git a/source/app/blueprints/rest/v2/case_routes/evidences.py b/source/app/blueprints/rest/v2/case_routes/evidences.py index 03d712361..2bd350f18 100644 --- a/source/app/blueprints/rest/v2/case_routes/evidences.py +++ b/source/app/blueprints/rest/v2/case_routes/evidences.py @@ -23,8 +23,8 @@ from app.blueprints.access_controls import ac_api_requires from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access from app.models.authorization import CaseAccessLevel -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.blueprints.rest.parsing import parse_pagination_parameters from app.blueprints.access_controls import ac_api_return_access_denied from app.blueprints.rest.endpoints import response_api_created @@ -159,7 +159,7 @@ def delete(self, case_identifier, identifier): @case_evidences_blueprint.get('') @ac_api_requires() -def get_evidences(case_identifier): +def search_evidences(case_identifier): return evidences_operations.search(case_identifier) diff --git a/source/app/blueprints/rest/v2/case_routes/iocs.py b/source/app/blueprints/rest/v2/case_routes/iocs.py index 64292cebb..903ff5e66 100644 --- a/source/app/blueprints/rest/v2/case_routes/iocs.py +++ b/source/app/blueprints/rest/v2/case_routes/iocs.py @@ -31,8 +31,8 @@ from app.blueprints.rest.endpoints import response_api_paginated from app.blueprints.rest.parsing import parse_pagination_parameters from app.blueprints.rest.parsing import parse_fields_parameters -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.iocs import iocs_create from app.business.iocs import iocs_get from app.business.iocs import iocs_delete diff --git a/source/app/blueprints/rest/v2/case_routes/notes.py b/source/app/blueprints/rest/v2/case_routes/notes.py index ebb6fb61a..6936d4c18 100644 --- a/source/app/blueprints/rest/v2/case_routes/notes.py +++ b/source/app/blueprints/rest/v2/case_routes/notes.py @@ -35,8 +35,8 @@ from app.business.notes import notes_update from app.business.notes import notes_delete from app.business.cases import cases_exists -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook diff --git a/source/app/blueprints/rest/v2/case_routes/notes_directories.py b/source/app/blueprints/rest/v2/case_routes/notes_directories.py index 38359f871..c288dc1ff 100644 --- a/source/app/blueprints/rest/v2/case_routes/notes_directories.py +++ b/source/app/blueprints/rest/v2/case_routes/notes_directories.py @@ -30,8 +30,8 @@ from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import response_api_paginated from app.blueprints.access_controls import ac_api_return_access_denied -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.schema.marshables import CaseNoteDirectorySchema from app.schema.marshables import SearchCaseNoteDirectorySchema from app.business.notes_directories import notes_directories_create diff --git a/source/app/blueprints/rest/v2/case_routes/tasks.py b/source/app/blueprints/rest/v2/case_routes/tasks.py index d3fa1b700..cf1a658a8 100644 --- a/source/app/blueprints/rest/v2/case_routes/tasks.py +++ b/source/app/blueprints/rest/v2/case_routes/tasks.py @@ -30,8 +30,8 @@ from app.blueprints.access_controls import ac_api_return_access_denied, ac_fast_check_current_user_has_case_access from app.blueprints.access_controls import ac_api_requires from app.schema.marshables import CaseTaskSchema -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.tasks import tasks_create from app.business.tasks import tasks_get from app.business.tasks import tasks_update diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index 0df9d52e6..9d9b7c5ba 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -42,16 +42,16 @@ from app.business.cases import cases_delete from app.business.cases import cases_get_by_identifier from app.business.cases import cases_update -from app.business.errors import BusinessProcessingError, ObjectNotFoundError +from app.models.errors import BusinessProcessingError, ObjectNotFoundError from app.business.cases import cases_filter from app.schema.marshables import CaseSchemaForAPIV2 from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_customer_access from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access from app.blueprints.access_controls import ac_api_return_access_denied from app.models.authorization import Permissions from app.models.authorization import CaseAccessLevel from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook -from app.business.access_controls import access_controls_user_has_customer_access class CasesOperations: @@ -101,10 +101,10 @@ def search(self): def create(self): try: - request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json(), None) + request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json()) case = self._schema.load(request_data) case_template_id = request_data.pop('case_template_id', None) - case = cases_create(case, case_template_id) + case = cases_create(iris_current_user, case, case_template_id) result = self._schema.dump(case) return response_api_created(result) except ValidationError as e: @@ -135,7 +135,7 @@ def update(self, identifier): customer_identifier = request_data.get('case_customer_id') # If user tries to update the customer, check if the user has access to the new customer if customer_identifier and customer_identifier != case.client_id: - if not access_controls_user_has_customer_access(iris_current_user, customer_identifier): + if not ac_current_user_has_customer_access(customer_identifier): raise BusinessProcessingError('Invalid customer ID. Permission denied.') if 'case_name' in request_data: diff --git a/source/app/blueprints/rest/v2/dashboard.py b/source/app/blueprints/rest/v2/dashboard.py index d026805c7..5eda20964 100644 --- a/source/app/blueprints/rest/v2/dashboard.py +++ b/source/app/blueprints/rest/v2/dashboard.py @@ -20,6 +20,7 @@ from flask import request from app.blueprints.access_controls import ac_api_requires +from app.blueprints.iris_user import iris_current_user from app.blueprints.rest.endpoints import response_api_success from app.business.cases import cases_filter_by_user from app.business.cases import cases_filter_by_reviewer @@ -39,14 +40,14 @@ @ac_api_requires() def list_own_cases(): show_closed = request.args.get('show_closed', 'false', type=str).lower() - cases = cases_filter_by_user(show_closed == 'true') + cases = cases_filter_by_user(iris_current_user, show_closed == 'true') return response_api_success(data=CaseDetailsSchema(many=True).dump(cases)) # TODO this endpoint does not adhere to the conventions (verb in URL). # We should rather have /api/v2/tasks? -@dashboard_blueprint.route('/tasks/list', methods=['GET']) +@dashboard_blueprint.get('/tasks/list') @ac_api_requires() def list_own_tasks(): ct = tasks_filter_by_user() @@ -55,10 +56,10 @@ def list_own_tasks(): # TODO this endpoint does not adhere to the conventions (verb in URL). # We should rather have /api/v2/reviews? -@dashboard_blueprint.route('/reviews/list', methods=['GET']) +@dashboard_blueprint.get('/reviews/list') @ac_api_requires() def list_own_reviews(): - reviews = cases_filter_by_reviewer() + reviews = cases_filter_by_reviewer(iris_current_user) return response_api_success( data=CaseSchema( many=True, diff --git a/source/app/blueprints/rest/v2/events_routes/comments.py b/source/app/blueprints/rest/v2/events_routes/comments.py index f3a9d45eb..ae428fae7 100644 --- a/source/app/blueprints/rest/v2/events_routes/comments.py +++ b/source/app/blueprints/rest/v2/events_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_get_for_event from app.business.comments import comments_delete_for_event from app.business.events import events_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.models.cases import CasesEvent from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel diff --git a/source/app/blueprints/rest/v2/evidences_routes/comments.py b/source/app/blueprints/rest/v2/evidences_routes/comments.py index 139698ef9..3db44e012 100644 --- a/source/app/blueprints/rest/v2/evidences_routes/comments.py +++ b/source/app/blueprints/rest/v2/evidences_routes/comments.py @@ -36,7 +36,7 @@ from app.business.comments import comments_delete_for_evidence from app.models.models import CaseReceivedFile from app.business.evidences import evidences_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel from app.blueprints.rest.case_comments import case_comment_update diff --git a/source/app/blueprints/rest/v2/global_tasks.py b/source/app/blueprints/rest/v2/global_tasks.py new file mode 100644 index 000000000..0c2b90623 --- /dev/null +++ b/source/app/blueprints/rest/v2/global_tasks.py @@ -0,0 +1,92 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 marshmallow import ValidationError + +from app.blueprints.iris_user import iris_current_user +from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook +from app.schema.marshables import GlobalTasksSchema +from app.blueprints.access_controls import ac_api_requires +from app.blueprints.rest.endpoints import response_api_error +from app.blueprints.rest.endpoints import response_api_created +from app.blueprints.rest.endpoints import response_api_success +from app.blueprints.rest.endpoints import response_api_not_found +from app.blueprints.rest.endpoints import response_api_deleted +from app.business.global_tasks import global_tasks_create +from app.business.global_tasks import global_tasks_get +from app.business.global_tasks import global_tasks_delete +from app.models.errors import ObjectNotFoundError + + +class GlobalTasksOperations: + + def __init__(self): + self._schema = GlobalTasksSchema() + + def create(self): + request_data = call_deprecated_on_preload_modules_hook('global_task_create', request.get_json()) + try: + global_task = self._schema.load(request_data) + global_task = global_tasks_create(iris_current_user, global_task) + result = self._schema.dump(global_task) + return response_api_created(result) + except ValidationError as e: + return response_api_error('Data error', data=e.messages) + + def read(self, identifier): + try: + global_task = global_tasks_get(identifier) + result = self._schema.dump(global_task) + return response_api_success(result) + except ObjectNotFoundError: + return response_api_not_found() + + @staticmethod + def delete(identifier): + try: + global_task = global_tasks_get(identifier) + global_tasks_delete(global_task) + return response_api_deleted() + + except ObjectNotFoundError: + return response_api_not_found() + + +global_tasks_blueprint = Blueprint('global_tasks_rest_v2', __name__, url_prefix='/global-tasks') + +global_tasks_operations = GlobalTasksOperations() + + +@global_tasks_blueprint.post('') +@ac_api_requires() +def create_global_task(): + return global_tasks_operations.create() + + +@global_tasks_blueprint.get('/') +@ac_api_requires() +def get_global_task(identifier): + return global_tasks_operations.read(identifier) + + +@global_tasks_blueprint.delete('/') +@ac_api_requires() +def delete_global_task(identifier): + return global_tasks_operations.delete(identifier) diff --git a/source/app/blueprints/rest/v2/iocs.py b/source/app/blueprints/rest/v2/iocs.py index e43231497..1a08d961b 100644 --- a/source/app/blueprints/rest/v2/iocs.py +++ b/source/app/blueprints/rest/v2/iocs.py @@ -25,8 +25,8 @@ from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import response_api_success -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.iocs import iocs_update from app.business.iocs import iocs_delete from app.business.iocs import iocs_get diff --git a/source/app/blueprints/rest/v2/iocs_routes/comments.py b/source/app/blueprints/rest/v2/iocs_routes/comments.py index ffa5e6131..e4088fce4 100644 --- a/source/app/blueprints/rest/v2/iocs_routes/comments.py +++ b/source/app/blueprints/rest/v2/iocs_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_get_for_ioc from app.business.comments import comments_delete_for_ioc from app.business.iocs import iocs_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel from app.blueprints.rest.case_comments import case_comment_update diff --git a/source/app/blueprints/rest/v2/manage.py b/source/app/blueprints/rest/v2/manage.py index 08f024935..f24bd02b5 100644 --- a/source/app/blueprints/rest/v2/manage.py +++ b/source/app/blueprints/rest/v2/manage.py @@ -18,13 +18,12 @@ from flask import Blueprint -from app.blueprints.rest.v2.manage_routes.groups import create_groups_blueprint +from app.blueprints.rest.v2.manage_routes.groups import groups_blueprint from app.blueprints.rest.v2.manage_routes.users import users_blueprint from app.blueprints.rest.v2.manage_routes.customers import customers_blueprint manage_v2_blueprint = Blueprint('manage', __name__, url_prefix='/manage') -groups_blueprint = create_groups_blueprint() manage_v2_blueprint.register_blueprint(groups_blueprint) manage_v2_blueprint.register_blueprint(users_blueprint) manage_v2_blueprint.register_blueprint(customers_blueprint) diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index 830a7a0de..e8b6cadbc 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -22,35 +22,111 @@ from app.blueprints.rest.endpoints import response_api_created 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.blueprints.rest.endpoints import response_api_paginated +from app.blueprints.rest.endpoints import response_api_deleted from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_current_user_has_permission from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema -from app.business.customers import customers_create +from app.models.errors import ObjectNotFoundError +from app.models.errors import ElementInUseError +from app.business.customers import customers_create_with_user +from app.business.customers import customers_filter +from app.business.customers import customers_get +from app.business.customers import customers_update +from app.business.customers import customers_delete from app.blueprints.iris_user import iris_current_user +from app.blueprints.rest.parsing import parse_pagination_parameters -class Customers: +class CustomersOperations: def __init__(self): self._schema = CustomerSchema() + def search(self): + pagination_parameters = parse_pagination_parameters(request) + user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) + customers = customers_filter(iris_current_user, pagination_parameters, user_is_server_administrator) + return response_api_paginated(self._schema, customers) + def create(self): try: request_data = request.get_json() customer = self._schema.load(request_data) - customers_create(iris_current_user, customer) + customers_create_with_user(iris_current_user, customer) result = self._schema.dump(customer) return response_api_created(result) except ValidationError as e: return response_api_error('Data error', data=e.messages) + def read(self, identifier): + try: + customer = customers_get(identifier) + result = self._schema.dump(customer) + return response_api_success(result) + except ObjectNotFoundError: + return response_api_not_found() + + def update(self, identifier): + try: + customer = customers_get(identifier) + request_data = request.get_json() + request_data['customer_id'] = identifier + updated_customer = self._schema.load(request_data, instance=customer) + customers_update() + result = self._schema.dump(updated_customer) + return response_api_success(result) + except ValidationError as e: + return response_api_error('Data error', data=e.messages) + except ObjectNotFoundError: + return response_api_not_found() + + @staticmethod + def delete(identifier): + try: + customer = customers_get(identifier) + customers_delete(customer) + return response_api_deleted() + + except ObjectNotFoundError: + return response_api_not_found() + except ElementInUseError as e: + return response_api_error(e.get_message()) + customers_blueprint = Blueprint('customers_rest_v2', __name__, url_prefix='/customers') -customers = Customers() +customers_operations = CustomersOperations() + + +@customers_blueprint.get('') +@ac_api_requires(Permissions.customers_read) +def search_evidences(): + return customers_operations.search() @customers_blueprint.post('') @ac_api_requires(Permissions.customers_write) def create_customer(): - return customers.create() + return customers_operations.create() + + +@customers_blueprint.get('/') +@ac_api_requires(Permissions.customers_read) +def get_customer(identifier): + return customers_operations.read(identifier) + + +@customers_blueprint.put('/') +@ac_api_requires(Permissions.customers_write) +def put_customer(identifier): + return customers_operations.update(identifier) + + +@customers_blueprint.delete('/') +@ac_api_requires(Permissions.customers_write) +def delete_user(identifier): + return customers_operations.delete(identifier) diff --git a/source/app/blueprints/rest/v2/manage_routes/groups.py b/source/app/blueprints/rest/v2/manage_routes/groups.py index ba6ba727d..1a05ed379 100644 --- a/source/app/blueprints/rest/v2/manage_routes/groups.py +++ b/source/app/blueprints/rest/v2/manage_routes/groups.py @@ -25,7 +25,7 @@ from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.rest.endpoints import response_api_deleted -from app.blueprints.access_controls import wrap_with_permission_checks +from app.blueprints.access_controls import ac_api_requires from app.schema.marshables import AuthorizationGroupSchema from app.business.groups import groups_create from app.business.groups import groups_get @@ -33,8 +33,8 @@ from app.business.groups import groups_delete from app.models.authorization import Permissions from app.models.authorization import ac_flag_match_mask -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.blueprints.iris_user import iris_current_user from app.iris_engine.access_control.utils import ac_ldp_group_update @@ -54,7 +54,7 @@ def create(self): except ValidationError as e: return response_api_error('Data error', data=e.messages) - def get(self, identifier): + def read(self, identifier): try: group = groups_get(identifier) result = self._schema.dump(group) @@ -81,7 +81,7 @@ def update(self, identifier): return response_api_not_found() def delete(self, identifier): - try : + try: group = groups_get(identifier) groups_delete(iris_current_user, group) return response_api_deleted() @@ -92,20 +92,29 @@ def delete(self, identifier): return response_api_error(e.get_message()) -def create_groups_blueprint(): - blueprint = Blueprint('rest_v2_groups', __name__, url_prefix='/groups') - groups = Groups() +groups_blueprint = Blueprint('rest_v2_groups', __name__, url_prefix='/groups') +groups = Groups() + + +@groups_blueprint.post('') +@ac_api_requires(Permissions.server_administrator) +def create_group(): + return groups.create() + - create_group = wrap_with_permission_checks(groups.create, Permissions.server_administrator) - blueprint.add_url_rule('', view_func=create_group, methods=['POST']) +@groups_blueprint.get('/') +@ac_api_requires(Permissions.server_administrator) +def read_group(identifier): + return groups.read(identifier) - get_group = wrap_with_permission_checks(groups.get, Permissions.server_administrator) - blueprint.add_url_rule('/', view_func=get_group, methods=['GET']) - update_group = wrap_with_permission_checks(groups.update, Permissions.server_administrator) - blueprint.add_url_rule('/', view_func=update_group, methods=['PUT']) +@groups_blueprint.put('/') +@ac_api_requires(Permissions.server_administrator) +def update_group(identifier): + return groups.update(identifier) - delete_group = wrap_with_permission_checks(groups.delete, Permissions.server_administrator) - blueprint.add_url_rule('/', view_func=delete_group, methods=['DELETE']) - return blueprint +@groups_blueprint.delete('/') +@ac_api_requires(Permissions.server_administrator) +def delete_group(identifier): + return groups.delete(identifier) diff --git a/source/app/blueprints/rest/v2/manage_routes/users.py b/source/app/blueprints/rest/v2/manage_routes/users.py index e2d7c4d9e..d59a4b6d6 100644 --- a/source/app/blueprints/rest/v2/manage_routes/users.py +++ b/source/app/blueprints/rest/v2/manage_routes/users.py @@ -28,8 +28,8 @@ from app.blueprints.rest.endpoints import response_api_deleted from app.schema.marshables import UserSchemaForAPIV2 from app.models.authorization import Permissions -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.business.users import users_create from app.business.users import users_get from app.business.users import users_update @@ -54,7 +54,6 @@ def create(self): return response_api_error('Data error', data=e.messages) def read(self, identifier): - try: user = users_get(identifier) result = self._schema.dump(user) @@ -80,7 +79,7 @@ def update(self, identifier): return response_api_not_found() def delete(self, identifier): - try : + try: user = users_get(identifier) users_delete(user) return response_api_deleted() diff --git a/source/app/blueprints/rest/v2/notes_routes/comments.py b/source/app/blueprints/rest/v2/notes_routes/comments.py index 67b98ea38..534b8c0f0 100644 --- a/source/app/blueprints/rest/v2/notes_routes/comments.py +++ b/source/app/blueprints/rest/v2/notes_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_get_for_note from app.business.comments import comments_delete_for_note from app.business.notes import notes_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel from app.blueprints.rest.case_comments import case_comment_update diff --git a/source/app/blueprints/rest/v2/tags.py b/source/app/blueprints/rest/v2/tags.py index 42475ff54..3b2cef1c2 100644 --- a/source/app/blueprints/rest/v2/tags.py +++ b/source/app/blueprints/rest/v2/tags.py @@ -23,7 +23,7 @@ from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.parsing import parse_fields_parameters from app.blueprints.rest.parsing import parse_pagination_parameters -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.business.tags import tags_filter from app.schema.marshables import TagsSchema from app.blueprints.access_controls import ac_api_requires diff --git a/source/app/blueprints/rest/v2/tasks.py b/source/app/blueprints/rest/v2/tasks.py index a0ee2755c..527a23b6b 100644 --- a/source/app/blueprints/rest/v2/tasks.py +++ b/source/app/blueprints/rest/v2/tasks.py @@ -27,8 +27,8 @@ from app.blueprints.access_controls import ac_api_return_access_denied from app.business.tasks import tasks_delete from app.business.tasks import tasks_get -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.models.authorization import CaseAccessLevel from app.schema.marshables import CaseTaskSchema from app.blueprints.rest.v2.tasks_routes.comments import tasks_comments_blueprint diff --git a/source/app/blueprints/rest/v2/tasks_routes/comments.py b/source/app/blueprints/rest/v2/tasks_routes/comments.py index d18d18529..1e2db5909 100644 --- a/source/app/blueprints/rest/v2/tasks_routes/comments.py +++ b/source/app/blueprints/rest/v2/tasks_routes/comments.py @@ -35,7 +35,7 @@ from app.business.comments import comments_get_for_task from app.business.comments import comments_delete_for_task from app.business.tasks import tasks_get -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.schema.marshables import CommentSchema from app.models.authorization import CaseAccessLevel from app.blueprints.rest.case_comments import case_comment_update diff --git a/source/app/business/access_controls.py b/source/app/business/access_controls.py index f340f711a..3797f62cb 100644 --- a/source/app/business/access_controls.py +++ b/source/app/business/access_controls.py @@ -16,15 +16,16 @@ # 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 db +from app.db import db from app.datamgmt.manage.manage_access_control_db import get_case_effective_access from app.datamgmt.manage.manage_access_control_db import remove_duplicate_user_case_effective_accesses -from app.datamgmt.manage.manage_access_control_db import set_user_case_effective_access +from app.datamgmt.manage.manage_access_control_db import add_user_case_effective_access from app.datamgmt.manage.manage_access_control_db import check_ua_case_client from app.datamgmt.manage.manage_access_control_db import user_has_client_access from app.logger import logger from app.models.authorization import UserCaseAccess +from app.models.authorization import ac_has_permission_server_administrator from app.models.authorization import CaseAccessLevel from app.models.authorization import ac_flag_match_mask @@ -64,7 +65,7 @@ def set_case_effective_access_for_user(user_id, case_id, access_level: int): if remove_duplicate_user_case_effective_accesses(user_id, case_id): logger.error(f'Multiple access found for user {user_id} and case {case_id}') - set_user_case_effective_access(access_level, case_id, user_id) + add_user_case_effective_access(user_id, case_id, access_level) def ac_fast_check_user_has_case_access(user_id, cid, expected_access_levels: list[CaseAccessLevel]): @@ -94,5 +95,8 @@ def ac_fast_check_user_has_case_access(user_id, cid, expected_access_levels: lis return None -def access_controls_user_has_customer_access(user, customer_identifier): +def access_controls_user_has_customer_access(user, permissions, customer_identifier): + if ac_has_permission_server_administrator(permissions): + return True + return user_has_client_access(user.id, customer_identifier) diff --git a/source/app/business/alerts.py b/source/app/business/alerts.py index fe9bea312..7acc44454 100644 --- a/source/app/business/alerts.py +++ b/source/app/business/alerts.py @@ -20,11 +20,12 @@ from datetime import datetime from typing import Optional -from app import db +from app.business.access_controls import access_controls_user_has_customer_access +from app.db import db from app import socket_io from app.models.alerts import Alert from app.models.iocs import Ioc -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.datamgmt.alerts.alerts_db import cache_similar_alert from app.datamgmt.alerts.alerts_db import delete_similar_alert_cache from app.datamgmt.alerts.alerts_db import delete_related_alerts_cache @@ -35,38 +36,38 @@ from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.util import add_obj_history_entry -from app.business.errors import ObjectNotFoundError -from app.datamgmt.manage.manage_access_control_db import user_has_client_access +from app.models.errors import ObjectNotFoundError -def alerts_search(user_identifier, start_date, end_date, source_start_date, source_end_date, title, description, - status, severity, owner, source, tags, case_identifier, customer, classification, alert_identifiers, - assets, iocs, resolution_status, source_reference, custom_conditions, page, per_page, sort): +def alerts_search(start_date, end_date, source_start_date, source_end_date, title, description, + status, severity, owner, source, tags, case_identifier, customer_identifier, classification, alert_identifiers, + assets, iocs, resolution_status, source_reference, custom_conditions, user_identifier_filter, page, per_page, sort): + return get_filtered_alerts( - start_date=start_date, - end_date=end_date, - source_start_date=source_start_date, - source_end_date=source_end_date, - title=title, - description=description, - status=status, - severity=severity, - owner=owner, - source=source, - tags=tags, - case_id=case_identifier, - client=customer, - classification=classification, - alert_ids=alert_identifiers, - assets=assets, - iocs=iocs, - resolution_status=resolution_status, - page=page, - per_page=per_page, - sort=sort, - current_user_id=user_identifier, - source_reference=source_reference, - custom_conditions=custom_conditions + start_date, + end_date, + source_start_date, + source_end_date, + title, + description, + status, + severity, + owner, + source, + tags, + case_identifier, + customer_identifier, + classification, + alert_identifiers, + assets, + iocs, + resolution_status, + page, + per_page, + sort, + user_identifier_filter, + source_reference, + custom_conditions ) @@ -84,7 +85,7 @@ def alerts_create(alert: Alert, iocs: list[Ioc], assets: list[CaseAssets]) -> Al cache_similar_alert(alert.alert_customer_id, assets, iocs, alert.alert_id, alert.alert_source_event_time) - alert = call_modules_hook('on_postload_alert_create', data=alert) + alert = call_modules_hook('on_postload_alert_create', alert) track_activity(f"created alert #{alert.alert_id} - {alert.alert_title}", ctx_less=True) @@ -95,17 +96,17 @@ def alerts_create(alert: Alert, iocs: list[Ioc], assets: list[CaseAssets]) -> Al return alert -def _get(user, identifier) -> Optional[Alert]: +def _get(user, permissions, identifier) -> Optional[Alert]: alert = get_alert_by_id(identifier) if not alert: return None - if not user_has_client_access(user.id, alert.alert_customer_id): + if not access_controls_user_has_customer_access(user, permissions, alert.alert_customer_id): return None return alert -def alerts_get(user, identifier) -> Alert: - alert = _get(user, identifier) +def alerts_get(user, permissions, identifier) -> Alert: + alert = _get(user, permissions, identifier) if not alert: raise ObjectNotFoundError() return alert @@ -118,8 +119,8 @@ def related_alerts_get(alert, open_alerts, closed_alerts, open_cases, closed_cas days_back, number_of_results) -def alerts_exists(user, identifier) -> bool: - alert = _get(user, identifier) +def alerts_exists(user, permissions, identifier) -> bool: + alert = _get(user, permissions, identifier) return alert is not None @@ -134,13 +135,13 @@ def alerts_update(alert: Alert, updated_alert: Alert, activity_data) -> Alert: if alert.alert_status_id != updated_alert.alert_status_id: do_status_hook = True - updated_alert = call_modules_hook('on_postload_alert_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_update', updated_alert) if do_resolution_hook: - updated_alert = call_modules_hook('on_postload_alert_resolution_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_resolution_update', updated_alert) if do_status_hook: - updated_alert = call_modules_hook('on_postload_alert_status_update', data=updated_alert) + updated_alert = call_modules_hook('on_postload_alert_status_update', updated_alert) if activity_data: activity_data_as_string = ','.join(activity_data) @@ -160,5 +161,5 @@ def alerts_delete(alert: Alert): delete_related_alerts_cache([alert.alert_id]) delete_alert(alert) - call_modules_hook('on_postload_alert_delete', data=alert.alert_id) + call_modules_hook('on_postload_alert_delete', alert.alert_id) track_activity(f'delete alert #{alert.alert_id}', ctx_less=True) diff --git a/source/tests/unit/blueprints/manage/test_manage_modules_routes.py b/source/app/business/alerts_filters.py similarity index 54% rename from source/tests/unit/blueprints/manage/test_manage_modules_routes.py rename to source/app/business/alerts_filters.py index e636d3722..09ee796a8 100644 --- a/source/tests/unit/blueprints/manage/test_manage_modules_routes.py +++ b/source/app/business/alerts_filters.py @@ -1,6 +1,6 @@ # IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net +# 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 @@ -16,21 +16,27 @@ # 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.db import db +from app.datamgmt.filters.filters_db import get_filter_by_id +from app.models.errors import ObjectNotFoundError -from unittest import TestCase -from app import app -from tests.test_helper import TestHelper +def alert_filter_add(new_saved_filter): + db.session.add(new_saved_filter) + db.session.commit() -app.testing = True +def alert_filter_get(identifier): + alert_filter = get_filter_by_id(identifier) + if not alert_filter: + raise ObjectNotFoundError() + return alert_filter -class TestManageModulesRoutes(TestCase): - def setUp(self) -> None: - self._test_helper = TestHelper() - def test_case_get_manage_modules_index_should_redirect_to_cid_1_if_no_cid_is_provided(self): - self._test_helper.verify_path_without_cid_redirects_correctly( - 'manage_module.manage_modules_index', - 'You should be redirected automatically to target URL: /manage/modules?cid=1' - ) +def alert_filter_update(): + db.session.commit() + + +def alert_filter_delete(saved_filter): + db.session.delete(saved_filter) + db.session.commit() diff --git a/source/app/business/asset_types.py b/source/app/business/asset_types.py index 55094e0e3..60b417ab8 100644 --- a/source/app/business/asset_types.py +++ b/source/app/business/asset_types.py @@ -16,7 +16,7 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from app.models.models import AssetsType +from app.models.assets import AssetsType from app.datamgmt.case.assets_type import add_asset_type from app.datamgmt.case.assets_type import exists_asset_type_with_name diff --git a/source/app/business/assets.py b/source/app/business/assets.py index c7a331e3d..13ec08193 100644 --- a/source/app/business/assets.py +++ b/source/app/business/assets.py @@ -18,12 +18,12 @@ from flask_sqlalchemy.pagination import Pagination -from app import db -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.db import db +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.cases import cases_exists from app.datamgmt.states import update_assets_state -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.pagination_parameters import PaginationParameters from app.datamgmt.case.case_assets_db import get_asset from app.datamgmt.case.case_assets_db import filter_assets @@ -47,7 +47,7 @@ def assets_create(user, case_identifier, asset: CaseAssets, ioc_links): errors, _ = set_ioc_links(ioc_links, asset.asset_id) if errors: raise BusinessProcessingError('Encountered errors while linking IOC. Asset has still been created.') - asset = call_modules_hook('on_postload_asset_create', data=asset, caseid=case_identifier) + asset = call_modules_hook('on_postload_asset_create', asset, caseid=case_identifier) add_obj_history_entry(asset, 'created') @@ -59,10 +59,10 @@ def assets_create(user, case_identifier, asset: CaseAssets, ioc_links): def assets_delete(asset: CaseAssets): - call_modules_hook('on_preload_asset_delete', data=asset.asset_id) + call_modules_hook('on_preload_asset_delete', asset.asset_id) # Deletes an asset and the potential links with the IoCs from the database delete_asset(asset) - call_modules_hook('on_postload_asset_delete', data=asset.asset_id, caseid=asset.case_id) + call_modules_hook('on_postload_asset_delete', asset.asset_id, caseid=asset.case_id) track_activity(f'removed asset ID {asset.asset_name}', caseid=asset.case_id) diff --git a/source/app/business/auth.py b/source/app/business/auth.py index 3b2b0382d..89e85ec60 100644 --- a/source/app/business/auth.py +++ b/source/app/business/auth.py @@ -26,7 +26,7 @@ from app import bc from app import app -from app import db +from app.db import db from app.business.cases import cases_get_by_identifier from app.business.cases import cases_get_first from app.logger import logger diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 8fec77c50..5ffc80433 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -19,18 +19,19 @@ import datetime import traceback -from app import db -from app.blueprints.iris_user import iris_current_user +from app.db import db from app.logger import logger from app.util import add_obj_history_entry -from app.models.models import ReviewStatusList -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.business.iocs import iocs_exports_to_json from app.iris_engine.module_handler.module_handler import call_modules_hook 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 list_user_cases +from app.datamgmt.case.case_db import case_db_save +from app.datamgmt.case.case_db import list_user_reviews 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 @@ -52,10 +53,11 @@ from app.datamgmt.reporter.report_db import export_case_tasks_json from app.datamgmt.reporter.report_db import export_case_comments_json from app.datamgmt.reporter.report_db import export_case_notes_json -from app.models.cases import Cases from app.datamgmt.manage.manage_cases_db import get_filtered_cases -from app.datamgmt.dashboard.dashboard_db import list_user_cases -from app.datamgmt.dashboard.dashboard_db import list_user_reviews +from app.datamgmt.case.case_db import get_first_case_with_customer +from app.models.cases import Cases +from app.models.cases import ReviewStatusList +from app.models.customers import Client def cases_filter(current_user, pagination_parameters, name, case_identifiers, customer_identifier, @@ -79,12 +81,12 @@ def cases_filter(current_user, pagination_parameters, name, case_identifiers, cu is_open=is_open) -def cases_filter_by_user(show_all: bool): - return list_user_cases(iris_current_user.id, show_all) +def cases_filter_by_user(user, show_all: bool): + return list_user_cases(user.id, show_all) -def cases_filter_by_reviewer(): - return list_user_reviews(iris_current_user.id) +def cases_filter_by_reviewer(user): + return list_user_reviews(user.id) def cases_get_by_identifier(case_identifier) -> Cases: @@ -98,12 +100,16 @@ def cases_get_first() -> Cases: return get_first_case() +def cases_get_first_with_customer(client: Client) -> Cases: + return get_first_case_with_customer(client.client_id) + + def cases_exists(identifier): return case_db_exists(identifier) -def cases_create(case: Cases, case_template_id) -> Cases: - case.owner_id = iris_current_user.id +def cases_create(user, case: Cases, case_template_id) -> Cases: + case.owner_id = user.id case.severity_id = 4 if case_template_id and len(case_template_id) > 0: @@ -113,7 +119,7 @@ def cases_create(case: Cases, case_template_id) -> Cases: case.state_id = get_case_state_by_name('Open').state_id - case.save() + case_db_save(case) if case_template_id and len(case_template_id) > 0: try: @@ -125,10 +131,9 @@ def cases_create(case: Cases, case_template_id) -> Cases: logger.error(e.__str__()) raise BusinessProcessingError(f'Unexpected error when loading template {case_template_id} to new case.') - ac_set_new_case_access(None, case.case_id, case.client_id) + ac_set_new_case_access(user, case.case_id, case.client_id) - # TODO remove caseid doesn't seems to be useful for call_modules_hook => remove argument - case = call_modules_hook('on_postload_case_create', case, None) + case = call_modules_hook('on_postload_case_create', case) add_obj_history_entry(case, 'created') track_activity(f'new case "{case.name}" created', caseid=case.case_id, ctx_less=False) @@ -144,12 +149,12 @@ def cases_delete(case_identifier): raise BusinessProcessingError('Cannot delete a primary case to keep consistency') try: - call_modules_hook('on_preload_case_delete', data=case_identifier, caseid=case_identifier) + call_modules_hook('on_preload_case_delete', case_identifier, caseid=case_identifier) if not delete_case(case_identifier): track_activity(f'tried to delete case {case_identifier}, but it doesn\'t exist', caseid=case_identifier, ctx_less=True) raise BusinessProcessingError('Tried to delete a non-existing case') - call_modules_hook('on_postload_case_delete', data=case_identifier, caseid=case_identifier) + call_modules_hook('on_postload_case_delete', case_identifier, caseid=case_identifier) track_activity(f'case {case_identifier} deleted successfully', ctx_less=True) except Exception as e: logger.exception(e) @@ -178,11 +183,11 @@ def cases_update(case: Cases, updated_case, protagonists, tags) -> Cases: for alert in updated_case.alerts: if alert.alert_status_id != close_status.status_id: alert.alert_status_id = close_status.status_id - alert = call_modules_hook('on_postload_alert_update', data=alert, caseid=case.case_id) + alert = call_modules_hook('on_postload_alert_update', alert, caseid=case.case_id) if alert.alert_resolution_status_id != case_status_id_mapped: alert.alert_resolution_status_id = case_status_id_mapped - alert = call_modules_hook('on_postload_alert_resolution_update', data=alert, + alert = call_modules_hook('on_postload_alert_resolution_update', alert, caseid=case.case_id) track_activity( diff --git a/source/app/business/comments.py b/source/app/business/comments.py index e53c4c9d5..1e317de0b 100644 --- a/source/app/business/comments.py +++ b/source/app/business/comments.py @@ -20,11 +20,11 @@ from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.business.alerts import alerts_exists from app.business.alerts import alerts_get -from app.business.errors import ObjectNotFoundError -from app.business.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError from app.datamgmt.case.case_comments import get_case_comment from app.datamgmt.comments import get_filtered_alert_comments from app.datamgmt.comments import get_filtered_asset_comments @@ -55,7 +55,7 @@ from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.models.comments import Comments -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.models import CaseReceivedFile from app.models.iocs import Ioc from app.models.models import Notes @@ -67,8 +67,8 @@ from app.models.alerts import Alert -def comments_get_filtered_by_alert(current_user, alert_identifier: int, pagination_parameters: PaginationParameters) -> Pagination: - if not alerts_exists(current_user, alert_identifier): +def comments_get_filtered_by_alert(current_user, permissions, alert_identifier: int, pagination_parameters: PaginationParameters) -> Pagination: + if not alerts_exists(current_user, permissions, alert_identifier): raise ObjectNotFoundError() return get_filtered_alert_comments(alert_identifier, pagination_parameters) @@ -115,14 +115,14 @@ def comments_update_for_case(current_user, comment_text, comment_id, object_type if hook.endswith('s'): hook = hook[:-1] - call_modules_hook(f'on_postload_{hook}_comment_update', data=comment, caseid=caseid) + call_modules_hook(f'on_postload_{hook}_comment_update', comment, caseid=caseid) track_activity(f'comment {comment.comment_id} on {object_type} edited', caseid=caseid) return comment -def comments_create_for_alert(current_user, comment: Comments, alert_identifier: int): - alert = alerts_get(current_user, alert_identifier) +def comments_create_for_alert(current_user, permissions, comment: Comments, alert_identifier: int): + alert = alerts_get(current_user, permissions, alert_identifier) comment.comment_alert_id = alert_identifier comment.comment_user_id = current_user.id comment.comment_date = datetime.now() @@ -152,7 +152,7 @@ def comments_create_for_asset(current_user, asset: CaseAssets, comment: Comments 'comment': comment, 'asset': asset } - call_modules_hook('on_postload_asset_commented', data=hook_data, caseid=asset.case_id) + call_modules_hook('on_postload_asset_commented', hook_data, caseid=asset.case_id) track_activity(f'asset "{asset.asset_name}" commented', caseid=asset.case_id) @@ -168,7 +168,7 @@ def comments_create_for_evidence(current_user, evidence: CaseReceivedFile, comme 'comment': comment, 'evidence': evidence } - call_modules_hook('on_postload_evidence_commented', data=hook_data, caseid=evidence.case_id) + call_modules_hook('on_postload_evidence_commented', hook_data, caseid=evidence.case_id) track_activity(f'evidence "{evidence.filename}" commented', caseid=evidence.case_id) @@ -183,7 +183,7 @@ def comments_create_for_ioc(current_user, ioc: Ioc, comment: Comments): 'comment': comment, 'ioc': ioc } - call_modules_hook('on_postload_ioc_commented', data=hook_data, caseid=ioc.case_id) + call_modules_hook('on_postload_ioc_commented', hook_data, caseid=ioc.case_id) track_activity(f'ioc "{ioc.ioc_value}" commented', caseid=ioc.case_id) @@ -198,7 +198,7 @@ def comments_create_for_note(current_user, note: Notes, comment: Comments): 'comment': comment, 'note': note } - call_modules_hook('on_postload_note_commented', data=hook_data, caseid=note.note_case_id) + call_modules_hook('on_postload_note_commented', hook_data, caseid=note.note_case_id) track_activity(f'note "{note.note_title}" commented', caseid=note.note_case_id) @@ -214,7 +214,7 @@ def comments_create_for_task(current_user, task: CaseTasks, comment: Comments): 'comment': comment, 'task': task } - call_modules_hook('on_postload_task_commented', data=hook_data, caseid=task.task_case_id) + call_modules_hook('on_postload_task_commented', hook_data, caseid=task.task_case_id) track_activity(f'task "{task.task_title}" commented', caseid=task.task_case_id) @@ -232,7 +232,7 @@ def comments_create_for_event(current_user, event: CasesEvent, comment: Comments 'comment': comment, 'event': event } - call_modules_hook('on_postload_event_commented', data=hook_data, caseid=event.case_id) + call_modules_hook('on_postload_event_commented', hook_data, caseid=event.case_id) track_activity(f'event "{event.event_title}" commented', caseid=event.case_id) @@ -303,7 +303,7 @@ def comments_delete_for_alert(comment: Comments): def comments_delete_for_asset(asset: CaseAssets, comment: Comments): - delete_asset_comment(asset.asset_id, comment.comment_id) + delete_asset_comment(asset.asset_id, comment) call_modules_hook('on_postload_asset_comment_delete', comment.comment_id, caseid=comment.comment_case_id) track_activity(f'comment {comment.comment_id} on asset {asset.asset_id} deleted', caseid=comment.comment_case_id) diff --git a/source/app/business/customers.py b/source/app/business/customers.py index cacb7805d..9e776cc78 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -16,13 +16,63 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from app.datamgmt.client.client_db import create_client -from app.models.models import Client +from flask_sqlalchemy.pagination import Pagination + +from app.datamgmt.db_operations import db_create +from app.models.customers import Client from app.iris_engine.utils.tracker import track_activity from app.datamgmt.manage.manage_users_db import add_user_to_customer +from app.datamgmt.client.client_db import get_paginated_customers +from app.datamgmt.client.client_db import get_customer +from app.datamgmt.client.client_db import get_customer_by_name +from app.datamgmt.client.client_db import update_customer +from app.datamgmt.client.client_db import delete_client +from app.models.errors import ObjectNotFoundError +from app.models.pagination_parameters import PaginationParameters + + +def customers_filter(user, pagination_parameters: PaginationParameters, is_server_administrator) -> Pagination: + return get_paginated_customers(pagination_parameters, user.id, is_server_administrator) + +# TODO maybe this method should be removed and always create a customer with at least a user +def customers_create(customer: Client): + db_create(customer) -def customers_create(user, customer: Client): - create_client(customer) + +def customers_create_with_user(user, customer: Client): + customers_create(customer) track_activity(f'Added customer {customer.name}', ctx_less=True) add_user_to_customer(user.id, customer.client_id) + + +def customers_get(identifier) -> Client: + customer = get_customer(identifier) + if not customer: + raise ObjectNotFoundError() + return customer + + +def customers_get_by_name(name) -> Client: + customer = get_customer_by_name(name) + if not customer: + raise ObjectNotFoundError() + return customer + + +def customers_exists_another_with_same_name(identifier, name: str) -> bool: + customer = get_customer_by_name(name, case_insensitive=True) + if not customer: + return False + if customer.client_id == identifier: + return False + return True + + +def customers_update(): + update_customer() + + +def customers_delete(customer: Client): + delete_client(customer) + track_activity(f'Deleted Customer with ID {customer.client_id}', ctx_less=True) diff --git a/source/app/business/customers_contacts.py b/source/app/business/customers_contacts.py new file mode 100644 index 000000000..d7948b3c0 --- /dev/null +++ b/source/app/business/customers_contacts.py @@ -0,0 +1,28 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 app.models.models import Contact +from app.models.errors import ObjectNotFoundError +from app.datamgmt.client.client_db import get_client_contact + + +def customers_contacts_get(identifier) -> Contact: + contact = get_client_contact(identifier) + if not contact: + raise ObjectNotFoundError() + return contact diff --git a/source/app/business/events.py b/source/app/business/events.py index 9e9f5e2f6..8f6702ae8 100644 --- a/source/app/business/events.py +++ b/source/app/business/events.py @@ -18,15 +18,15 @@ from datetime import datetime -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.cases import CasesEvent -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.util import add_obj_history_entry from app.datamgmt.states import update_timeline_state from app.datamgmt.case.case_events_db import save_event_category from app.datamgmt.case.case_events_db import update_event_assets -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.datamgmt.case.case_events_db import update_event_iocs from app.datamgmt.case.case_events_db import get_case_event from app.datamgmt.case.case_events_db import delete_event @@ -44,7 +44,7 @@ def events_create(case_identifier, event: CasesEvent, event_category_id, event_a add_obj_history_entry(event, 'created') db.session.add(event) - update_timeline_state(caseid=case_identifier) + update_timeline_state(case_identifier) db.session.commit() save_event_category(event.event_id, event_category_id) diff --git a/source/app/business/evidences.py b/source/app/business/evidences.py index 623c18a7c..9a07f74fe 100644 --- a/source/app/business/evidences.py +++ b/source/app/business/evidences.py @@ -19,8 +19,8 @@ from flask_sqlalchemy.pagination import Pagination from app.blueprints.iris_user import iris_current_user -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.models.models import CaseReceivedFile @@ -35,7 +35,7 @@ def evidences_create(case_identifier, evidence: CaseReceivedFile) -> CaseReceivedFile: crf = add_rfile(evidence, case_identifier, iris_current_user.id) - crf = call_modules_hook('on_postload_evidence_create', data=crf, caseid=case_identifier) + crf = call_modules_hook('on_postload_evidence_create', crf, caseid=case_identifier) if not crf: raise BusinessProcessingError('Unable to create evidence for internal reasons') @@ -52,7 +52,7 @@ def evidences_get(identifier) -> CaseReceivedFile: def evidences_update(evidence: CaseReceivedFile) -> CaseReceivedFile: evidence = update_rfile(evidence=evidence, user_id=iris_current_user.id, caseid=evidence.case_id) - evidence = call_modules_hook('on_postload_evidence_update', data=evidence, caseid=evidence.case_id) + evidence = call_modules_hook('on_postload_evidence_update', evidence, caseid=evidence.case_id) if not evidence: raise BusinessProcessingError('Unable to update task for internal reasons') track_activity(f'updated evidence "{evidence.filename}"', caseid=evidence.case_id) @@ -67,7 +67,7 @@ def evidences_filter(case_identifier, pagination_parameters: PaginationParameter def evidences_delete(evidence: CaseReceivedFile): - call_modules_hook('on_preload_evidence_delete', data=evidence.id, caseid=evidence.case_id) + call_modules_hook('on_preload_evidence_delete', evidence.id, caseid=evidence.case_id) delete_rfile(evidence) - call_modules_hook('on_postload_evidence_delete', data=evidence.id, caseid=evidence.case_id) + call_modules_hook('on_postload_evidence_delete', evidence.id, caseid=evidence.case_id) track_activity(f'deleted evidence "{evidence.filename}" from registry', evidence.case_id) diff --git a/source/app/business/global_tasks.py b/source/app/business/global_tasks.py new file mode 100644 index 000000000..426c834a5 --- /dev/null +++ b/source/app/business/global_tasks.py @@ -0,0 +1,55 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 app.models.errors import ObjectNotFoundError +from app.models.models import GlobalTasks +from datetime import datetime +from app.iris_engine.module_handler.module_handler import call_modules_hook +from app.iris_engine.utils.tracker import track_activity +from app.datamgmt.db_operations import db_create +from app.datamgmt.global_tasks import delete_global_task, get_global_task_by_identifier + + +def global_tasks_create(user, global_task: GlobalTasks) -> GlobalTasks: + global_task.task_userid_update = user.id + global_task.task_open_date = datetime.utcnow() + global_task.task_last_update = datetime.utcnow() + global_task.task_last_update = datetime.utcnow() + + db_create(global_task) + + global_task = call_modules_hook('on_postload_global_task_create', global_task) + track_activity(f'created new global task "{global_task.task_title}"') + + return global_task + + +def global_tasks_get(identifier) -> GlobalTasks: + task = get_global_task_by_identifier(identifier) + if not task: + raise ObjectNotFoundError() + return task + + +def global_tasks_delete(task: GlobalTasks): + call_modules_hook('on_preload_global_task_delete', task.id) + + delete_global_task(task) + + call_modules_hook('on_postload_global_task_delete', task) + track_activity(f'deleted global task ID {task.id}') diff --git a/source/app/business/groups.py b/source/app/business/groups.py index 26e947299..a07bdfff6 100644 --- a/source/app/business/groups.py +++ b/source/app/business/groups.py @@ -15,20 +15,19 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - +from app.datamgmt.db_operations import db_create from app.models.authorization import Group from app.iris_engine.utils.tracker import track_activity -from app.datamgmt.manage.manage_groups_db import create_group -from app.datamgmt.manage.manage_groups_db import get_group_details +from app.datamgmt.manage.manage_groups_db import get_group_details, get_group_by_name from app.datamgmt.manage.manage_groups_db import update_group from app.datamgmt.manage.manage_groups_db import delete_group -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.iris_engine.access_control.utils import ac_ldp_group_removal def groups_create(group: Group) -> Group: - create_group(group) + db_create(group) track_activity(f'added group {group.group_name}', ctx_less=True) return group @@ -41,6 +40,13 @@ def groups_get(identifier) -> Group: return group +def groups_get_by_name(name) -> Group: + group = get_group_by_name(name) + if not group: + raise ObjectNotFoundError() + return group + + def groups_exist(identifier) -> bool: group = get_group_details(identifier) return group is not None diff --git a/source/app/business/iocs.py b/source/app/business/iocs.py index b68a21f22..7247a991d 100644 --- a/source/app/business/iocs.py +++ b/source/app/business/iocs.py @@ -17,7 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.iocs import Ioc from app.datamgmt.case.case_iocs_db import add_ioc @@ -29,8 +29,8 @@ from app.schema.marshables import IocSchema from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.case_iocs_db import get_ioc from app.util import add_obj_history_entry from app.datamgmt.case.case_iocs_db import get_filtered_iocs @@ -56,7 +56,7 @@ def iocs_create(ioc: Ioc): add_ioc(ioc, iris_current_user.id, ioc.case_id) - ioc = call_modules_hook('on_postload_ioc_create', data=ioc, caseid=ioc.case_id) + ioc = call_modules_hook('on_postload_ioc_create', ioc, caseid=ioc.case_id) if ioc: track_activity(f'added ioc "{ioc.ioc_value}"', caseid=ioc.case_id) @@ -81,7 +81,7 @@ def iocs_update(ioc: Ioc, ioc_sc: Ioc) -> (Ioc, str): add_obj_history_entry(ioc, 'updated ioc') db.session.commit() - ioc_sc = call_modules_hook('on_postload_ioc_update', data=ioc_sc, caseid=ioc.case_id) + ioc_sc = call_modules_hook('on_postload_ioc_update', ioc_sc, caseid=ioc.case_id) if ioc_sc: track_activity(f'updated ioc "{ioc_sc.ioc_value}"', caseid=ioc.case_id) @@ -94,11 +94,11 @@ def iocs_update(ioc: Ioc, ioc_sc: Ioc) -> (Ioc, str): def iocs_delete(ioc: Ioc): - call_modules_hook('on_preload_ioc_delete', data=ioc.ioc_id) + call_modules_hook('on_preload_ioc_delete', ioc.ioc_id) delete_ioc(ioc) - call_modules_hook('on_postload_ioc_delete', data=ioc.ioc_id, caseid=ioc.case_id) + call_modules_hook('on_postload_ioc_delete', ioc.ioc_id, caseid=ioc.case_id) track_activity(f'deleted IOC "{ioc.ioc_value}"', caseid=ioc.case_id) return f'IOC {ioc.ioc_id} deleted' diff --git a/source/app/business/notes.py b/source/app/business/notes.py index 9ebaaed08..51cedca40 100644 --- a/source/app/business/notes.py +++ b/source/app/business/notes.py @@ -18,13 +18,13 @@ from datetime import datetime -from app import db +from app.db import db from app.datamgmt.persistence_error import PersistenceError from app.blueprints.iris_user import iris_current_user from app.logger import logger -from app.business.errors import BusinessProcessingError -from app.business.errors import UnhandledBusinessError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import UnhandledBusinessError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.case_notes_db import search_notes_in_case from app.datamgmt.case.case_notes_db import get_note from app.datamgmt.case.case_notes_db import update_note_revision @@ -62,7 +62,7 @@ def notes_create(note: Notes, case_identifier): db.session.add(note_revision) add_obj_history_entry(note, 'created note', commit=True) - note = call_modules_hook('on_postload_note_create', data=note, caseid=case_identifier) + note = call_modules_hook('on_postload_note_create', note, caseid=case_identifier) track_activity(f'created note "{note.note_title}"', caseid=case_identifier) @@ -89,7 +89,7 @@ def notes_update(note: Notes): note.user_id = iris_current_user.id add_obj_history_entry(note, 'updated note', commit=True) - note = call_modules_hook('on_postload_note_update', data=note, caseid=note.note_case_id) + note = call_modules_hook('on_postload_note_update', note, caseid=note.note_case_id) track_activity(f'updated note "{note.note_title}"', caseid=note.note_case_id) @@ -104,9 +104,9 @@ def notes_update(note: Notes): def notes_delete(note: Notes): - call_modules_hook('on_preload_note_delete', data=note.note_id, caseid=note.note_case_id) + call_modules_hook('on_preload_note_delete', note.note_id, caseid=note.note_case_id) delete_note(note.note_id, note.note_case_id) - call_modules_hook('on_postload_note_delete', data=note.note_id, caseid=note.note_case_id) + call_modules_hook('on_postload_note_delete', note.note_id, caseid=note.note_case_id) track_activity(f'deleted note "{note.note_title}"', caseid=note.note_case_id) diff --git a/source/app/business/notes_directories.py b/source/app/business/notes_directories.py index 73565e65b..c5b5eef41 100644 --- a/source/app/business/notes_directories.py +++ b/source/app/business/notes_directories.py @@ -16,10 +16,10 @@ # 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 db +from app.db import db from app.iris_engine.utils.tracker import track_activity from app.models.models import NoteDirectory -from app.business.errors import ObjectNotFoundError +from app.models.errors import ObjectNotFoundError from app.datamgmt.case.case_notes_db import get_directory from app.datamgmt.case.case_notes_db import delete_directory from app.datamgmt.case.case_notes_db import paginate_notes_directories diff --git a/source/app/business/organisations.py b/source/app/business/organisations.py new file mode 100644 index 000000000..bb6c8bdcd --- /dev/null +++ b/source/app/business/organisations.py @@ -0,0 +1,35 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 app.datamgmt.db_operations import db_create +from app.datamgmt.manage.manage_organisations import get_organisation_by_name +from app.models.authorization import Organisation +from app.models.errors import ObjectNotFoundError + + +def organisations_get(name) -> Organisation: + organisation = get_organisation_by_name(name) + if not organisation: + raise ObjectNotFoundError() + return organisation + + +def organisations_create(name, description) -> Organisation: + organisation = Organisation(org_name=name, org_description=description) + db_create(organisation) + return organisation diff --git a/source/app/business/reports/reporter.py b/source/app/business/reports/reporter.py index b53aa0fb8..ec633dce2 100644 --- a/source/app/business/reports/reporter.py +++ b/source/app/business/reports/reporter.py @@ -22,7 +22,7 @@ import os from datetime import datetime -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.blueprints.iris_user import iris_current_user from docx_generator.docx_generator import DocxGenerator from docx_generator.exceptions import rendering_error diff --git a/source/app/business/reports/reports.py b/source/app/business/reports/reports.py index ca8c38f36..6271a8a73 100644 --- a/source/app/business/reports/reports.py +++ b/source/app/business/reports/reports.py @@ -19,7 +19,7 @@ import base64 import os -from app.business.errors import ObjectNotFoundError, BusinessProcessingError +from app.models.errors import ObjectNotFoundError, BusinessProcessingError from app.business.reports.reporter import IrisMakeMdReport, IrisMakeDocReport from app.datamgmt.case.case_db import get_case from app.iris_engine.module_handler.module_handler import call_modules_hook @@ -28,7 +28,7 @@ def generate_investigation_report(caseid, report_id, safe_mode, tmp_dir): - call_modules_hook('on_preload_report_create', data=report_id, caseid=caseid) + call_modules_hook('on_preload_report_create', report_id, caseid=caseid) report = CaseTemplateReport.query.filter(CaseTemplateReport.id == report_id).first() if not report: raise ObjectNotFoundError() @@ -47,20 +47,20 @@ def generate_investigation_report(caseid, report_id, safe_mode, tmp_dir): with open(fpath, 'rb') as rfile: encoded_file = base64.b64encode(rfile.read()).decode('utf-8') res = get_case(caseid) - _data = { + data = { 'report_id': report_id, 'file_path': fpath, 'case_id': res.case_id, 'user_name': res.user.name, 'file': encoded_file } - call_modules_hook('on_postload_report_create', data=_data, caseid=caseid) + call_modules_hook('on_postload_report_create', data, caseid=caseid) track_activity('generated a report') return fpath def generate_activities_report(caseid, report_id, safe_mode, tmp_dir): - call_modules_hook('on_preload_activities_report_create', data=report_id, caseid=caseid) + call_modules_hook('on_preload_activities_report_create', report_id, caseid=caseid) report = CaseTemplateReport.query.filter(CaseTemplateReport.id == report_id).first() if not report: raise ObjectNotFoundError() @@ -77,6 +77,6 @@ def generate_activities_report(caseid, report_id, safe_mode, tmp_dir): else: raise BusinessProcessingError('Report error', data='Unknown report format.') - call_modules_hook('on_postload_activities_report_create', data=report_id, caseid=caseid) + call_modules_hook('on_postload_activities_report_create', report_id, caseid=caseid) track_activity('generated a report') return fpath diff --git a/source/app/business/tasks.py b/source/app/business/tasks.py index 5d88609a1..15345c69a 100644 --- a/source/app/business/tasks.py +++ b/source/app/business/tasks.py @@ -20,29 +20,29 @@ from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.case.case_tasks_db import delete_task +from app.datamgmt.case.case_tasks_db import list_user_tasks from app.datamgmt.case.case_tasks_db import add_task from app.datamgmt.case.case_tasks_db import update_task_assignees from app.datamgmt.case.case_tasks_db import get_task from app.datamgmt.case.case_tasks_db import get_filtered_tasks from app.datamgmt.states import update_tasks_state -from app.datamgmt.dashboard.dashboard_db import list_user_tasks from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.models.models import CaseTasks from app.models.pagination_parameters import PaginationParameters -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError def tasks_delete(task: CaseTasks): - call_modules_hook('on_preload_task_delete', data=task.id) + call_modules_hook('on_preload_task_delete', task.id) delete_task(task.id) update_tasks_state(caseid=task.task_case_id) - call_modules_hook('on_postload_task_delete', data=task.id, caseid=task.task_case_id) + call_modules_hook('on_postload_task_delete', task.id, caseid=task.task_case_id) track_activity(f'deleted task "{task.task_title}"') @@ -54,7 +54,7 @@ def tasks_create(task: CaseTasks, task_assignee_list) -> CaseTasks: caseid=task.task_case_id ) - ctask = call_modules_hook('on_postload_task_create', data=ctask, caseid=task.task_case_id) + ctask = call_modules_hook('on_postload_task_create', ctask, caseid=task.task_case_id) if not ctask: raise BusinessProcessingError('Unable to create task for internal reasons') @@ -86,7 +86,7 @@ def tasks_update(task: CaseTasks, task_assignee_list): db.session.commit() - task = call_modules_hook('on_postload_task_update', data=task, caseid=task.task_case_id) + task = call_modules_hook('on_postload_task_update', task, caseid=task.task_case_id) if not task: raise BusinessProcessingError('Unable to update task for internal reasons') diff --git a/source/app/business/users.py b/source/app/business/users.py index f10f693c0..9ebff7a2f 100644 --- a/source/app/business/users.py +++ b/source/app/business/users.py @@ -16,10 +16,10 @@ # 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 db +from app.db import db from app.models.authorization import User -from app.business.errors import BusinessProcessingError -from app.business.errors import ObjectNotFoundError +from app.models.errors import BusinessProcessingError +from app.models.errors import ObjectNotFoundError from app.datamgmt.manage.manage_users_db import get_active_user from app.datamgmt.manage.manage_users_db import get_user_details_return_user from app.datamgmt.manage.manage_users_db import get_active_user_by_login diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 67b701109..1e91ce048 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -20,7 +20,8 @@ import json from datetime import datetime from datetime import timedelta -from typing import List, Optional +from typing import List +from typing import Optional from typing import Tuple from sqlalchemy import desc @@ -35,8 +36,9 @@ from sqlalchemy.orm import selectinload from flask_sqlalchemy.pagination import Pagination -import app -from app import db +from app.logger import logger +from app.datamgmt.case.case_db import case_db_save +from app.db import db from app.datamgmt.filtering import combine_conditions from app.datamgmt.filtering import apply_custom_conditions from app.datamgmt.case.case_assets_db import create_asset @@ -51,18 +53,15 @@ from app.datamgmt.manage.manage_case_templates_db import case_template_post_modifier from app.datamgmt.states import update_timeline_state from app.blueprints.iris_user import iris_current_user -from app.iris_engine.access_control.utils import ac_current_user_has_permission from app.iris_engine.utils.common import parse_bf_date_format from app.models.cases import Cases from app.models.models import EventCategory from app.models.models import Tags -from app.models.models import AssetsType +from app.models.assets import AssetsType, alert_assets_association, CaseAssets from app.models.comments import Comments -from app.models.models import CaseAssets -from app.models.models import alert_assets_association from app.models.iocs import alert_iocs_association from app.models.iocs import Ioc -from app.models.models import Client +from app.models.customers import Client from app.models.alerts import Alert from app.models.alerts import AlertStatus from app.models.alerts import AlertCaseAssociation @@ -70,11 +69,10 @@ from app.models.alerts import AlertResolutionStatus from app.models.alerts import AlertSimilarity from app.models.alerts import Severity -from app.models.authorization import Permissions from app.models.authorization import User from app.schema.marshables import EventSchema from app.util import add_obj_history_entry - +from app.datamgmt.db_operations import db_create, db_delete relationship_model_map = { 'owner': User, @@ -97,67 +95,31 @@ def db_list_all_alerts(): def get_filtered_alerts( - start_date: str = None, - end_date: str = None, - source_start_date: str = None, - source_end_date: str = None, - title: str = None, - description: str = None, - status: int = None, - severity: int = None, - owner: int = None, - source: str = None, - tags: str = None, - case_id: int = None, - client: int = None, - classification: int = None, - alert_ids: List[int] = None, - assets: List[str] = None, - iocs: List[str] = None, - resolution_status: List[int] = None, - logical_operator: str = 'and', # Logical operator: 'and', 'or', 'not' - page: int = 1, - per_page: int = 10, - sort: str = 'desc', - current_user_id: int = None, - source_reference=None, - custom_conditions: List[dict] = None) -> Pagination: - """ - Get a list of alerts that match the given filter conditions - - args: - start_date (datetime): The start date of the alert creation time - end_date (datetime): The end date of the alert creation time - title (str): The title of the alert - description (str): The description of the alert - status (str): The status of the alert - severity (str): The severity of the alert - owner (str): The owner of the alert - source (str): The source of the alert - tags (str): The tags of the alert - case_id (int): The case id of the alert - client (int): The client id of the alert - classification (int): The classification id of the alert - alert_ids (int): The alert ids - assets (list): The assets of the alert - iocs (list): The iocs of the alert - resolution_status (list): The resolution status of the alert - logical_operator (str): Logical operator to combine conditions ('and', 'or', 'not') - page (int): The page number - per_page (int): The number of alerts per page - sort (str): The sort order - current_user_id (int): The ID of the current user - source_reference (str): Alert source reference - custom_conditions (list): Custom conditions to be applied (e.g., NOT client AND owner_id in [1,2,3]) - - returns: - list: A list of alerts that match the given filter conditions - ... - fields (List[str]): The list of fields to include in the output - - returns: - dict: Dictionary with pagination info and list of serialized alerts - """ + start_date: str, + end_date: str, + source_start_date: str, + source_end_date: str, + title: str, + description: str, + status: int, + severity: int, + owner: int, + source: str, + tags: str, + case_id: int, + client: int, + classification: int, + alert_ids: List[int], + assets: List[str], + iocs: List[str], + resolution_status: List[int], + page: int, + per_page: int, + sort: str, + user_identifier: int | None, + source_reference, + custom_conditions: List[dict] + ) -> Pagination: conditions = [] if start_date is not None and end_date is not None: @@ -226,8 +188,8 @@ def get_filtered_alerts( if isinstance(iocs, list): conditions.append(Alert.iocs.any(Ioc.ioc_value.in_(iocs))) - if current_user_id is not None and not ac_current_user_has_permission(Permissions.server_administrator): - clients_filters = get_user_clients_id(current_user_id) + if user_identifier is not None: + clients_filters = get_user_clients_id(user_identifier) if clients_filters is not None: conditions.append(Alert.alert_customer_id.in_(clients_filters)) @@ -248,14 +210,14 @@ def get_filtered_alerts( try: custom_conditions = json.loads(custom_conditions) except: - app.app.logger.exception(f"Error parsing custom_conditions: {custom_conditions}") + logger.exception(f"Error parsing custom_conditions: {custom_conditions}") return query, conditions_tmp = apply_custom_conditions(query, Alert, custom_conditions, relationship_model_map) conditions.extend(conditions_tmp) - # Combine conditions - combined_conditions = combine_conditions(conditions, logical_operator) + # Combine conditions + combined_conditions = combine_conditions(conditions, 'and') order_func = desc if sort == "desc" else asc @@ -272,7 +234,7 @@ def get_filtered_alerts( return filtered_alerts except Exception as e: - app.app.logger.exception(f"Error getting alerts: {str(e)}") + logger.exception(f"Error getting alerts: {str(e)}") return None @@ -308,8 +270,7 @@ def add_alert( alert.alert_owner_id = owner # Add the alert to the database - db.session.add(alert) - db.session.commit() + db_create(alert) return alert @@ -372,6 +333,7 @@ def create_case_from_alerts(alerts: List[Alert], iocs_list: List[str], assets_li case_template_title_prefix = case_template.title_prefix # Create the case + # FIXME I think there is a bug, if no template_id is provided case = Cases( name=f"[ALERT]{case_template_title_prefix} " f"Merge of alerts {', '.join([str(alert.alert_id) for alert in alerts])}" if not case_title else @@ -385,7 +347,7 @@ def create_case_from_alerts(alerts: List[Alert], iocs_list: List[str], assets_li state_id=get_case_state_by_name('Open').state_id ) - case.save() + case_db_save(case) for tag in case_tags.split(','): tag = Tags(tag_title=tag) @@ -510,7 +472,7 @@ def create_case_from_alert(alert: Alert, iocs_list: List[str], assets_list: List state_id=get_case_state_by_name('Open').state_id ) - case.save() + case_db_save(case) for tag in case_tags.split(','): tag = Tags(tag_title=tag) @@ -555,8 +517,7 @@ def create_case_from_alert(alert: Alert, iocs_list: List[str], assets_list: List new_alert_ioc.user_id = iris_current_user.id new_alert_ioc.case_id = case.case_id - db.session.add(new_alert_ioc) - db.session.commit() + db_create(new_alert_ioc) alert_ioc = new_alert_ioc @@ -578,8 +539,7 @@ def create_case_from_alert(alert: Alert, iocs_list: List[str], assets_list: List new_alert_asset.asset_id = None new_alert_asset.asset_uuid = asset_uuid - db.session.add(new_alert_asset) - db.session.commit() + db_create(new_alert_asset) alert_asset = new_alert_asset @@ -1322,8 +1282,7 @@ def delete_alert_comment(comment_id: int, alert_id: int) -> Tuple[bool, str]: if not comment: return False, "You are not allowed to delete this comment" - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted successfully" @@ -1442,7 +1401,7 @@ def delete_alerts(alert_ids: List[int]) -> tuple[bool, str]: except Exception as e: db.session.rollback() - app.logger.exception(str(e)) + logger.exception(str(e)) return False, "Server side error" return True, "" @@ -1462,5 +1421,4 @@ def get_alert_status_by_name(name: str) -> AlertStatus: def delete_alert(alert): - db.session.delete(alert) - db.session.commit() + db_delete(alert) diff --git a/source/app/datamgmt/case/assets_type.py b/source/app/datamgmt/case/assets_type.py index 410ac5ea9..d470e4574 100644 --- a/source/app/datamgmt/case/assets_type.py +++ b/source/app/datamgmt/case/assets_type.py @@ -18,7 +18,7 @@ from sqlalchemy import func -from app.models.models import AssetsType +from app.models.assets import AssetsType def get_assets_types(): diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index 2fb112602..f3666de60 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -23,20 +23,21 @@ from sqlalchemy import func from flask_sqlalchemy.pagination import Pagination -from app import db +from app.datamgmt.db_operations import db_create +from app.datamgmt.db_operations import db_delete +from app.db import db from app.logger import logger -from app.blueprints.iris_user import iris_current_user from app.datamgmt.filtering import get_filtered_data from app.datamgmt.states import update_assets_state -from app.models.models import AnalysisStatus -from app.models.models import CaseStatus -from app.models.models import AssetsType -from app.models.models import CaseAssets from app.models.models import CaseEventsAssets from app.models.cases import Cases +from app.models.cases import CaseStatus from app.models.comments import Comments from app.models.comments import AssetComments -from app.models.models import CompromiseStatus +from app.models.assets import CompromiseStatus +from app.models.assets import AssetsType +from app.models.assets import CaseAssets +from app.models.assets import AnalysisStatus from app.models.iocs import Ioc from app.models.models import IocAssetLink from app.models.models import IocType @@ -336,8 +337,7 @@ def add_comment_to_asset(asset_id, comment_id): ec.comment_asset_id = asset_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_assets_comments_count(asset_id): @@ -353,43 +353,19 @@ def get_case_assets_comments_count(asset_id): def get_case_asset_comment(asset_id, comment_id) -> Optional[Comments]: - return AssetComments.query.filter( + return Comments.query.join(AssetComments.comment).filter( AssetComments.comment_asset_id == asset_id, - AssetComments.comment_id == comment_id - ).with_entities( - Comments.comment_id, - Comments.comment_text, - Comments.comment_date, - Comments.comment_update_date, - Comments.comment_uuid, - Comments.comment_user_id, - Comments.comment_case_id, - User.name, - User.user - ).join( - AssetComments.comment - ).join( - Comments.user + Comments.comment_id == comment_id ).first() -def delete_asset_comment(asset_id, comment_id): - comment = Comments.query.filter( - Comments.comment_id == comment_id, - Comments.comment_user_id == iris_current_user.id - ).first() - if not comment: - return False, "You are not allowed to delete this comment" - +def delete_asset_comment(asset_id, comment: Comments): AssetComments.query.filter( AssetComments.comment_asset_id == asset_id, - AssetComments.comment_id == comment_id + AssetComments.comment_id == comment.comment_id ).delete() - db.session.delete(comment) - db.session.commit() - - return True, "Comment deleted" + db_delete(comment) def get_asset_by_name(asset_name, caseid): diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index d0b3cbf59..1a2e26aa8 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -24,21 +24,36 @@ from sqlalchemy import exists from sqlalchemy import select -from app import db -from app.datamgmt.manage.manage_tags_db import add_db_tag +from app.db import db from app.models.authorization import User from app.models.cases import CaseProtagonist from app.models.cases import Cases -from app.models.models import CaseTemplateReport, ReviewStatus -from app.models.models import Client +from app.models.models import CaseTemplateReport +from app.models.models import ReviewStatus +from app.models.customers import Client from app.models.models import Languages from app.models.models import ReportType +from app.datamgmt.manage.manage_tags_db import add_db_tag +from app.datamgmt.db_operations import db_create +from app.datamgmt.states import update_assets_state +from app.datamgmt.states import update_evidences_state +from app.datamgmt.states import update_ioc_state +from app.datamgmt.states import update_notes_state +from app.datamgmt.states import update_tasks_state +from app.datamgmt.states import update_timeline_state -def get_first_case() -> Cases: +def get_first_case() -> Optional[Cases]: return Cases.query.order_by(Cases.case_id).first() +def get_first_case_with_customer(customer_identifier) -> Optional[Cases]: + case = Cases.query.filter( + Cases.client_id == customer_identifier + ).first() + return case + + def get_case_summary(caseid): case_summary = Cases.query.filter( Cases.case_id == caseid @@ -215,3 +230,49 @@ def get_review_id_from_name(review_name): return status.id return None + + +def case_db_save(case: Cases): + db_create(case) + + # Rename case with the ID + case.name = f'#{case.case_id} - {case.name}' + + # Create the states + update_timeline_state(caseid=case.case_id, userid=case.user_id) + update_tasks_state(caseid=case.case_id, userid=case.user_id) + update_evidences_state(caseid=case.case_id, userid=case.user_id) + update_ioc_state(caseid=case.case_id, userid=case.user_id) + update_assets_state(caseid=case.case_id, userid=case.user_id) + update_notes_state(caseid=case.case_id, userid=case.user_id) + + db.session.commit() + + +def list_user_reviews(user_identifier): + ct = Cases.query.with_entities( + Cases.case_id, + Cases.name, + ReviewStatus.status_name, + ReviewStatus.id.label('status_id') + ).join( + Cases.review_status + ).filter( + Cases.reviewer_id == user_identifier, + ReviewStatus.status_name != 'Reviewed', + ReviewStatus.status_name != 'Not reviewed' + ).all() + + return ct + + +def list_user_cases(user_identifier, show_all=False): + if show_all: + return Cases.query.filter( + Cases.owner_id == user_identifier + ).all() + + return Cases.query.filter( + Cases.owner_id == user_identifier, + Cases.close_date == None + ).all() diff --git a/source/app/datamgmt/case/case_events_db.py b/source/app/datamgmt/case/case_events_db.py index de41e8586..046de3794 100644 --- a/source/app/datamgmt/case/case_events_db.py +++ b/source/app/datamgmt/case/case_events_db.py @@ -18,11 +18,11 @@ from sqlalchemy import and_ -from app import db +from app.datamgmt.db_operations import db_create, db_delete +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.states import update_timeline_state -from app.models.models import AssetsType -from app.models.models import CaseAssets +from app.models.assets import AssetsType, CaseAssets from app.models.models import CaseEventCategory from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc @@ -171,8 +171,7 @@ def delete_event_comment(event_id, comment_id): EventComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted" @@ -195,8 +194,7 @@ def add_comment_to_event(event_id, comment_id): ec.comment_event_id = event_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def delete_event_category(event_id): @@ -221,8 +219,7 @@ def save_event_category(event_id, category_id): cec.event_id = event_id cec.category_id = category_id - db.session.add(cec) - db.session.commit() + db_create(cec) def get_event_assets_ids(event_id, caseid): diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index b95d110b3..828736a4a 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -18,18 +18,18 @@ from sqlalchemy import and_ -from app import db -from app import app +from app.datamgmt.db_operations import db_create, db_delete +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.filtering import get_filtered_data from app.datamgmt.states import update_ioc_state -from app.iris_engine.access_control.utils import ac_get_fast_user_cases_access from app.models.alerts import Alert from app.models.cases import Cases from app.models.cases import CasesEvent -from app.models.models import Client -from app.models.models import CaseAssets -from app.models.comments import Comments, IocComments +from app.models.customers import Client +from app.models.assets import CaseAssets +from app.models.comments import Comments +from app.models.comments import IocComments from app.models.iocs import Ioc from app.models.models import IocType from app.models.iocs import Tlp @@ -37,7 +37,6 @@ from app.models.pagination_parameters import PaginationParameters from app.util import add_obj_history_entry -log = app.logger relationship_model_map = { 'case': Cases, @@ -119,12 +118,11 @@ def get_detailed_iocs(caseid): return detailed_iocs -def get_ioc_links(ioc_id): - search_condition = and_(Cases.case_id.in_([])) - - user_search_limitations = ac_get_fast_user_cases_access(iris_current_user.id) +def get_ioc_links(ioc_id, user_search_limitations): if user_search_limitations: search_condition = and_(Cases.case_id.in_(user_search_limitations)) + else: + search_condition = and_(Cases.case_id.in_([])) ioc = Ioc.query.filter(Ioc.ioc_id == ioc_id).first() @@ -182,8 +180,7 @@ def add_ioc_type(name: str, description: str, taxonomy: str): type_taxonomy=taxonomy ) - db.session.add(ioct) - db.session.commit() + db_create(ioct) return ioct @@ -232,8 +229,7 @@ def add_comment_to_ioc(ioc_id, comment_id): ec.comment_ioc_id = ioc_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_iocs_comments_count(iocs_list): @@ -279,8 +275,7 @@ def delete_ioc_comment(ioc_id, comment_id): IocComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted" diff --git a/source/app/datamgmt/case/case_notes_db.py b/source/app/datamgmt/case/case_notes_db.py index b74902d1d..a47ea433b 100644 --- a/source/app/datamgmt/case/case_notes_db.py +++ b/source/app/datamgmt/case/case_notes_db.py @@ -22,7 +22,8 @@ from datetime import datetime from flask_sqlalchemy.pagination import Pagination -from app import db +from app.datamgmt.db_operations import db_create, db_delete +from app.db import db from app.datamgmt.persistence_error import PersistenceError from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes @@ -36,7 +37,7 @@ from app.models.models import NotesGroupLink from app.models.authorization import User from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.pagination_parameters import PaginationParameters from app.datamgmt.filtering import paginate @@ -66,9 +67,7 @@ def delete_directory(directory: NoteDirectory): for subdirectory in directory.subdirectories: delete_directory(subdirectory) - # Delete the directory - db.session.delete(directory) - db.session.commit() + db_delete(directory) return True @@ -160,8 +159,7 @@ def update_note_revision(note: Notes) -> bool: note_user=iris_current_user.id, revision_timestamp=datetime.utcnow() ) - db.session.add(note_version) - db.session.commit() + db_create(note_version) return True except IntegrityError as e: @@ -379,8 +377,7 @@ def add_comment_to_note(note_id, comment_id): ec.comment_note_id = note_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_notes_comments_count(notes_list): @@ -431,8 +428,7 @@ def delete_note_comment(note_id, comment_id): NotesComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, 'Comment deleted' diff --git a/source/app/datamgmt/case/case_rfiles_db.py b/source/app/datamgmt/case/case_rfiles_db.py index 1cc73b8ff..5e116daa3 100644 --- a/source/app/datamgmt/case/case_rfiles_db.py +++ b/source/app/datamgmt/case/case_rfiles_db.py @@ -20,7 +20,8 @@ from sqlalchemy import desc from flask_sqlalchemy.pagination import Pagination -from app import db +from app.datamgmt.db_operations import db_create, db_delete +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes from app.datamgmt.states import update_evidences_state @@ -129,8 +130,7 @@ def add_comment_to_evidence(evidence_id, comment_id): ec.comment_evidence_id = evidence_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_evidence_comments_count(evidences_list): @@ -179,7 +179,6 @@ def delete_evidence_comment(evidence_id, comment_id): EvidencesComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted" diff --git a/source/app/datamgmt/case/case_tasks_db.py b/source/app/datamgmt/case/case_tasks_db.py index 27ee76296..0d65f1c95 100644 --- a/source/app/datamgmt/case/case_tasks_db.py +++ b/source/app/datamgmt/case/case_tasks_db.py @@ -22,7 +22,8 @@ from sqlalchemy import desc from sqlalchemy import and_ -from app import db +from app.datamgmt.db_operations import db_create, db_delete +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.conversions import convert_sort_direction from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes @@ -241,8 +242,7 @@ def add_comment_to_task(task_id, comment_id): ec.comment_task_id = task_id ec.comment_id = comment_id - db.session.add(ec) - db.session.commit() + db_create(ec) def get_case_tasks_comments_count(tasks_list): @@ -313,8 +313,7 @@ def delete_task_comment(task_id, comment_id): TaskComments.comment_id == comment_id ).delete() - db.session.delete(comment) - db.session.commit() + db_delete(comment) return True, "Comment deleted" @@ -343,3 +342,51 @@ def get_tasks_cases_mapping(open_cases_only=False): ).join( CaseTasks.case ).all() + + +def list_user_tasks(user_identifier): + ct = CaseTasks.query.with_entities( + CaseTasks.id.label("task_id"), + CaseTasks.task_title, + CaseTasks.task_description, + CaseTasks.task_last_update, + CaseTasks.task_tags, + Cases.name.label('task_case'), + CaseTasks.task_case_id.label('case_id'), + CaseTasks.task_status_id, + TaskStatus.status_name, + TaskStatus.status_bscolor + ).join( + CaseTasks.case + ).order_by( + desc(TaskStatus.status_name) + ).filter(and_( + TaskStatus.status_name != 'Done', + TaskStatus.status_name != 'Canceled' + )).join( + CaseTasks.status, + ).filter(and_( + TaskAssignee.task_id == CaseTasks.id, + TaskAssignee.user_id == user_identifier + )).all() + + return ct + + +def update_utask_status(task_id, status, case_id): + if task_id != 0: + task = CaseTasks.query.filter( + CaseTasks.id == task_id, + CaseTasks.task_case_id == case_id + ).first() + if task: + try: + task.task_status_id = status + + db.session.commit() + return True + + except: + pass + + return False diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index ed2f745d0..8851eb07d 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -16,25 +16,35 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import marshmallow from sqlalchemy import func from sqlalchemy import and_ from typing import List +from typing import Optional +from flask_sqlalchemy.pagination import Pagination -from app import db -from app.datamgmt.exceptions.ElementExceptions import ElementInUseException -from app.datamgmt.exceptions.ElementExceptions import ElementNotFoundException +from app.datamgmt.db_operations import db_delete +from app.db import db +from app.models.errors import ElementInUseError from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.models import Contact from app.models.authorization import User from app.models.authorization import UserClient -from app.schema.marshables import ContactSchema -from app.schema.marshables import CustomerSchema +from app.models.pagination_parameters import PaginationParameters +from app.datamgmt.filtering import paginate -def get_client_list(current_user_id: int = None, - is_server_administrator: bool = False) -> List[dict]: +# TODO most probably rename into update (or save?) and make more generic, maybe just use the preceding method? +def update_contact(): + db.session.commit() + + +# TODO same +def update_customer(): + db.session.commit() + + +def get_client_list(current_user_id: int, is_server_administrator: bool) -> List[dict]: if not is_server_administrator: filter = and_( Client.client_id == UserClient.client_id, @@ -59,9 +69,20 @@ def get_client_list(current_user_id: int = None, return output -def get_client(client_id: int) -> Client: - client = Client.query.filter(Client.client_id == client_id).first() - return client +def get_paginated_customers(pagination_parameters: PaginationParameters, current_user_identifier: int, is_server_administrator: bool) -> Pagination: + query = Client.query + + if not is_server_administrator: + query = query.filter( + Client.client_id == UserClient.client_id, + UserClient.user_id == current_user_identifier + ) + + return paginate(Client, pagination_parameters, query) + + +def get_customer(client_id: int) -> Optional[Client]: + return Client.query.filter(Client.client_id == client_id).first() def get_client_api(client_id: str) -> Client: @@ -100,12 +121,6 @@ def get_client_cases(client_id: int): return cases_list -def create_client(customer: Client): - - db.session.add(customer) - db.session.commit() - - def get_client_contacts(client_id: int) -> List[Contact]: contacts = Contact.query.filter( Contact.client_id == client_id @@ -116,95 +131,26 @@ def get_client_contacts(client_id: int) -> List[Contact]: return contacts -def get_client_contact(client_id: int, contact_id: int) -> Contact: +def get_client_contact(contact_id: int) -> Contact: contact = Contact.query.filter( - Contact.client_id == client_id, Contact.id == contact_id ).first() return contact -def delete_contact(contact_id: int) -> None: - contact = Contact.query.filter( - Contact.id == contact_id - ).first() - - if not contact: - raise ElementNotFoundException('No Contact found with this uuid.') - +def delete_contact(contact: Contact): try: - - db.session.delete(contact) - db.session.commit() - + db_delete(contact) except Exception: - raise ElementInUseException('A currently referenced contact cannot be deleted') - - -def create_contact(data, customer_id) -> Contact: - data['client_id'] = customer_id - contact_schema = ContactSchema() - contact = contact_schema.load(data) - - db.session.add(contact) - db.session.commit() + raise ElementInUseError('A currently referenced contact cannot be deleted') - return contact - - -def update_contact(data, contact_id, customer_id) -> Contact: - contact = get_client_contact(customer_id, contact_id) - data['client_id'] = customer_id - contact_schema = ContactSchema() - contact_schema.load(data, instance=contact) - - db.session.commit() - - return contact - - -def update_client(client_id: int, data) -> Client: - # TODO: Possible reuse somewhere else ... - client = get_client(client_id) - - if not client: - raise ElementNotFoundException('No Customer found with this uuid.') - - exists = Client.query.filter( - Client.client_id != client_id, - func.lower(Client.name) == data.get('customer_name').lower() - ).first() - - if exists: - raise marshmallow.exceptions.ValidationError( - "Customer already exists", - field_name="customer_name" - ) - - client_schema = CustomerSchema() - client_schema.load(data, instance=client) - - db.session.commit() - - return client - - -def delete_client(client_id: int) -> None: - client = Client.query.filter( - Client.client_id == client_id - ).first() - - if not client: - raise ElementNotFoundException('No Customer found with this uuid.') +def delete_client(customer: Client) -> None: try: - - db.session.delete(client) - db.session.commit() - + db_delete(customer) except Exception: - raise ElementInUseException('A currently referenced customer cannot be deleted') + raise ElementInUseError('Cannot delete a referenced customer') def get_case_client(case_id: int) -> Client: @@ -213,3 +159,12 @@ def get_case_client(case_id: int) -> Client: ).first() return client + + +def get_customer_by_name(name, case_insensitive=False) -> Client: + query = db.session.query(Client) + if case_insensitive: + query = query.filter(func.upper(Client.name) == name.upper()) + else: + query = query.filter_by(name=name) + return query.first() diff --git a/source/app/datamgmt/comments.py b/source/app/datamgmt/comments.py index 267beb358..1640ff111 100644 --- a/source/app/datamgmt/comments.py +++ b/source/app/datamgmt/comments.py @@ -15,11 +15,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from typing import Optional from flask_sqlalchemy.pagination import Pagination from sqlalchemy import and_ -from app import db +from app.datamgmt.db_operations import db_delete from app.models.cases import Cases from app.models.comments import Comments from app.models.comments import EventComments @@ -30,7 +31,7 @@ from app.models.comments import NotesComments from app.models.pagination_parameters import PaginationParameters from app.models.authorization import User -from app.models.models import Client +from app.models.customers import Client def _get_filtered_comments(query, pagination_parameters: PaginationParameters) -> Pagination: @@ -106,8 +107,7 @@ def get_filtered_event_comments(event_identifier, pagination_parameters: Paginat def delete_comment(comment: Comments): - db.session.delete(comment) - db.session.commit() + db_delete(comment) def user_has_comments(user: User): @@ -136,3 +136,10 @@ def search_comments(search_value): ).all() return [row._asdict() for row in comments] + + +def get_comment(user, identifier) -> Optional[Comments]: + return Comments.query.filter( + Comments.comment_id == identifier, + Comments.comment_user_id == user.id + ).first() diff --git a/source/app/datamgmt/context/context_db.py b/source/app/datamgmt/context/context_db.py index d9654dfa3..a753f387b 100644 --- a/source/app/datamgmt/context/context_db.py +++ b/source/app/datamgmt/context/context_db.py @@ -22,7 +22,7 @@ from sqlalchemy import desc from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.authorization import CaseAccessLevel from app.models.authorization import UserCaseEffectiveAccess from app.datamgmt.authorization import has_deny_all_access_level diff --git a/source/app/datamgmt/datastore/datastore_db.py b/source/app/datamgmt/datastore/datastore_db.py index 666810ea7..e78e41551 100644 --- a/source/app/datamgmt/datastore/datastore_db.py +++ b/source/app/datamgmt/datastore/datastore_db.py @@ -25,7 +25,8 @@ from sqlalchemy import func from app import app -from app import db +from app.datamgmt.db_operations import db_create, db_delete +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.models import CaseReceivedFile from app.models.models import DataStoreFile @@ -146,8 +147,7 @@ def init_ds_tree(cid): dsp_root.path_case_id = cid dsp_root.path_parent_id = 0 - db.session.add(dsp_root) - db.session.commit() + db_create(dsp_root) for path in ['Evidences', 'IOCs', 'Images']: dsp_init = DataStorePath() @@ -193,8 +193,7 @@ def datastore_add_child_node(parent_node, folder_name, cid): dsp.path_parent_id = parent_node dsp.path_is_root = False - db.session.add(dsp) - db.session.commit() + db_create(dsp) return False, 'Folder added', dsp @@ -250,8 +249,7 @@ def datastore_iter_deletion(dsp, cid): datastore_delete_files_of_path(dsp.path_id, cid) - db.session.delete(dsp) - db.session.commit() + db_delete(dsp) return None @@ -269,8 +267,7 @@ def datastore_delete_files_of_path(node_id, cid): if fln.is_file(): fln.unlink(missing_ok=True) - db.session.delete(dsf_list_item) - db.session.commit() + db_delete(dsf_list_item) return @@ -296,8 +293,7 @@ def datastore_get_interactive_path_node(cid): dsp.path_name = 'Notes Upload' dsp.path_is_root = False - db.session.add(dsp) - db.session.commit() + db_create(dsp) return dsp @@ -342,13 +338,12 @@ def datastore_delete_file(cur_id, cid): if fln.is_file(): fln.unlink(missing_ok=True) - db.session.delete(dsf) - db.session.commit() + db_delete(dsf) return False, f'File {cur_id} deleted' -def datastore_add_file_as_ioc(dsf, caseid): +def datastore_add_file_as_ioc(dsf): ioc = Ioc.query.filter( Ioc.ioc_value == dsf.file_sha256 ).first() @@ -370,8 +365,7 @@ def datastore_add_file_as_ioc(dsf, caseid): ioc.ioc_tags = "datastore" ioc.user_id = iris_current_user.id - db.session.add(ioc) - db.session.commit() + db_create(ioc) def datastore_add_file_as_evidence(dsf, caseid): @@ -389,8 +383,7 @@ def datastore_add_file_as_evidence(dsf, caseid): crf.file_size = dsf.file_size crf.user_id = iris_current_user.id - db.session.add(crf) - db.session.commit() + db_create(crf) return @@ -559,4 +552,3 @@ def datastore_filter_tree(filter_d, caseid): datastore_iter_tree(dpath_parent_id, path_node, droot_children) return path_tree, 'Success' - diff --git a/source/app/datamgmt/exceptions/ElementExceptions.py b/source/app/datamgmt/db_operations.py similarity index 76% rename from source/app/datamgmt/exceptions/ElementExceptions.py rename to source/app/datamgmt/db_operations.py index 52614d4dc..ddf789009 100644 --- a/source/app/datamgmt/exceptions/ElementExceptions.py +++ b/source/app/datamgmt/db_operations.py @@ -1,6 +1,6 @@ # IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net +# Copyright (C) 2025 - 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 @@ -16,9 +16,14 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -class ElementNotFoundException(Exception): - pass +from app.db import db -class ElementInUseException(Exception): - pass +def db_create(element): + db.session.add(element) + db.session.commit() + + +def db_delete(element): + db.session.delete(element) + db.session.commit() diff --git a/source/app/datamgmt/filtering.py b/source/app/datamgmt/filtering.py index 14150e747..66ff125e6 100644 --- a/source/app/datamgmt/filtering.py +++ b/source/app/datamgmt/filtering.py @@ -20,7 +20,7 @@ from sqlalchemy import String, Text, inspect, or_, not_, and_ from app import app -from app.business.errors import BusinessProcessingError +from app.models.errors import BusinessProcessingError from app.datamgmt.conversions import convert_sort_direction from app.models.pagination_parameters import PaginationParameters from app.datamgmt.authorization import RESTRICTED_USER_FIELDS diff --git a/source/app/datamgmt/dashboard/dashboard_db.py b/source/app/datamgmt/global_tasks.py similarity index 51% rename from source/app/datamgmt/dashboard/dashboard_db.py rename to source/app/datamgmt/global_tasks.py index 8f916ccf8..3af8a7251 100644 --- a/source/app/datamgmt/dashboard/dashboard_db.py +++ b/source/app/datamgmt/global_tasks.py @@ -1,6 +1,6 @@ # IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net +# Copyright (C) ${current_year} - 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 @@ -15,17 +15,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from sqlalchemy import and_ + from sqlalchemy import desc -from app import db -from app.models.models import CaseTasks -from app.models.models import TaskAssignee -from app.models.models import ReviewStatus -from app.models.cases import Cases +from app.db import db +from app.models.authorization import User from app.models.models import GlobalTasks from app.models.models import TaskStatus -from app.models.authorization import User + + +def delete_global_task(task: GlobalTasks): + GlobalTasks.query.filter(GlobalTasks.id == task.id).delete() + db.session.commit() def list_global_tasks(): @@ -78,54 +79,10 @@ def get_global_task(task_id): return ct -def get_tasks_status(): - return TaskStatus.query.all() - - -def list_user_reviews(user_identifier): - ct = Cases.query.with_entities( - Cases.case_id, - Cases.name, - ReviewStatus.status_name, - ReviewStatus.id.label('status_id') - ).join( - Cases.review_status - ).filter( - Cases.reviewer_id == user_identifier, - ReviewStatus.status_name != 'Reviewed', - ReviewStatus.status_name != 'Not reviewed' - ).all() - - return ct - - -def list_user_tasks(user_identifier): - ct = CaseTasks.query.with_entities( - CaseTasks.id.label("task_id"), - CaseTasks.task_title, - CaseTasks.task_description, - CaseTasks.task_last_update, - CaseTasks.task_tags, - Cases.name.label('task_case'), - CaseTasks.task_case_id.label('case_id'), - CaseTasks.task_status_id, - TaskStatus.status_name, - TaskStatus.status_bscolor - ).join( - CaseTasks.case - ).order_by( - desc(TaskStatus.status_name) - ).filter(and_( - TaskStatus.status_name != 'Done', - TaskStatus.status_name != 'Canceled' - )).join( - CaseTasks.status, - ).filter(and_( - TaskAssignee.task_id == CaseTasks.id, - TaskAssignee.user_id == user_identifier - )).all() - - return ct +def get_global_task_by_identifier(identifier): + return GlobalTasks.query.filter( + GlobalTasks.id == identifier + ).first() def update_gtask_status(task_id, status): @@ -142,44 +99,3 @@ def update_gtask_status(task_id, status): pass return None - - -def update_utask_status(task_id, status, case_id): - if task_id != 0: - task = CaseTasks.query.filter( - CaseTasks.id == task_id, - CaseTasks.task_case_id == case_id - ).first() - if task: - try: - task.task_status_id = status - - db.session.commit() - return True - - except: - pass - - return False - - -def get_task_status(task_status_id): - ret = TaskStatus.query.filter( - TaskStatus.id == task_status_id - ).first() - - return ret - - -def list_user_cases(user_identifier, show_all=False): - if show_all: - return Cases.query.filter( - Cases.owner_id == user_identifier - ).all() - - return Cases.query.filter( - Cases.owner_id == user_identifier, - Cases.close_date == None - ).all() - - diff --git a/source/app/datamgmt/iris_engine/modules_db.py b/source/app/datamgmt/iris_engine/modules_db.py index 381618355..f11d37851 100644 --- a/source/app/datamgmt/iris_engine/modules_db.py +++ b/source/app/datamgmt/iris_engine/modules_db.py @@ -18,15 +18,15 @@ import base64 import datetime -from app import db, app +from app.datamgmt.db_operations import db_create +from app.db import db +from app.logger import logger from app.blueprints.iris_user import iris_current_user from app.models.models import IrisHook from app.models.models import IrisModule from app.models.models import IrisModuleHook from app.models.authorization import User -log = app.logger - def iris_module_exists(module_name): return IrisModule.query.filter(IrisModule.module_name == module_name).first() is not None @@ -56,8 +56,7 @@ def iris_module_add(module_name, module_human_name, module_description, im.module_type = module_type try: - db.session.add(im) - db.session.commit() + db_create(im) except Exception: return None @@ -243,7 +242,7 @@ def parse_module_parameter(module_parameter): param_name = param.split('##')[1] except Exception as e: - log.exception(e) + logger.exception(e) return None, None, None, None mod_config, mod_name, mod_iname = get_module_config_from_id(mod_id) @@ -258,3 +257,13 @@ def parse_module_parameter(module_parameter): return None, None, None, None return mod_config, mod_id, mod_name, mod_iname, parameter + + +def deregister_module_from_hook(module_id: int, iris_hook_name: str): + hooks = IrisModuleHook.query.filter( + IrisModuleHook.module_id == module_id, + IrisHook.hook_name == iris_hook_name, + IrisModuleHook.hook_id == IrisHook.id + ).all() + for hook in hooks: + db.session.delete(hook) diff --git a/source/app/datamgmt/manage/manage_access_control_db.py b/source/app/datamgmt/manage/manage_access_control_db.py index cb65fdc18..9ccd50916 100644 --- a/source/app/datamgmt/manage/manage_access_control_db.py +++ b/source/app/datamgmt/manage/manage_access_control_db.py @@ -17,7 +17,7 @@ from sqlalchemy import and_ from app import ac_current_user_has_permission -from app import db +from app.db import db from app.models.cases import Cases from app.models.authorization import Group from app.models.authorization import UserClient @@ -167,9 +167,6 @@ def user_has_client_access(user_id: int, client_id: int) -> bool: Returns: bool: True if the user has access to the client """ - if ac_current_user_has_permission(Permissions.server_administrator): - return True - result = UserClient.query.filter( UserClient.user_id == user_id, UserClient.client_id == client_id @@ -193,12 +190,34 @@ def remove_duplicate_user_case_effective_accesses(user_id, case_id): return True -def set_user_case_effective_access(access_level, case_id, user_id): +def add_user_case_effective_access(user_identifier, case_identifier, access_level): uac = UserCaseEffectiveAccess.query.where(and_( - UserCaseEffectiveAccess.user_id == user_id, - UserCaseEffectiveAccess.case_id == case_id + UserCaseEffectiveAccess.user_id == user_identifier, + UserCaseEffectiveAccess.case_id == case_identifier )).first() if uac: uac = uac[0] uac.access_level = access_level db.session.commit() + + +def add_several_user_effective_access(user_identifiers, case_identifier, access_level): + """ + Directly add a set of effective user access + """ + + UserCaseEffectiveAccess.query.filter( + UserCaseEffectiveAccess.case_id == case_identifier, + UserCaseEffectiveAccess.user_id.in_(user_identifiers) + ).delete() + + access_to_add = [] + for user_id in user_identifiers: + ucea = UserCaseEffectiveAccess() + ucea.user_id = user_id + ucea.case_id = case_identifier + ucea.access_level = access_level + access_to_add.append(ucea) + + db.session.add_all(access_to_add) + db.session.commit() diff --git a/source/app/datamgmt/manage/manage_assets_db.py b/source/app/datamgmt/manage/manage_assets_db.py index f236f167f..164cdbc0e 100644 --- a/source/app/datamgmt/manage/manage_assets_db.py +++ b/source/app/datamgmt/manage/manage_assets_db.py @@ -1,13 +1,13 @@ from sqlalchemy import and_ from functools import reduce -import app +from app.logger import logger from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_cases_db import user_list_cases_view from app.datamgmt.conversions import convert_sort_direction from app.models.cases import Cases -from app.models.models import CaseAssets -from app.models.models import Client +from app.models.assets import CaseAssets +from app.models.customers import Client def get_filtered_assets(case_id=None, @@ -82,7 +82,7 @@ def get_filtered_assets(case_id=None, filtered_assets = data.paginate(page=page, per_page=per_page) except Exception as e: - app.logger.exception(f"Failed to get filtered assets: {e}") + logger.exception(f"Failed to get filtered assets: {e}") raise e return filtered_assets diff --git a/source/app/datamgmt/manage/manage_attribute_db.py b/source/app/datamgmt/manage/manage_attribute_db.py index 12728799b..74048eff9 100644 --- a/source/app/datamgmt/manage/manage_attribute_db.py +++ b/source/app/datamgmt/manage/manage_attribute_db.py @@ -20,14 +20,14 @@ import logging as logger from sqlalchemy.orm.attributes import flag_modified -from app import db +from app.db import db from app import app -from app.models.models import CaseAssets +from app.models.assets import CaseAssets from app.models.models import CaseReceivedFile from app.models.models import CaseTasks from app.models.cases import Cases from app.models.cases import CasesEvent -from app.models.models import Client +from app.models.customers import Client from app.models.models import CustomAttribute from app.models.iocs import Ioc from app.models.models import Notes diff --git a/source/app/datamgmt/manage/manage_case_classifications_db.py b/source/app/datamgmt/manage/manage_case_classifications_db.py index 09eb1fca8..9bdb998c2 100644 --- a/source/app/datamgmt/manage/manage_case_classifications_db.py +++ b/source/app/datamgmt/manage/manage_case_classifications_db.py @@ -17,7 +17,7 @@ from sqlalchemy import func from typing import List -from app.models.models import CaseClassification +from app.models.cases import CaseClassification def get_case_classifications_list() -> List[dict]: diff --git a/source/app/datamgmt/manage/manage_case_objs.py b/source/app/datamgmt/manage/manage_case_objs.py index 8ed653fcc..361bf16d1 100644 --- a/source/app/datamgmt/manage/manage_case_objs.py +++ b/source/app/datamgmt/manage/manage_case_objs.py @@ -17,9 +17,8 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import func -from app.models.models import AnalysisStatus from app.models.models import IocType -from app.models.models import AssetsType +from app.models.assets import AssetsType, AnalysisStatus from app.models.models import EventCategory diff --git a/source/app/datamgmt/manage/manage_case_state_db.py b/source/app/datamgmt/manage/manage_case_state_db.py index 3e6ff268f..e1d45b884 100644 --- a/source/app/datamgmt/manage/manage_case_state_db.py +++ b/source/app/datamgmt/manage/manage_case_state_db.py @@ -17,7 +17,6 @@ from typing import List from app.models.cases import CaseState -from app.schema.marshables import CaseStateSchema def get_case_states_list() -> List[dict]: @@ -26,9 +25,7 @@ def get_case_states_list() -> List[dict]: Returns: List[dict]: List of case state """ - case_state = CaseState.query.all() - - return CaseStateSchema(many=True).dump(case_state) + return CaseState.query.all() def get_case_state_by_id(cur_id: int) -> CaseState: diff --git a/source/app/datamgmt/manage/manage_case_templates_db.py b/source/app/datamgmt/manage/manage_case_templates_db.py index d82975299..db58aedbd 100644 --- a/source/app/datamgmt/manage/manage_case_templates_db.py +++ b/source/app/datamgmt/manage/manage_case_templates_db.py @@ -14,11 +14,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + import marshmallow from datetime import datetime -from typing import List, Optional, Union +from typing import List +from typing import Optional +from typing import Union -from app import db +from app.datamgmt.db_operations import db_create +from app.db import db from app.datamgmt.case.case_tasks_db import add_task from app.datamgmt.manage.manage_case_classifications_db import get_case_classification_by_name from app.iris_engine.module_handler.module_handler import call_modules_hook @@ -27,7 +31,6 @@ from app.models.models import Tags from app.models.models import NoteDirectory from app.models.authorization import User -from app.schema.marshables import CaseSchema from app.schema.marshables import CaseTaskSchema from app.schema.marshables import CaseNoteDirectorySchema from app.schema.marshables import CaseNoteSchema @@ -155,7 +158,7 @@ def validate_case_template(data: dict, update: bool = False) -> Optional[str]: return str(e) -def case_template_pre_modifier(case_schema: CaseSchema, case_template_id: str): +def case_template_pre_modifier(case_schema: Cases, case_template_id: str): case_template = get_case_template_by_id(int(case_template_id)) if not case_template: return None @@ -170,8 +173,8 @@ def case_template_pre_modifier(case_schema: CaseSchema, case_template_id: str): def case_template_populate_tasks(case: Cases, case_template: CaseTemplate): + tasks = [] logs = [] - # Update case tasks for task_template in case_template.tasks: try: # validate before saving @@ -180,31 +183,27 @@ def case_template_populate_tasks(case: Cases, case_template: CaseTemplate): # Remap case task template fields # Set status to "To Do" which is ID 1 mapped_task_template = { - "task_title": task_template['title'], - "task_description": task_template['description'] if task_template.get('description') else "", - "task_tags": ",".join(tag for tag in task_template["tags"]) if task_template.get('tags') else "", - "task_status_id": 1 + 'task_title': task_template['title'], + 'task_description': task_template['description'] if task_template.get('description') else '', + 'task_tags': ','.join(tag for tag in task_template['tags']) if task_template.get('tags') else '', + 'task_status_id': 1 } - mapped_task_template = call_modules_hook('on_preload_task_create', data=mapped_task_template, caseid=case.case_id) + mapped_task_template = call_modules_hook('on_preload_task_create', mapped_task_template, caseid=case.case_id) task = task_schema.load(mapped_task_template) + tasks.append(task) + except marshmallow.exceptions.ValidationError as e: + logs.append(e.messages) - assignee_id_list = [] - - ctask = add_task(task=task, - assignee_id_list=assignee_id_list, - user_id=case.user_id, - caseid=case.case_id - ) - - ctask = call_modules_hook('on_postload_task_create', data=ctask, caseid=case.case_id) + # Update case tasks + for task in tasks: + ctask = add_task(task=task, assignee_id_list=[], user_id=case.user_id, caseid=case.case_id) - if not ctask: - logs.append("Unable to create task for internal reasons") + ctask = call_modules_hook('on_postload_task_create', ctask, caseid=case.case_id) - except marshmallow.exceptions.ValidationError as e: - logs.append(e.messages) + if not ctask: + logs.append('Unable to create task for internal reasons') return logs @@ -223,7 +222,7 @@ def case_template_populate_notes(case: Cases, note_dir_template: dict, ng: NoteD } mapped_note_template = call_modules_hook('on_preload_note_create', - data=mapped_note_template, + mapped_note_template, caseid=case.case_id) note_schema.verify_directory_id(mapped_note_template, caseid=ng.case_id) @@ -236,7 +235,7 @@ def case_template_populate_notes(case: Cases, note_dir_template: dict, ng: NoteD db.session.add(note) - note = call_modules_hook('on_postload_note_create', data=note, caseid=case.case_id) + note = call_modules_hook('on_postload_note_create', note, caseid=case.case_id) if not note: logs.append("Unable to add note for internal reasons") @@ -264,8 +263,7 @@ def case_template_populate_note_groups(case: Cases, case_template: CaseTemplate) } note_dir = note_dir_schema.load(mapped_note_dir_template) - db.session.add(note_dir) - db.session.commit() + db_create(note_dir) if not note_dir: logs.append("Unable to add note group for internal reasons") @@ -283,11 +281,11 @@ def case_template_post_modifier(case: Cases, case_template_id: Union[str, int]): case_template = get_case_template_by_id(int(case_template_id)) logs = [] if not case_template: - logs.append(f"Case template {case_template_id} not found") + logs.append(f'Case template {case_template_id} not found') return None, logs # Update summary, we want to append in order not to skip the initial case description - case.description += "\n" + case_template.summary + case.description += '\n' + case_template.summary # Update case tags for tag_str in case_template.tags: diff --git a/source/app/datamgmt/manage/manage_cases_db.py b/source/app/datamgmt/manage/manage_cases_db.py index b28efe9f7..6b43bf1a4 100644 --- a/source/app/datamgmt/manage/manage_cases_db.py +++ b/source/app/datamgmt/manage/manage_cases_db.py @@ -25,18 +25,15 @@ from sqlalchemy.orm import aliased from functools import reduce -from app import db +from app.db import db from app.datamgmt.alerts.alerts_db import search_alert_resolution_by_name from app.datamgmt.case.case_db import get_case_tags from app.datamgmt.manage.manage_case_state_db import get_case_state_by_name from app.datamgmt.conversions import convert_sort_direction from app.datamgmt.authorization import has_deny_all_access_level from app.datamgmt.states import delete_case_states -from app.models.models import CaseAssets from app.models.models import NoteRevisions -from app.models.models import CaseClassification -from app.models.models import alert_assets_association -from app.models.models import CaseStatus +from app.models.assets import alert_assets_association, CaseAssets from app.models.models import TaskAssignee from app.models.models import NoteDirectory from app.models.models import Tags @@ -45,9 +42,9 @@ from app.models.models import CaseEventsIoc from app.models.models import CaseReceivedFile from app.models.models import CaseTasks -from app.models.cases import Cases +from app.models.cases import Cases, CaseStatus, CaseClassification from app.models.cases import CasesEvent -from app.models.models import Client +from app.models.customers import Client from app.models.models import DataStoreFile from app.models.models import DataStorePath from app.models.models import IocAssetLink diff --git a/source/app/datamgmt/manage/manage_common.py b/source/app/datamgmt/manage/manage_common.py index 52b4894f6..d1106675f 100644 --- a/source/app/datamgmt/manage/manage_common.py +++ b/source/app/datamgmt/manage/manage_common.py @@ -17,7 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import func -from app import db +from app.db import db from app.models.alerts import Severity diff --git a/source/app/datamgmt/manage/manage_groups_db.py b/source/app/datamgmt/manage/manage_groups_db.py index 84139a3b8..157acd70b 100644 --- a/source/app/datamgmt/manage/manage_groups_db.py +++ b/source/app/datamgmt/manage/manage_groups_db.py @@ -16,25 +16,21 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import and_ -from app import db +from app.datamgmt.db_operations import db_delete +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.case.case_db import get_case from app.datamgmt.manage.manage_cases_db import list_cases_id -from app.iris_engine.access_control.utils import ac_access_level_mask_from_val_list, ac_ldp_group_removal +from app.iris_engine.access_control.utils import ac_ldp_group_removal from app.iris_engine.access_control.utils import ac_access_level_to_list from app.iris_engine.access_control.utils import ac_auto_update_user_effective_access from app.iris_engine.access_control.utils import ac_permission_to_list from app.models.cases import Cases from app.models.authorization import Group +from app.models.authorization import ac_access_level_mask_from_val_list from app.models.authorization import GroupCaseAccess from app.models.authorization import User from app.models.authorization import UserGroup -from app.schema.marshables import AuthorizationGroupSchema - - -def create_group(group: Group): - db.session.add(group) - db.session.commit() def get_groups_list(): @@ -47,9 +43,7 @@ def update_group(): db.session.commit() -def get_groups_list_hr_perms(): - groups = get_groups_list() - +def get_users_by_group_identifiers(): get_membership_list = UserGroup.query.with_entities( UserGroup.group_id, User.user, @@ -57,28 +51,21 @@ def get_groups_list_hr_perms(): User.name ).join(UserGroup.user).all() - membership_list = {} + result = {} for member in get_membership_list: - if member.group_id not in membership_list: - membership_list[member.group_id] = [{ + if member.group_id not in result: + result[member.group_id] = [{ 'user': member.user, 'name': member.name, 'id': member.id }] else: - membership_list[member.group_id].append({ + result[member.group_id].append({ 'user': member.user, 'name': member.name, 'id': member.id }) - - groups = AuthorizationGroupSchema().dump(groups, many=True) - for group in groups: - perms = ac_permission_to_list(group['group_permissions']) - group['group_permissions_list'] = perms - group['group_members'] = membership_list.get(group['group_id'], []) - - return groups + return result def get_group(group_id): @@ -219,8 +206,7 @@ def delete_group(group): UserGroup.query.filter(UserGroup.group_id == group.group_id).delete() GroupCaseAccess.query.filter(GroupCaseAccess.group_id == group.group_id).delete() - db.session.delete(group) - db.session.commit() + db_delete(group) def add_case_access_to_group(group, cases_list, access_level): diff --git a/source/app/datamgmt/manage/manage_organisations.py b/source/app/datamgmt/manage/manage_organisations.py new file mode 100644 index 000000000..4bcd21307 --- /dev/null +++ b/source/app/datamgmt/manage/manage_organisations.py @@ -0,0 +1,24 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 app.db import db +from app.models.authorization import Organisation + + +def get_organisation_by_name(name) -> Organisation: + return db.session.query(Organisation).filter_by(org_name=name).first() diff --git a/source/app/datamgmt/manage/manage_srv_settings_db.py b/source/app/datamgmt/manage/manage_srv_settings_db.py index a7e77b282..f76626e34 100644 --- a/source/app/datamgmt/manage/manage_srv_settings_db.py +++ b/source/app/datamgmt/manage/manage_srv_settings_db.py @@ -1,6 +1,6 @@ from sqlalchemy import text -from app import db +from app.db import db from app.models.models import ServerSettings from app.schema.marshables import ServerSettingsSchema diff --git a/source/app/datamgmt/manage/manage_tags_db.py b/source/app/datamgmt/manage/manage_tags_db.py index a7befe2e6..085963af7 100644 --- a/source/app/datamgmt/manage/manage_tags_db.py +++ b/source/app/datamgmt/manage/manage_tags_db.py @@ -21,7 +21,7 @@ from sqlalchemy import and_ from flask_sqlalchemy.pagination import Pagination -from app import db +from app.db import db from app.logger import logger from app.models.models import Tags from app.datamgmt.conversions import convert_sort_direction @@ -89,4 +89,3 @@ def add_db_tag(tag_title, tag_namespace=None): raise e return tag - diff --git a/source/app/datamgmt/manage/manage_users_db.py b/source/app/datamgmt/manage/manage_users_db.py index 7a536dcf6..c8f2f1d3c 100644 --- a/source/app/datamgmt/manage/manage_users_db.py +++ b/source/app/datamgmt/manage/manage_users_db.py @@ -24,21 +24,21 @@ from sqlalchemy import and_ from app.blueprints.iris_user import iris_current_user +from app.datamgmt.db_operations import db_create from app.logger import logger from app import bc -from app import db +from app.db import db from app.datamgmt.case.case_db import get_case from app.datamgmt.conversions import convert_sort_direction -from app.iris_engine.access_control.utils import ac_access_level_mask_from_val_list from app.iris_engine.access_control.utils import ac_ldp_group_removal from app.iris_engine.access_control.utils import ac_access_level_to_list from app.iris_engine.access_control.utils import ac_auto_update_user_effective_access from app.iris_engine.access_control.utils import ac_get_detailed_effective_permissions_from_groups from app.iris_engine.access_control.utils import ac_remove_case_access_from_user from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.models import UserActivity -from app.models.authorization import CaseAccessLevel +from app.models.authorization import CaseAccessLevel, ac_access_level_mask_from_val_list from app.models.authorization import UserClient from app.models.authorization import Group from app.models.authorization import Organisation @@ -150,8 +150,7 @@ def add_user_to_customer(user_id, customer_id): user_client.client_id = customer_id user_client.access_level = CaseAccessLevel.full_access.value user_client.allow_alerts = True - db.session.add(user_client) - db.session.commit() + db_create(user_client) ac_auto_update_user_effective_access(user_id) @@ -258,7 +257,6 @@ def change_user_primary_org(user_id, old_org_id, new_org_id): uo_new.is_primary_org = True db.session.commit() - return def add_user_to_organisation(user_id, org_id, make_primary=False): @@ -272,8 +270,7 @@ def add_user_to_organisation(user_id, org_id, make_primary=False): if uo_exists: uo_exists.is_primary_org = make_primary db.session.commit() - - return True + return # Check if user has a primary org already prim_org = get_user_primary_org(user_id=user_id) @@ -286,9 +283,7 @@ def add_user_to_organisation(user_id, org_id, make_primary=False): uo.user_id = user_id uo.org_id = org_id uo.is_primary_org = prim_org is None - db.session.add(uo) - db.session.commit() - return True + db_create(uo) def get_user_primary_org(user_id): @@ -324,14 +319,12 @@ def add_user_to_group(user_id, group_id): ).scalar() if exists: - return True + return ug = UserGroup() ug.user_id = user_id ug.group_id = group_id - db.session.add(ug) - db.session.commit() - return True + db_create(ug) def get_user_organisations(user_id): diff --git a/source/app/datamgmt/reporter/report_db.py b/source/app/datamgmt/reporter/report_db.py index e4690a72e..1a94b19da 100644 --- a/source/app/datamgmt/reporter/report_db.py +++ b/source/app/datamgmt/reporter/report_db.py @@ -22,11 +22,8 @@ from app.datamgmt.case.case_notes_db import get_notes_from_group from app.datamgmt.case.case_notes_db import get_case_note_comments -from app.models.models import AnalysisStatus -from app.models.models import CompromiseStatus +from app.models.assets import CompromiseStatus, AssetsType, CaseAssets, AnalysisStatus from app.models.models import TaskAssignee -from app.models.models import AssetsType -from app.models.models import CaseAssets from app.models.models import CaseEventsAssets from app.models.models import CaseEventsIoc from app.models.models import CaseReceivedFile diff --git a/source/app/datamgmt/states.py b/source/app/datamgmt/states.py index 48463720f..c22a3e030 100644 --- a/source/app/datamgmt/states.py +++ b/source/app/datamgmt/states.py @@ -19,7 +19,7 @@ from datetime import datetime from sqlalchemy import and_ -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.models import ObjectState diff --git a/source/tests/unit/blueprints/case/test_case_rfiles_routes.py b/source/app/db.py similarity index 56% rename from source/tests/unit/blueprints/case/test_case_rfiles_routes.py rename to source/app/db.py index 6395e7333..d7171a351 100644 --- a/source/tests/unit/blueprints/case/test_case_rfiles_routes.py +++ b/source/app/db.py @@ -1,5 +1,5 @@ # IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) +# Copyright (C) 2025 - Airbus CyberSecurity (SAS) # ir@cyberactionlab.net # # This program is free software; you can redistribute it and/or @@ -16,21 +16,16 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from functools import partial +import json +import collections -from unittest import TestCase +from flask_sqlalchemy import SQLAlchemy -from app import app -from tests.test_helper import TestHelper -app.testing = True +SQLALCHEMY_ENGINE_OPTIONS = { + "json_deserializer": partial(json.loads, object_pairs_hook=collections.OrderedDict), + "pool_pre_ping": True +} - -class TestCaseRfilesRoutes(TestCase): - def setUp(self) -> None: - self._test_helper = TestHelper() - - def test_case_get_case_rfiles_should_redirect_to_cid_1_if_no_cid_is_provided(self): - self._test_helper.verify_path_without_cid_redirects_correctly( - 'case_rfiles.case_rfile', - 'You should be redirected automatically to target URL: /case/evidences?cid=1' - ) +db = SQLAlchemy(engine_options=SQLALCHEMY_ENGINE_OPTIONS) # flask-sqlalchemy diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index 176e46956..3c70e2f34 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -1,12 +1,12 @@ -from flask import session from sqlalchemy import and_ -from app import db +from app.db import db from app.business.access_controls import set_case_effective_access_for_user +from app.datamgmt.manage.manage_access_control_db import add_several_user_effective_access from app.logger import logger from app.blueprints.iris_user import iris_current_user from app.models.cases import Cases -from app.models.models import Client +from app.models.customers import Client from app.models.authorization import CaseAccessLevel from app.models.authorization import ac_flag_match_mask from app.models.authorization import UserClient @@ -320,33 +320,11 @@ def ac_add_users_multi_effective_access(users_list, cases_list, access_level): Add multiple users to multiple cases with a specific access level """ for case_id in cases_list: - ac_add_user_effective_access(users_list, case_id=case_id, access_level=access_level) + add_several_user_effective_access(users_list, case_identifier=case_id, access_level=access_level) return -def ac_add_user_effective_access(users_list, case_id, access_level): - """ - Directly add a set of effective user access - """ - - UserCaseEffectiveAccess.query.filter( - UserCaseEffectiveAccess.case_id == case_id, - UserCaseEffectiveAccess.user_id.in_(users_list) - ).delete() - - access_to_add = [] - for user_id in users_list: - ucea = UserCaseEffectiveAccess() - ucea.user_id = user_id - ucea.case_id = case_id - ucea.access_level = access_level - access_to_add.append(ucea) - - db.session.add_all(access_to_add) - db.session.commit() - - def ac_add_user_effective_access_from_map(users_map, case_id): """ Directly add a set of effective user access @@ -368,47 +346,58 @@ def ac_add_user_effective_access_from_map(users_map, case_id): db.session.commit() -def ac_set_new_case_access(org_members, case_id, customer_id = None): +def ac_set_new_case_access(user, case_id, customer_id): """ Set a new case access """ users = ac_apply_autofollow_groups_access(case_id) - if iris_current_user.id in users: - del users[iris_current_user.id] + if user.id in users: + del users[user.id] users_full = User.query.with_entities(User.id).all() users_full_access = list(set([u.id for u in users_full]) - set(users.keys())) # Default users case access - Full access - ac_add_user_effective_access(users_full_access, case_id, CaseAccessLevel.deny_all.value) + add_several_user_effective_access(users_full_access, case_id, CaseAccessLevel.deny_all.value) + + set_user_case_access(user, case_id) + + add_several_user_effective_access([user.id], case_id, CaseAccessLevel.full_access.value) + + # Add customer permissions for all users belonging to the customer + if customer_id: + users_client = get_user_access_levels_by_customer(customer_id) + users_map = {u.user_id: u.access_level for u in users_client} + ac_add_user_effective_access_from_map(users_map, case_id) + +# TODO move down into app.datamgmt.manage.manage_access_control_db +def get_user_access_levels_by_customer(customer_id): + users_client = UserClient.query.filter( + UserClient.client_id == customer_id + ).with_entities( + UserClient.user_id, + UserClient.access_level + ).all() + return users_client + + +# TODO try to move down into app.datamgmt.manage.manage_users_db +def set_user_case_access(user, case_id): # Add specific right for the user creating the case UserCaseAccess.query.filter( UserCaseAccess.case_id == case_id, - UserCaseAccess.user_id == iris_current_user.id + UserCaseAccess.user_id == user.id ).delete() db.session.commit() uca = UserCaseAccess() uca.case_id = case_id - uca.user_id = iris_current_user.id + uca.user_id = user.id uca.access_level = CaseAccessLevel.full_access.value db.session.add(uca) db.session.commit() - ac_add_user_effective_access([iris_current_user.id], case_id, CaseAccessLevel.full_access.value) - - # Add customer permissions for all users belonging to the customer - if customer_id: - users_client = UserClient.query.filter( - UserClient.client_id == customer_id - ).with_entities( - UserClient.user_id, - UserClient.access_level - ).all() - users_map = { u.user_id: u.access_level for u in users_client } - ac_add_user_effective_access_from_map(users_map, case_id) - def ac_apply_autofollow_groups_access(case_id): """ @@ -845,28 +834,3 @@ def ac_access_level_to_list(access_level): }) return access_levels - - -def ac_access_level_mask_from_val_list(access_levels): - """ - Return an access level mask from a list of access levels - """ - am = 0 - for acc in access_levels: - am |= int(acc) - - return am - - -def ac_user_has_permission(user, permission): - """ - Return True if user has permission - """ - return ac_flag_match_mask(ac_get_effective_permissions_of_user(user), permission.value) - - -def ac_current_user_has_permission(permission): - """ - Return True if current user has permission - """ - return ac_flag_match_mask(session['permissions'], permission.value) diff --git a/source/app/iris_engine/demo_builder.py b/source/app/iris_engine/demo_builder.py index 6059012cb..51bb69297 100644 --- a/source/app/iris_engine/demo_builder.py +++ b/source/app/iris_engine/demo_builder.py @@ -15,12 +15,15 @@ # 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 random import string from app import app from app import bc -from app import db +from app.business.customers import customers_get_by_name, customers_create +from app.datamgmt.case.case_db import case_db_save +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.manage.manage_groups_db import add_case_access_to_group from app.datamgmt.manage.manage_users_db import add_user_to_group @@ -28,8 +31,8 @@ from app.datamgmt.manage.manage_users_db import user_exists from app.iris_engine.access_control.utils import ac_add_users_multi_effective_access from app.models.cases import Cases -from app.models.models import Client -from app.models.models import get_or_create +from app.models.errors import ObjectNotFoundError +from app.models.customers import Client from app.models.authorization import CaseAccessLevel from app.models.authorization import User @@ -135,14 +138,20 @@ def create_demo_users(def_org, gadm, ganalystes, users_count, seed_user, adm_cou return users +def safe_create_customer(name, description): + try: + return customers_get_by_name(name) + except ObjectNotFoundError: + customer = Client(name=name, description=description) + customers_create(customer) + return customer + + def create_demo_cases(users_data: dict = None, cases_count: int = 0, clients_count: int = 0): clients = [] for client_index in range(0, clients_count): - client = get_or_create(db.session, - Client, - name=f'Client {client_index}', - description=f'Description for client {client_index}') + client = safe_create_customer(f'Client {client_index}', f'Description for client {client_index}') clients.append(client.client_id) cases_list = [] @@ -159,8 +168,7 @@ def create_demo_cases(users_data: dict = None, cases_count: int = 0, clients_cou client_id=random.choice(clients) ) - case.validate_on_build() - case.save() + case_db_save(case) db.session.commit() cases_list.append(case.case_id) @@ -196,8 +204,7 @@ def create_demo_cases(users_data: dict = None, cases_count: int = 0, clients_cou user=random.choice(users_data['admins']), client_id=random.choice(clients) ) - case.validate_on_build() - case.save() + case_db_save(case) db.session.commit() cases_list.append(case.case_id) diff --git a/source/app/iris_engine/module_handler/module_handler.py b/source/app/iris_engine/module_handler/module_handler.py index 2e66840c4..8ac12cf7c 100644 --- a/source/app/iris_engine/module_handler/module_handler.py +++ b/source/app/iris_engine/module_handler/module_handler.py @@ -17,8 +17,8 @@ # 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 traceback +import traceback import base64 import importlib from packaging import version @@ -30,8 +30,9 @@ from app.blueprints.iris_user import iris_current_user from app.logger import logger from app import celery -from app import db +from app.db import db from app.datamgmt.iris_engine.modules_db import get_module_config_from_hname +from app.datamgmt.iris_engine.modules_db import deregister_module_from_hook from app.datamgmt.iris_engine.modules_db import iris_module_add from app.datamgmt.iris_engine.modules_db import iris_module_exists from app.datamgmt.iris_engine.modules_db import modules_list_pipelines @@ -398,15 +399,8 @@ def deregister_from_hook(module_id: int, iris_hook_name: str): :return: IrisInterfaceStatus object """ logger.info(f'Deregistering module #{module_id} from {iris_hook_name}') - hooks = IrisModuleHook.query.filter( - IrisModuleHook.module_id == module_id, - IrisHook.hook_name == iris_hook_name, - IrisModuleHook.hook_id == IrisHook.id - ).all() - if hooks: - for hook in hooks: - logger.info(f'Deregistered module #{module_id} from {iris_hook_name}') - db.session.delete(hook) + deregister_module_from_hook(module_id, iris_hook_name) + logger.info(f'Deregistered module #{module_id} from {iris_hook_name}') return True, ['Hook deregistered'] @@ -416,6 +410,11 @@ def task_hook_wrapper(self, module_name, hook_name, hook_ui_name, data, init_use """ Wrap a hook call into a Celery task to run asynchronously + note: even if the caseid parameter does not seem very useful, it is used when tasks are retrieved to display the + table of "DFIR-IRIS Module Tasks". + see endpoint /dim/tasks/list + see tests_rest_module_tasks.TestsRestModuleTasks.test_get_module_tasks_should_return_case_identifier + :param self: Task instance :param module_name: Module name to instanciate and call :param hook_name: Name of the hook which was triggered @@ -581,7 +580,7 @@ def call_modules_hook(hook_name: str, data: any, caseid: int = None, hook_ui_nam return data -def call_deprecated_on_preload_modules_hook(hook_name: str, data: any, case_identifier) -> any: +def call_deprecated_on_preload_modules_hook(hook_name: str, data: any, case_identifier=None) -> any: hook_name = f'on_preload_{hook_name}' logger.warning(f'DEPRECATION WARNING: Hook {hook_name} has been deprecated and will be removed in a future version') return call_modules_hook(hook_name, data, caseid=case_identifier) diff --git a/source/app/iris_engine/tasker/tasks.py b/source/app/iris_engine/tasker/tasks.py index b437c54c2..ddde1506f 100644 --- a/source/app/iris_engine/tasker/tasks.py +++ b/source/app/iris_engine/tasker/tasks.py @@ -20,7 +20,7 @@ import urllib.parse from celery.signals import task_prerun -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user from app.datamgmt.case.case_db import get_case from app.iris_engine.module_handler.module_handler import pipeline_dispatcher diff --git a/source/app/iris_engine/updater/__init__.py b/source/app/iris_engine/updater/__init__.py index 139597f9c..8b1378917 100644 --- a/source/app/iris_engine/updater/__init__.py +++ b/source/app/iris_engine/updater/__init__.py @@ -1,2 +1 @@ - diff --git a/source/app/iris_engine/updater/updater.py b/source/app/iris_engine/updater/updater.py index d3b068dfd..59e8d5cd8 100644 --- a/source/app/iris_engine/updater/updater.py +++ b/source/app/iris_engine/updater/updater.py @@ -32,7 +32,7 @@ from app import app from app import cache from app import celery -from app import db +from app.db import db from app import socket_io from app.datamgmt.manage.manage_srv_settings_db import get_server_settings_as_dict from app.iris_engine.backup.backup import backup_iris_db @@ -324,7 +324,7 @@ def call_ext_updater(update_archive, scope, need_reboot): '1' if need_reboot else '0', # Do we need to restart the app '&']) - except Exception as e : + except Exception as e: log.error(str(e)) return False diff --git a/source/app/iris_engine/utils/__init__.py b/source/app/iris_engine/utils/__init__.py index 2e0379bb3..0edbd4a50 100644 --- a/source/app/iris_engine/utils/__init__.py +++ b/source/app/iris_engine/utils/__init__.py @@ -15,4 +15,3 @@ # 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. - diff --git a/source/app/iris_engine/utils/tracker.py b/source/app/iris_engine/utils/tracker.py index 0fb99cb0a..d17cdbda8 100644 --- a/source/app/iris_engine/utils/tracker.py +++ b/source/app/iris_engine/utils/tracker.py @@ -19,13 +19,11 @@ from datetime import datetime from flask import request -import app -from app import db +from app.logger import logger +from app.db import db from app.blueprints.iris_user import iris_current_user from app.models.models import UserActivity -log = app.app.logger - def track_activity(message, caseid=None, ctx_less=False, user_input=False, display_in_ui=True): """ @@ -51,9 +49,9 @@ def track_activity(message, caseid=None, ctx_less=False, user_input=False, displ ua.activity_desc = message.capitalize() if iris_current_user.is_authenticated: - log.info(f"{iris_current_user.user} [#{iris_current_user.id}] :: Case {caseid} :: {ua.activity_desc}") + logger.info(f"{iris_current_user.user} [#{iris_current_user.id}] :: Case {caseid} :: {ua.activity_desc}") else: - log.info(f"Anonymous :: Case {caseid} :: {ua.activity_desc}") + logger.info(f"Anonymous :: Case {caseid} :: {ua.activity_desc}") ua.user_input = user_input ua.display_in_ui = display_in_ui diff --git a/source/app/models/alerts.py b/source/app/models/alerts.py index b8c0b68cd..1723629e1 100644 --- a/source/app/models/alerts.py +++ b/source/app/models/alerts.py @@ -31,8 +31,8 @@ from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship -from app import db -from app.models.models import alert_assets_association +from app.db import db +from app.models.assets import alert_assets_association from app.models.iocs import alert_iocs_association diff --git a/source/app/models/assets.py b/source/app/models/assets.py new file mode 100644 index 000000000..b2f86ba7b --- /dev/null +++ b/source/app/models/assets.py @@ -0,0 +1,102 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 enum + +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy import ForeignKey +from sqlalchemy import BigInteger +from sqlalchemy import UUID +from sqlalchemy import text +from sqlalchemy import Text +from sqlalchemy import DateTime +from sqlalchemy.dialects.postgresql import JSON +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import relationship + +from app.db import db + + +class CompromiseStatus(enum.Enum): + to_be_determined = 0x0 + compromised = 0x1 + not_compromised = 0x2 + unknown = 0x3 + + @classmethod + def has_value(cls, value): + return value in cls._value2member_map_ + + +class AssetsType(db.Model): + __tablename__ = 'assets_type' + + asset_id = Column(Integer, primary_key=True) + asset_name = Column(String(155)) + asset_description = Column(String(255)) + asset_icon_not_compromised = Column(String(255)) + asset_icon_compromised = Column(String(255)) + + +alert_assets_association = Table( + 'alert_assets_association', + db.Model.metadata, + Column('alert_id', ForeignKey('alerts.alert_id'), primary_key=True), + Column('asset_id', ForeignKey('case_assets.asset_id'), primary_key=True) +) + + +class CaseAssets(db.Model): + __tablename__ = 'case_assets' + + asset_id = Column(BigInteger, primary_key=True) + asset_uuid = Column(UUID(as_uuid=True), server_default=text("gen_random_uuid()"), nullable=False) + asset_name = Column(Text) + asset_description = Column(Text) + asset_domain = Column(Text) + asset_ip = Column(Text) + asset_info = Column(Text) + asset_compromise_status_id = Column(Integer, nullable=True) + asset_type_id = Column(ForeignKey('assets_type.asset_id')) + asset_tags = Column(Text) + case_id = Column(ForeignKey('cases.case_id')) + date_added = Column(DateTime) + date_update = Column(DateTime) + user_id = Column(ForeignKey('user.id')) + analysis_status_id = Column(ForeignKey('analysis_status.id')) + custom_attributes = Column(JSON) + asset_enrichment = Column(JSONB) + modification_history = Column(JSON) + + case = relationship('Cases') + user = relationship('User') + asset_type = relationship('AssetsType') + analysis_status = relationship('AnalysisStatus') + + alerts = relationship('Alert', secondary=alert_assets_association, back_populates='assets') + iocs = relationship('IocAssetLink', back_populates='asset') + + +class AnalysisStatus(db.Model): + __tablename__ = 'analysis_status' + + id = Column(Integer, primary_key=True) + name = Column(Text) diff --git a/source/app/models/authorization.py b/source/app/models/authorization.py index 444cc1a27..04030bd42 100644 --- a/source/app/models/authorization.py +++ b/source/app/models/authorization.py @@ -20,7 +20,8 @@ import secrets import uuid from flask_login import UserMixin -from sqlalchemy import BigInteger, JSON +from sqlalchemy import BigInteger +from sqlalchemy import JSON from sqlalchemy import Boolean from sqlalchemy import Column from sqlalchemy import ForeignKey @@ -32,7 +33,7 @@ from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship -from app import db +from app.db import db class CaseAccessLevel(enum.Enum): @@ -257,3 +258,18 @@ def save(self): def ac_flag_match_mask(flag, mask): return (flag & mask) == mask + + +def ac_has_permission_server_administrator(permissions): + return ac_flag_match_mask(permissions, Permissions.server_administrator.value) + + +def ac_access_level_mask_from_val_list(access_levels) -> int: + """ + Return an access level mask from a list of access levels + """ + am = 0 + for acc in access_levels: + am |= int(acc) + + return am diff --git a/source/app/models/cases.py b/source/app/models/cases.py index 622ab1627..a6dcadd0b 100644 --- a/source/app/models/cases.py +++ b/source/app/models/cases.py @@ -15,10 +15,13 @@ # 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 uuid +import enum +import uuid from datetime import datetime + from sqlalchemy import BigInteger +from sqlalchemy import func from sqlalchemy import CheckConstraint from sqlalchemy import Boolean from sqlalchemy import Column @@ -36,15 +39,8 @@ from sqlalchemy.orm import relationship from sqlalchemy.orm import backref -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user -from app.datamgmt.states import update_assets_state -from app.datamgmt.states import update_evidences_state -from app.datamgmt.states import update_ioc_state -from app.datamgmt.states import update_notes_state -from app.datamgmt.states import update_tasks_state -from app.datamgmt.states import update_timeline_state -from app.models.models import Client class Cases(db.Model): @@ -117,48 +113,6 @@ def __init__(self, self.state_id = state_id, self.severity_id = severity_id - def save(self): - """ - Save the current case in database - :return: - """ - # Inject self into db - db.session.add(self) - - # Commit the changes - db.session.commit() - - # Rename case with the ID - self.name = f'#{self.case_id} - {self.name}' - - # Create the states - update_timeline_state(caseid=self.case_id, userid=self.user_id) - update_tasks_state(caseid=self.case_id, userid=self.user_id) - update_evidences_state(caseid=self.case_id, userid=self.user_id) - update_ioc_state(caseid=self.case_id, userid=self.user_id) - update_assets_state(caseid=self.case_id, userid=self.user_id) - update_notes_state(caseid=self.case_id, userid=self.user_id) - - db.session.commit() - - return self - - def validate_on_build(self): - """ - Execute an autocheck of the case metadata and validate it - :return: True if valid else false; Tuple with errors - """ - # TODO : Check if case name already exists - - res = Client.query\ - .with_entities(Client.client_id)\ - .filter(Client.client_id == self.client_id)\ - .first() - - self.client_id = res[0] - - return True, [] - class CaseTags(db.Model): __tablename__ = 'case_tags' @@ -231,3 +185,33 @@ class CaseProtagonist(db.Model): case = relationship('Cases') user = relationship('User') + + +class CaseStatus(enum.Enum): + unknown = 0x0 + false_positive = 0x1 + true_positive_with_impact = 0x2 + not_applicable = 0x3 + true_positive_without_impact = 0x4 + legitimate = 0x5 + + +class ReviewStatusList: + no_review_required = "No review required" + not_reviewed = "Not reviewed" + pending_review = "Pending review" + review_in_progress = "Review in progress" + reviewed = "Reviewed" + + +class CaseClassification(db.Model): + __tablename__ = 'case_classification' + + id = Column(Integer, primary_key=True) + name = Column(Text) + name_expanded = Column(Text) + description = Column(Text) + creation_date = Column(DateTime, server_default=func.now(), nullable=True) + created_by_id = Column(ForeignKey('user.id'), nullable=True) + + created_by = relationship('User') diff --git a/source/app/models/comments.py b/source/app/models/comments.py index 459051ed5..7d5f48fd0 100644 --- a/source/app/models/comments.py +++ b/source/app/models/comments.py @@ -27,7 +27,7 @@ from sqlalchemy import ForeignKey from sqlalchemy.orm import relationship -from app import db +from app.db import db class Comments(db.Model): diff --git a/source/app/models/customers.py b/source/app/models/customers.py new file mode 100644 index 000000000..39b699093 --- /dev/null +++ b/source/app/models/customers.py @@ -0,0 +1,44 @@ +# IRIS Source Code +# Copyright (C) 2025 - DFIR-IRIS +# contact@dfir-iris.org +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from sqlalchemy import Column +from sqlalchemy import BigInteger +from sqlalchemy import UUID +from sqlalchemy import text +from sqlalchemy import Text +from sqlalchemy import DateTime +from sqlalchemy import func +from sqlalchemy import ForeignKey +from sqlalchemy.dialects.postgresql import JSON + +from app.db import db + + +class Client(db.Model): + __tablename__ = 'client' + + client_id = Column(BigInteger, primary_key=True) + client_uuid = Column(UUID(as_uuid=True), server_default=text("gen_random_uuid()"), nullable=False) + name = Column(Text, unique=True) + description = Column(Text) + sla = Column(Text) + creation_date = Column(DateTime, server_default=func.now(), nullable=True) + created_by = Column(ForeignKey('user.id'), nullable=True) + last_update_date = Column(DateTime, server_default=func.now(), nullable=True) + + custom_attributes = Column(JSON) diff --git a/source/app/business/errors.py b/source/app/models/errors.py similarity index 93% rename from source/app/business/errors.py rename to source/app/models/errors.py index df14c580e..f86a81859 100644 --- a/source/app/business/errors.py +++ b/source/app/models/errors.py @@ -41,7 +41,11 @@ def __init__(self): class UnhandledBusinessError(BusinessProcessingError): def __init__(self, message, data=None): - self._message = message - self._data = data + super().__init__(message, data) logger.exception(message) logger.exception(data) + + +class ElementInUseError(BusinessProcessingError): + + pass diff --git a/source/app/models/iocs.py b/source/app/models/iocs.py index 94dfefa49..8ea967435 100644 --- a/source/app/models/iocs.py +++ b/source/app/models/iocs.py @@ -16,7 +16,7 @@ # 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 db +from app.db import db from sqlalchemy import Table from sqlalchemy import Column diff --git a/source/app/models/models.py b/source/app/models/models.py index a4a2f0616..6fb87045c 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -17,12 +17,10 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import datetime -import enum import uuid from sqlalchemy import BigInteger from sqlalchemy import UniqueConstraint -from sqlalchemy import Table from sqlalchemy import Boolean from sqlalchemy import Column from sqlalchemy import DateTime @@ -36,7 +34,6 @@ from sqlalchemy import create_engine from sqlalchemy import text from sqlalchemy.dialects.postgresql import JSON -from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship @@ -44,40 +41,12 @@ from sqlalchemy.sql import func from app import app -from app import db +from app.db import db Base = declarative_base() metadata = Base.metadata -class CaseStatus(enum.Enum): - unknown = 0x0 - false_positive = 0x1 - true_positive_with_impact = 0x2 - not_applicable = 0x3 - true_positive_without_impact = 0x4 - legitimate = 0x5 - - -class ReviewStatusList: - no_review_required = "No review required" - not_reviewed = "Not reviewed" - pending_review = "Pending review" - review_in_progress = "Review in progress" - reviewed = "Reviewed" - - -class CompromiseStatus(enum.Enum): - to_be_determined = 0x0 - compromised = 0x1 - not_compromised = 0x2 - unknown = 0x3 - - @classmethod - def has_value(cls, value): - return value in cls._value2member_map_ - - def create_safe(session, model, **kwargs): instance = session.query(model).filter_by(**kwargs).first() if instance: @@ -105,101 +74,6 @@ def create_safe_limited(session, model, keywords_list, **kwargs): return True -def get_or_create(session, model, **kwargs): - instance = session.query(model).filter_by(**kwargs).first() - if instance: - return instance - else: - instance = model(**kwargs) - session.add(instance) - session.commit() - return instance - - -class Client(db.Model): - __tablename__ = 'client' - - client_id = Column(BigInteger, primary_key=True) - client_uuid = Column(UUID(as_uuid=True), server_default=text("gen_random_uuid()"), nullable=False) - name = Column(Text, unique=True) - description = Column(Text) - sla = Column(Text) - creation_date = Column(DateTime, server_default=func.now(), nullable=True) - created_by = Column(ForeignKey('user.id'), nullable=True) - last_update_date = Column(DateTime, server_default=func.now(), nullable=True) - - custom_attributes = Column(JSON) - - -class AssetsType(db.Model): - __tablename__ = 'assets_type' - - asset_id = Column(Integer, primary_key=True) - asset_name = Column(String(155)) - asset_description = Column(String(255)) - asset_icon_not_compromised = Column(String(255)) - asset_icon_compromised = Column(String(255)) - - -alert_assets_association = Table( - 'alert_assets_association', - db.Model.metadata, - Column('alert_id', ForeignKey('alerts.alert_id'), primary_key=True), - Column('asset_id', ForeignKey('case_assets.asset_id'), primary_key=True) -) - - -class CaseAssets(db.Model): - __tablename__ = 'case_assets' - - asset_id = Column(BigInteger, primary_key=True) - asset_uuid = Column(UUID(as_uuid=True), server_default=text("gen_random_uuid()"), nullable=False) - asset_name = Column(Text) - asset_description = Column(Text) - asset_domain = Column(Text) - asset_ip = Column(Text) - asset_info = Column(Text) - asset_compromise_status_id = Column(Integer, nullable=True) - asset_type_id = Column(ForeignKey('assets_type.asset_id')) - asset_tags = Column(Text) - case_id = Column(ForeignKey('cases.case_id')) - date_added = Column(DateTime) - date_update = Column(DateTime) - user_id = Column(ForeignKey('user.id')) - analysis_status_id = Column(ForeignKey('analysis_status.id')) - custom_attributes = Column(JSON) - asset_enrichment = Column(JSONB) - modification_history = Column(JSON) - - case = relationship('Cases') - user = relationship('User') - asset_type = relationship('AssetsType') - analysis_status = relationship('AnalysisStatus') - - alerts = relationship('Alert', secondary=alert_assets_association, back_populates='assets') - iocs = relationship('IocAssetLink', back_populates='asset') - - -class AnalysisStatus(db.Model): - __tablename__ = 'analysis_status' - - id = Column(Integer, primary_key=True) - name = Column(Text) - - -class CaseClassification(db.Model): - __tablename__ = 'case_classification' - - id = Column(Integer, primary_key=True) - name = Column(Text) - name_expanded = Column(Text) - description = Column(Text) - creation_date = Column(DateTime, server_default=func.now(), nullable=True) - created_by_id = Column(ForeignKey('user.id'), nullable=True) - - created_by = relationship('User') - - class EvidenceTypes(db.Model): __tablename__ = 'evidence_type' diff --git a/source/app/post_init.py b/source/app/post_init.py index af692750b..c11f6fdc5 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -29,15 +29,18 @@ from alembic import command from alembic.config import Config from sqlalchemy import create_engine -from sqlalchemy import exc from sqlalchemy import or_ from sqlalchemy_utils import create_database from sqlalchemy_utils import database_exists from app import bc from app import celery -from app import db -from app.iris_engine.access_control.utils import ac_add_user_effective_access +from app.business.groups import groups_get_by_name +from app.business.groups import groups_create +from app.business.organisations import organisations_get +from app.business.organisations import organisations_create +from app.db import db +from app.datamgmt.manage.manage_access_control_db import add_several_user_effective_access from app.iris_engine.demo_builder import create_demo_cases from app.iris_engine.access_control.utils import ac_get_mask_analyst from app.iris_engine.access_control.utils import ac_get_mask_full_permissions @@ -45,21 +48,18 @@ from app.iris_engine.module_handler.module_handler import instantiate_module_from_name from app.iris_engine.module_handler.module_handler import register_module from app.iris_engine.demo_builder import create_demo_users -from app.models.models import create_safe_limited, AssetsType +from app.models.models import create_safe_limited +from app.models.assets import AssetsType, AnalysisStatus from app.models.alerts import Severity from app.models.alerts import AlertStatus from app.models.alerts import AlertResolutionStatus from app.models.authorization import CaseAccessLevel from app.models.authorization import Group -from app.models.authorization import Organisation from app.models.authorization import User -from app.models.cases import Cases +from app.models.cases import Cases, ReviewStatusList, CaseClassification from app.models.cases import CaseState -from app.models.cases import Client -from app.models.models import AnalysisStatus -from app.models.models import CaseClassification +from app.models.customers import Client from app.models.models import ReviewStatus -from app.models.models import ReviewStatusList from app.models.models import EvidenceTypes from app.models.models import EventCategory from app.models.models import IocType @@ -74,14 +74,18 @@ from app.models.models import create_safe from app.models.models import create_safe_attr from app.business.asset_types import create_asset_type_if_not_exists -from app.models.models import get_or_create +from app.business.customers import customers_get_by_name +from app.business.customers import customers_create +from app.business.cases import cases_get_first_with_customer +from app.models.errors import ObjectNotFoundError from app.datamgmt.iris_engine.modules_db import iris_module_disable_by_id from app.datamgmt.manage.manage_groups_db import add_case_access_to_group from app.datamgmt.manage.manage_users_db import add_user_to_group from app.datamgmt.manage.manage_users_db import add_user_to_organisation -from app.datamgmt.manage.manage_groups_db import get_group_by_name +from app.datamgmt.case.case_db import case_db_save +_INITIAL_CLIENT_NAME = 'IrisInitialClient' _ASSET_TYPES = [ {'asset_name': 'Account', 'asset_description': 'Generic Account', 'asset_icon_not_compromised': 'user.png', 'asset_icon_compromised': 'ioc_user.png'}, @@ -122,6 +126,7 @@ {'asset_name': 'Windows Account - AD - Service', 'asset_description': 'Windows Account - AD - krbtgt', 'asset_icon_not_compromised': 'user.png', 'asset_icon_compromised': 'ioc_user.png'} ] +_DEFAULT_ORGANISATION_NAME = 'Default Org' def connect_to_database(host: str, port: int) -> bool: @@ -696,7 +701,7 @@ def create_safe_assets(): create_asset_type_if_not_exists(db.session, AssetsType(**asse_type)) -def create_safe_client(): +def create_safe_client() -> Client: """Creates a new Client object if it does not already exist. This function creates a new Client object with the specified client name @@ -704,10 +709,12 @@ def create_safe_client(): """ # Create a new Client object if it does not already exist - client = get_or_create(db.session, Client, - name="IrisInitialClient") - - return client + try: + return customers_get_by_name(_INITIAL_CLIENT_NAME) + except ObjectNotFoundError: + customer = Client(name=_INITIAL_CLIENT_NAME) + customers_create(customer) + return customer def create_safe_case(user, client, groups): @@ -719,9 +726,7 @@ def create_safe_case(user, client, groups): """ # Check if a case already exists for the client - case = Cases.query.filter( - Cases.client_id == client.client_id - ).first() + case = cases_get_first_with_customer(client) if not case: # Create a new case for the client @@ -733,22 +738,14 @@ def create_safe_case(user, client, groups): client_id=client.client_id ) - # Validate the case and save it to the database - case.validate_on_build() - case.save() + case_db_save(case) db.session.commit() # Add the specified user and groups to the case with full access level for group in groups: - add_case_access_to_group(group=group, - cases_list=[case.case_id], - access_level=CaseAccessLevel.full_access.value) - ac_add_user_effective_access(users_list=[user.id], - case_id=1, - access_level=CaseAccessLevel.full_access.value) - - return case + add_case_access_to_group(group, [case.case_id], CaseAccessLevel.full_access.value) + add_several_user_effective_access([user.id], 1, CaseAccessLevel.full_access.value) def create_safe_report_types(): @@ -1291,6 +1288,74 @@ def create_safe_server_settings(is_mfa_enabled): password_policy_special_chars="", enforce_mfa=is_mfa_enabled) +def create_safe_default_organisation(): + try: + return organisations_get(_DEFAULT_ORGANISATION_NAME) + except ObjectNotFoundError: + return organisations_create(_DEFAULT_ORGANISATION_NAME, 'Default Organisation') + + +def create_safe_group(name, description, auto_follow, auto_follow_access_level, permissions): + try: + return groups_get_by_name(name) + except ObjectNotFoundError: + group = Group(group_name=name, group_description=description, + group_auto_follow=auto_follow, group_auto_follow_access_level=auto_follow_access_level, + group_permissions=permissions) + return groups_create(group) + + +# TODO is it really necessary to do all that? +# shouldn't the migration upgrade step already have put the database in the expected state? +# and shouldn't we protect modifications on initial entries that are not supposed to be modified +# (maybe comparing the identifier of the objects that are to be updated with the last identifier after database +# initialization) +def create_safe_auth_model(): + """Creates new Organisation, Group, and User objects if they do not already exist. + + This function creates a new Organisation object with the specified name and description, + and creates new Group objects with the specified name, description, auto-follow status, + auto-follow access level, and permissions if they do not already exist in the database. + It also updates the attributes of the existing Group objects if they have changed. + + """ + def_org = create_safe_default_organisation() + + # Create new Administrator Group object + gadm = create_safe_group('Administrators', 'Administrators', True, + CaseAccessLevel.full_access.value, ac_get_mask_full_permissions()) + + # Update Administrator Group object attributes + if gadm.group_permissions != ac_get_mask_full_permissions(): + gadm.group_permissions = ac_get_mask_full_permissions() + + if gadm.group_auto_follow_access_level != CaseAccessLevel.full_access.value: + gadm.group_auto_follow_access_level = CaseAccessLevel.full_access.value + + if gadm.group_auto_follow is not True: + gadm.group_auto_follow = True + + db.session.commit() + + # Create new Analysts Group object + ganalysts = create_safe_group('Analysts', 'Standard Analysts', False, + CaseAccessLevel.full_access.value, ac_get_mask_analyst()) + + # Update Analysts Group object attributes + if ganalysts.group_permissions != ac_get_mask_analyst(): + ganalysts.group_permissions = ac_get_mask_analyst() + + if ganalysts.group_auto_follow: + ganalysts.group_auto_follow = False + + if ganalysts.group_auto_follow_access_level != CaseAccessLevel.full_access.value: + ganalysts.group_auto_follow_access_level = CaseAccessLevel.full_access.value + + db.session.commit() + + return def_org, gadm, ganalysts + + class PostInit: def __init__(self, app): @@ -1322,71 +1387,6 @@ def _create_safe_classifications(self): name_expanded=f"{predicate.title()}: {entry.get('expanded')}", description=entry['description']) - def _create_safe_auth_model(self): - """Creates new Organisation, Group, and User objects if they do not already exist. - - This function creates a new Organisation object with the specified name and description, - and creates new Group objects with the specified name, description, auto-follow status, - auto-follow access level, and permissions if they do not already exist in the database. - It also updates the attributes of the existing Group objects if they have changed. - - """ - # Create new Organisation object - def_org = get_or_create(db.session, Organisation, org_name="Default Org", - org_description="Default Organisation") - - # Create new Administrator Group object - try: - gadm = get_or_create(db.session, Group, group_name='Administrators', group_description='Administrators', - group_auto_follow=True, - group_auto_follow_access_level=CaseAccessLevel.full_access.value, - group_permissions=ac_get_mask_full_permissions()) - - except exc.IntegrityError: - db.session.rollback() - self._logger.warning('Administrator group integrity error. Group permissions were probably changed. Updating.') - gadm = Group.query.filter( - Group.group_name == 'Administrators' - ).first() - - # Update Administrator Group object attributes - if gadm.group_permissions != ac_get_mask_full_permissions(): - gadm.group_permissions = ac_get_mask_full_permissions() - - if gadm.group_auto_follow_access_level != CaseAccessLevel.full_access.value: - gadm.group_auto_follow_access_level = CaseAccessLevel.full_access.value - - if gadm.group_auto_follow is not True: - gadm.group_auto_follow = True - - db.session.commit() - - # Create new Analysts Group object - try: - ganalysts = get_or_create(db.session, Group, group_name='Analysts', group_description='Standard Analysts', - group_auto_follow=False, - group_auto_follow_access_level=CaseAccessLevel.full_access.value, - group_permissions=ac_get_mask_analyst()) - - except exc.IntegrityError: - db.session.rollback() - self._logger.warning('Analysts group integrity error. Group permissions were probably changed. Updating.') - ganalysts = get_group_by_name('Analysts') - - # Update Analysts Group object attributes - if ganalysts.group_permissions != ac_get_mask_analyst(): - ganalysts.group_permissions = ac_get_mask_analyst() - - if ganalysts.group_auto_follow is not False: - ganalysts.group_auto_follow = False - - if ganalysts.group_auto_follow_access_level != CaseAccessLevel.full_access.value: - ganalysts.group_auto_follow_access_level = CaseAccessLevel.full_access.value - - db.session.commit() - - return def_org, gadm, ganalysts - def _create_safe_admin(self, def_org, gadm, admin_username, admin_email, admin_password, api_key): """Creates a new admin user if one does not already exist. @@ -1644,7 +1644,7 @@ def run(self): # Create initial authorization model, administrative user, and customer self._logger.info("Creating initial authorisation model") - def_org, gadm, ganalysts = self._create_safe_auth_model() + def_org, gadm, ganalysts = create_safe_auth_model() self._logger.info("Creating first administrative user") admin_username = self._configuration.get('IRIS_ADM_USERNAME') @@ -1658,15 +1658,11 @@ def run(self): self._logger.info("Registering default modules") self._register_default_modules() - self._logger.info("Creating initial customer") + self._logger.info('Creating initial customer') client = create_safe_client() - self._logger.info("Creating initial case") - create_safe_case( - user=admin, - client=client, - groups=[gadm, ganalysts] - ) + self._logger.info('Creating initial case') + create_safe_case(admin, client, [gadm, ganalysts]) # Setup symlinks for custom_assets self._logger.info('Creating symlinks for custom asset icons') diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 354a8c51d..71b015571 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -42,7 +42,8 @@ from typing import Tuple from typing import Union from werkzeug.datastructures import FileStorage -from app import db +from app.business.customers import customers_exists_another_with_same_name +from app.db import db from app import ma from app.blueprints.iris_user import iris_current_user from app.logger import logger @@ -51,24 +52,20 @@ from app.datamgmt.manage.manage_tags_db import add_db_tag from app.datamgmt.case.case_iocs_db import get_ioc_links from app.iris_engine.access_control.utils import ac_mask_from_val_list -from app.models.models import AnalysisStatus -from app.models.models import CaseClassification from app.models.models import SavedFilter from app.models.models import DataStorePath from app.models.models import IrisModuleHook from app.models.models import Tags from app.models.models import ReviewStatus from app.models.models import EvidenceTypes -from app.models.models import CaseStatus from app.models.models import NoteDirectory from app.models.models import NoteRevisions -from app.models.models import AssetsType -from app.models.models import CaseAssets +from app.models.assets import AssetsType, CaseAssets, AnalysisStatus from app.models.models import CaseReceivedFile from app.models.models import CaseTasks -from app.models.cases import Cases +from app.models.cases import Cases, CaseStatus, CaseClassification from app.models.cases import CasesEvent -from app.models.models import Client +from app.models.customers import Client from app.models.comments import Comments from app.models.models import Contact from app.models.models import DataStoreFile @@ -98,6 +95,7 @@ from app.business.users import get_primary_organisation from app.business.users import get_organisations from app.datamgmt.case.assets_type import get_asset_type_by_name_case_insensitive +from app.iris_engine.access_control.utils import ac_get_fast_user_cases_access ALLOWED_EXTENSIONS = {'png', 'svg'} @@ -628,11 +626,11 @@ def verify_unique(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: """ assert_type_mml(input_var=data.asset_name, - field_name="asset_name", + field_name='asset_name', type=str) assert_type_mml(input_var=data.asset_id, - field_name="asset_id", + field_name='asset_id', type=int, allow_none=True) @@ -941,7 +939,8 @@ class IocSchemaForAPIV2(ma.SQLAlchemyAutoSchema): tlp = ma.Nested(TlpSchema) def get_link(self, ioc): - ial = get_ioc_links(ioc.ioc_id) + user_search_limitations = ac_get_fast_user_cases_access(iris_current_user.id) + ial = get_ioc_links(ioc.ioc_id, user_search_limitations) return [row._asdict() for row in ial] link = ma.Method('get_link') @@ -1784,6 +1783,16 @@ class Meta: exclude = ['name', 'client_id', 'description', 'sla'] unknown = EXCLUDE + @pre_load + def verify_unique_name(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: + if 'customer_name' not in data: + return data + identifier = data.get('customer_id') + name = data['customer_name'] + if customers_exists_another_with_same_name(identifier, name): + raise ValidationError('Customer already exists', field_name='customer_name') + return data + @post_load def verify_unique(self, data: Client, **kwargs: Any) -> Client: """Verifies that the customer name is unique. @@ -1810,13 +1819,6 @@ def verify_unique(self, data: Client, **kwargs: Any) -> Client: type=int, allow_none=True) - client = Client.query.filter( - func.upper(Client.name) == data.name.upper(), - Client.client_id != data.client_id - ).first() - if client: - raise ValidationError("Customer already exists", field_name="customer_name") - return data @post_load @@ -2157,7 +2159,7 @@ def verify_unique(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: for organisation in organisations: if data.get('org_id') is None or organisation.org_id != data.get('org_id'): - raise ValidationError("Organisation name already exists", field_name="org_name") + raise ValidationError('Organisation name already exists', field_name='org_name') return data @@ -2184,74 +2186,6 @@ class Meta: unknown = EXCLUDE -def validate_ioc_type(type_id: int) -> None: - """Validates the IOC type ID. - - This function validates the IOC type ID by checking if it exists in the database. - If the ID is invalid, it raises a validation error. - - Args: - type_id: The IOC type ID to validate. - - Raises: - ValidationError: If the IOC type ID is invalid. - - """ - if not IocType.query.get(type_id): - raise ValidationError("Invalid ioc_type ID") - - -def validate_ioc_tlp(tlp_id: int) -> None: - """Validates the IOC TLP ID. - - This function validates the IOC TLP ID by checking if it exists in the database. - If the ID is invalid, it raises a validation error. - - Args: - tlp_id: The IOC TLP ID to validate. - - Raises: - ValidationError: If the IOC TLP ID is invalid. - - """ - if not Tlp.query.get(tlp_id): - raise ValidationError("Invalid ioc_tlp ID") - - -def validate_asset_type(asset_id: int) -> None: - """Validates the asset type ID. - - This function validates the asset type ID by checking if it exists in the database. - If the ID is invalid, it raises a validation error. - - Args: - asset_id: The asset type ID to validate. - - Raises: - ValidationError: If the asset type ID is invalid. - - """ - if not AssetsType.query.get(asset_id): - raise ValidationError("Invalid asset_type ID") - - -def validate_asset_tlp(tlp_id: int) -> None: - """Validates the asset TLP ID. - - This function validates the asset TLP ID by checking if it exists in the database. - If the ID is invalid, it raises a validation error. - - Args: - tlp_id: The asset TLP ID to validate. - - Raises: - ValidationError: If the asset TLP ID is invalid. - - """ - if not Tlp.query.get(tlp_id): - raise ValidationError("Invalid asset_tlp ID") - - class SeveritySchema(ma.SQLAlchemyAutoSchema): """Schema for serializing and deserializing Severity objects. diff --git a/source/app/util.py b/source/app/util.py index 111db8247..d2fac7180 100644 --- a/source/app/util.py +++ b/source/app/util.py @@ -29,7 +29,7 @@ from sqlalchemy.orm.attributes import flag_modified from flask import current_app -from app import db +from app.db import db from app.blueprints.iris_user import iris_current_user diff --git a/source/run.py b/source/run.py index 32dbe6204..8a59458b6 100644 --- a/source/run.py +++ b/source/run.py @@ -28,4 +28,3 @@ if __name__ == "__main__": socket_io.run(app, host='127.0.0.1', port=8000, debug=True) - diff --git a/source/app/datamgmt/dashboard/__init__.py b/source/tests/app/__init__.py similarity index 100% rename from source/app/datamgmt/dashboard/__init__.py rename to source/tests/app/__init__.py diff --git a/source/app/datamgmt/exceptions/__init__.py b/source/tests/app/blueprints/__init__.py similarity index 100% rename from source/app/datamgmt/exceptions/__init__.py rename to source/tests/app/blueprints/__init__.py diff --git a/source/tests/unit/__init__.py b/source/tests/app/blueprints/rest/__init__.py similarity index 100% rename from source/tests/unit/__init__.py rename to source/tests/app/blueprints/rest/__init__.py diff --git a/source/tests/unit/blueprints/case/test_case_tasks_routes.py b/source/tests/app/blueprints/rest/test_parsing.py similarity index 57% rename from source/tests/unit/blueprints/case/test_case_tasks_routes.py rename to source/tests/app/blueprints/rest/test_parsing.py index cdc3d7240..f7cc253de 100644 --- a/source/tests/unit/blueprints/case/test_case_tasks_routes.py +++ b/source/tests/app/blueprints/rest/test_parsing.py @@ -1,6 +1,6 @@ # IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net +# Copyright (C) 2025 - 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 @@ -16,21 +16,13 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - from unittest import TestCase -from app import app -from tests.test_helper import TestHelper - -app.testing = True +from app.blueprints.rest.parsing import parse_comma_separated_identifiers -class TestCaseTasksRoutes(TestCase): - def setUp(self) -> None: - self._test_helper = TestHelper() +class TestCaseParsing(TestCase): - def test_case_get_tasks_should_redirect_to_cid_1_if_no_cid_is_provided(self): - self._test_helper.verify_path_without_cid_redirects_correctly( - 'case_tasks.case_tasks', - 'You should be redirected automatically to target URL: /case/tasks?cid=1' - ) + def test_parse_comma_separated_identifiers_should_return_identifiers(self): + result = parse_comma_separated_identifiers('a,b') + self.assertEqual(['a', 'c'], result) diff --git a/source/tests/test_helper.py b/source/tests/test_helper.py deleted file mode 100644 index 9809a7170..000000000 --- a/source/tests/test_helper.py +++ /dev/null @@ -1,50 +0,0 @@ -# IRIS Source Code -# Copyright (C) 2021 - Airbus CyberSecurity (SAS) -# ir@cyberactionlab.net -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -from os import environ -from unittest import TestCase - -import re -from flask import url_for -from flask.testing import FlaskClient - -from app import app - - -class TestHelper(TestCase): - @staticmethod - def log_in(test_app: FlaskClient) -> None: - login_page = test_app.get('/login') - - csrf_token = re.search(r'id="csrf_token" name="csrf_token" type="hidden" value="(.*?)"', str(login_page.data)).group(1) - - test_app.post('/login', data=dict(username='administrator', password=environ.get("IRIS_ADM_PASSWORD", ""), csrf_token=csrf_token), follow_redirects=True) - - def verify_path_without_cid_redirects_correctly(self, path: str, assert_string: str): - with app.test_client() as test_app: - self.log_in(test_app) - - result = test_app.get(url_for(path)) - - self.assertEqual(302, result.status_code) - self.assertIn(assert_string, str(result.data)) - - result2 = test_app.get(url_for(path), follow_redirects=True) - - self.assertEqual(200, result2.status_code) diff --git a/source/tests/unit/blueprints/__init__.py b/source/tests/unit/blueprints/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/source/tests/unit/blueprints/case/__init__.py b/source/tests/unit/blueprints/case/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/source/tests/unit/blueprints/manage/__init__.py b/source/tests/unit/blueprints/manage/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/source/tests/unit/datamgmt/__init__.py b/source/tests/unit/datamgmt/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/README.md b/tests/README.md index 6f398cbac..b32de4125 100644 --- a/tests/README.md +++ b/tests/README.md @@ -13,6 +13,12 @@ First activate the virtual environment: source ./venv/bin/activate ``` +Then start the development configuration of DFIR-IRIS server: +``` +cp ../.env.tests.model ../.env +docker compose --file ../docker-compose.dev.yml up --detach --wait +``` + Then run: ``` python -m unittest --verbose @@ -20,5 +26,20 @@ python -m unittest --verbose To execute only one test, suffix with the fully qualified test name. Example: ``` -python -m unittest tests_rest.TestsRest.test_create_asset_should_not_fail +python -m unittest tests_rest_assets.TestsRestAssets.test_create_asset_should_return_201 +``` + +Tip: this is a way to spped up the develop/run test loop. To restart only the `app` docker, do: +``` +docker compose stop app && docker compose --file ../docker-compose.dev.yml start app +``` + +Finally, stop the development server: +``` +docker compose down +``` + +Tip: if you want to clear database data: +``` +docker volume rm iris-web_db_data ``` diff --git a/tests/iris.py b/tests/iris.py index ee45b60e0..37e593bee 100644 --- a/tests/iris.py +++ b/tests/iris.py @@ -31,7 +31,7 @@ _ADMINISTRATOR_USER_LOGIN = 'administrator' ADMINISTRATOR_USER_IDENTIFIER = 1 _INITIAL_DEMO_CASE_IDENTIFIER = 1 -_IRIS_INITIAL_CUSTOMER_IDENTIFIER = 1 +IRIS_INITIAL_CUSTOMER_IDENTIFIER = 1 IRIS_PERMISSION_SERVER_ADMINISTRATOR = 0x2 IRIS_PERMISSION_ALERTS_READ = 0x4 @@ -104,11 +104,11 @@ def create_dummy_customer(self) -> int: response = self.create('/manage/customers/add', {'customer_name': f'customer{uuid4()}'}).json() return response['data']['customer_id'] - def create_dummy_case(self): + def create_dummy_case(self, customer_identifier=IRIS_INITIAL_CUSTOMER_IDENTIFIER): body = { 'case_name': 'case name', 'case_description': 'description', - 'case_customer_id': 1, + 'case_customer_id': customer_identifier, 'case_soc_id': '' } response = self._api.post('/api/v2/cases', body).json() @@ -134,12 +134,16 @@ def clear_database(self): for alert in response['data']: identifier = alert['alert_id'] self.delete(f'/api/v2/alerts/{identifier}') + response = self.get('/global/tasks/list').json() + for global_task in response['data']['tasks']: + identifier = global_task['task_id'] + self.create(f'/global/tasks/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.delete(f'/api/v2/manage/users/{identifier}') - body = {'customers_membership': [_IRIS_INITIAL_CUSTOMER_IDENTIFIER]} + body = {'customers_membership': [IRIS_INITIAL_CUSTOMER_IDENTIFIER]} self.create(f'/manage/users/{ADMINISTRATOR_USER_IDENTIFIER}/customers/update', body) customers = self.get('/manage/customers/list').json() for customer in customers['data']: diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py new file mode 100644 index 000000000..8fb0c1f31 --- /dev/null +++ b/tests/tests_rest_alerts_filters.py @@ -0,0 +1,578 @@ +# IRIS Source Code +# Copyright (C) 2023 - DFIR-IRIS +# contact@dfir-iris.org +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from unittest import TestCase +from iris import Iris + +_IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 + + +class TestsRestAlertsFilters(TestCase): + + def setUp(self) -> None: + self._subject = Iris() + + def tearDown(self): + self._subject.clear_database() + + def test_create_alert_filter_should_return_201(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + response = self._subject.create('/api/v2/alerts-filters', body) + self.assertEqual(201, response.status_code) + + def test_create_alert_filter_should_return_400_when_filter_data_is_missing(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + } + response = self._subject.create('/api/v2/alerts-filters', body) + self.assertEqual(400, response.status_code) + + def test_create_alert_filter_should_return_filter_type(self): + filter_type = 'alerts' + body = { + 'filter_is_private': 'true', + 'filter_type': filter_type, + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + response = self._subject.create('/api/v2/alerts-filters', body).json() + self.assertEqual(filter_type, response['filter_type']) + + def test_create_alert_filter_should_return_filter_name(self): + filter_name = 'name' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': filter_name, + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + response = self._subject.create('/api/v2/alerts-filters', body).json() + self.assertEqual(filter_name, response['filter_name']) + + def test_create_alert_filter_should_return_in_filter_data_alert_title(self): + alert_title = 'alert_title' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter_name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': alert_title, + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + response = self._subject.create('/api/v2/alerts-filters', body).json() + self.assertEqual(alert_title, response['filter_data']['alert_title']) + + def test_get_alert_filter_should_return_200(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + response = self._subject.get(f'/api/v2/alerts-filters/{identifier}') + self.assertEqual(200, response.status_code) + + def test_get_alert_filter_should_return_filter_name(self): + filter_name = 'filter name' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': filter_name, + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + response = self._subject.get(f'/api/v2/alerts-filters/{identifier}').json() + self.assertEqual(filter_name, response['filter_name']) + + def test_get_alert_filter_should_return_404_when_alert_filter_not_found(self): + response = self._subject.get(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') + self.assertEqual(404, response.status_code) + + def test_get_alert_filter_should_return_404_when_user_has_not_created_alert_filter(self): + user = self._subject.create_dummy_user() + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter_name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + response = user.get(f'/api/v2/alerts-filters/{identifier}') + self.assertEqual(404, response.status_code) + + def test_update_alert_filter_should_return_200(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_name': 'filter name', + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body) + self.assertEqual(200, response.status_code) + + def test_update_alert_filter_should_return_filter_name(self): + filter_name = 'new name' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_name': filter_name, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() + self.assertEqual(filter_name, response['filter_name']) + + def test_update_alert_filter_should_return_filter_description(self): + filter_description = 'new filter description' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_description': filter_description, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() + self.assertEqual(filter_description, response['filter_description']) + + def test_update_alert_filter_should_return_filter_type(self): + filter_type = 'new filter type' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_type': filter_type, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() + self.assertEqual(filter_type, response['filter_type']) + + def test_update_alert_filter_should_return_filter_data_alert_title(self): + alert_title = 'new alert title' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_data': {'alert_title': alert_title}, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() + self.assertEqual(alert_title, response['filter_data']['alert_title']) + + def test_update_alert_filter_should_return_404_when_alert_filter_is_not_found(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + body = { + 'filter_data': {'alert_title': 'alert_title'}, + } + response = self._subject.update(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}', body) + self.assertEqual(404, response.status_code) + + def test_delete_alert_filter_should_return_204(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + response = self._subject.delete(f'/api/v2/alerts-filters/{identifier}') + self.assertEqual(204, response.status_code) + + def test_delete_alert_filter_should_return_404_when_alert_not_found(self): + response = self._subject.delete(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') + self.assertEqual(404, response.status_code) + + def test_get_alert_filter_should_return_404_after_delete_alert_filter(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + self._subject.delete(f'/api/v2/alerts-filters/{identifier}') + response = self._subject.get(f'/api/v2/alerts-filters/{identifier}') + self.assertEqual(404, response.status_code) + + def test_update_alert_filter_should_return_400(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data': { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_name': 1, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body) + self.assertEqual(400, response.status_code) diff --git a/tests/tests_rest_assets.py b/tests/tests_rest_assets.py index ff736e003..7c5a1c667 100644 --- a/tests/tests_rest_assets.py +++ b/tests/tests_rest_assets.py @@ -270,7 +270,7 @@ def test_get_assets_should_accept_order_by_query_parameter(self): self._subject.create(f'/api/v2/cases/{case_identifier}/assets', body).json() body = {'asset_type_id': 1, 'asset_name': 'asset1'} self._subject.create(f'/api/v2/cases/{case_identifier}/assets', body).json() - response = self._subject.get(f'/api/v2/cases/{case_identifier}/assets', { 'order_by': 'asset_name' }).json() + response = self._subject.get(f'/api/v2/cases/{case_identifier}/assets', {'order_by': 'asset_name'}).json() self.assertEqual('asset1', response['data'][0]['asset_name']) def test_get_assets_should_return_200_when_user_has_read_only_access_to_case(self): diff --git a/tests/tests_rest_cases.py b/tests/tests_rest_cases.py index 3d3781ac7..77095493d 100644 --- a/tests/tests_rest_cases.py +++ b/tests/tests_rest_cases.py @@ -196,33 +196,33 @@ def test_create_case_should_return_field_case_customer(self): def test_update_case_should_return_200(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'case_name': 'new name' }) + response = self._subject.update(f'/api/v2/cases/{identifier}', {'case_name': 'new name'}) self.assertEqual(200, response.status_code) def test_update_case_should_allow_to_update_severity(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'severity_id': 5 }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'severity_id': 5}).json() self.assertEqual(5, response['severity_id']) def test_update_case_should_allow_to_update_classification(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'classification_id': 3 }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'classification_id': 3}).json() self.assertEqual(3, response['classification_id']) def test_update_case_should_allow_to_update_owner(self): user = self._subject.create_dummy_user() identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'owner_id': user.get_identifier() }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'owner_id': user.get_identifier()}).json() self.assertEqual(user.get_identifier(), response['owner']['id']) def test_update_case_should_allow_to_update_state(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'state_id': 2 }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'state_id': 2}).json() self.assertEqual(2, response['state']['state_id']) def test_update_case_should_allow_to_update_status(self): identifier = self._subject.create_dummy_case() - response = self._subject.update(f'/api/v2/cases/{identifier}', { 'status_id': 2 }).json() + response = self._subject.update(f'/api/v2/cases/{identifier}', {'status_id': 2}).json() self.assertEqual(2, response['status_id']) def test_update_case_should_allow_to_update_customer(self): diff --git a/tests/tests_rest_comments.py b/tests/tests_rest_comments.py index dd9cbf4cf..484ebf939 100644 --- a/tests/tests_rest_comments.py +++ b/tests/tests_rest_comments.py @@ -537,7 +537,7 @@ def test_get_iocs_comment_should_return_200(self): response = self._subject.get(f'/api/v2/iocs/{object_identifier}/comments/{identifier}', {}) self.assertEqual(200, response.status_code) - def test_get_notes_comment_should_return_200(self): + def test_get_notes_comments_should_return_200_when_there_is_a_comment(self): case_identifier = self._subject.create_dummy_case() response = self._subject.create(f'/api/v2/cases/{case_identifier}/notes-directories', {'name': 'directory_name'}).json() diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index 502a60ddc..cebf410d4 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -20,6 +20,9 @@ from iris import Iris from iris import IRIS_PERMISSION_CUSTOMERS_WRITE from iris import ADMINISTRATOR_USER_IDENTIFIER +from iris import IRIS_INITIAL_CUSTOMER_IDENTIFIER + +_IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 class TestsRestCustomers(TestCase): @@ -57,6 +60,10 @@ def test_create_customer_should_return_400_when_another_customer_with_the_same_n response = self._subject.create('/api/v2/manage/customers', body) self.assertEqual(400, response.status_code) + def test_create_customer_should_return_400_when_field_customer_name_is_not_provided(self): + response = self._subject.create('/api/v2/manage/customers', {}) + self.assertEqual(400, response.status_code) + def test_create_customer_should_add_an_activity(self): body = {'customer_name': 'customer_name'} self._subject.create('/api/v2/manage/customers', body) @@ -72,3 +79,83 @@ def test_create_customer_should_add_user_to_the_customer(self): for customer in response['user_customers']: user_customers_identifiers.append(customer['customer_id']) self.assertIn(identifier, user_customers_identifiers) + + def test_get_customer_should_return_200(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + response = self._subject.get(f'/api/v2/manage/customers/{identifier}') + self.assertEqual(200, response.status_code) + + def test_get_customer_should_return_404_when_customer_does_not_exist(self): + response = self._subject.get(f'/api/v2/manage/customers/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') + self.assertEqual(404, response.status_code) + + def test_get_customer_should_return_405_when_user_has_no_permission_to_read_customers(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + user = self._subject.create_dummy_user() + response = user.get(f'/api/v2/manage/customers/{identifier}') + self.assertEqual(403, response.status_code) + + def test_put_customer_should_return_200(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + body = {'customer_name': 'new name'} + response = self._subject.update(f'/api/v2/manage/customers/{identifier}', body) + self.assertEqual(200, response.status_code) + + def test_put_customer_should_return_400_when_another_customer_with_the_same_name_already_exists(self): + body = {'customer_name': 'already existing name'} + self._subject.create('/api/v2/manage/customers', body).json() + + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + body = {'customer_name': 'already existing name'} + response = self._subject.update(f'/api/v2/manage/customers/{identifier}', body) + self.assertEqual(400, response.status_code) + + def test_put_customer_should_return_200_when_updating_with_same_name(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + body = {'customer_name': 'customer'} + response = self._subject.update(f'/api/v2/manage/customers/{identifier}', body) + self.assertEqual(200, response.status_code) + + def test_delete_customer_should_return_204(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + # TODO currently, to remove a customer, no user should have any access to it. I am not sure this is the optimum behavior. + body = {'customers_membership': [IRIS_INITIAL_CUSTOMER_IDENTIFIER]} + self._subject.create(f'/manage/users/{ADMINISTRATOR_USER_IDENTIFIER}/customers/update', body) + + response = self._subject.delete(f'/api/v2/manage/customers/{identifier}') + self.assertEqual(204, response.status_code) + + def test_delete_customer_should_return_400_when_referenced_in_a_case(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + self._subject.create_dummy_case(identifier) + + # TODO currently, to remove a customer, no user should have any access to it. I am not sure this is the optimum behavior. + body = {'customers_membership': [IRIS_INITIAL_CUSTOMER_IDENTIFIER]} + self._subject.create(f'/manage/users/{ADMINISTRATOR_USER_IDENTIFIER}/customers/update', body) + + response = self._subject.delete(f'/api/v2/manage/customers/{identifier}') + self.assertEqual(400, response.status_code) + + def test_search_customers_should_return_200(self): + response = self._subject.get('/api/v2/manage/customers') + self.assertEqual(200, response.status_code) diff --git a/tests/tests_rest_global_tasks.py b/tests/tests_rest_global_tasks.py new file mode 100644 index 000000000..a6bb8474e --- /dev/null +++ b/tests/tests_rest_global_tasks.py @@ -0,0 +1,64 @@ +# IRIS Source Code +# Copyright (C) 2023 - DFIR-IRIS +# contact@dfir-iris.org +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from unittest import TestCase +from iris import Iris +from iris import ADMINISTRATOR_USER_IDENTIFIER + + +class TestsRestGlobalTasks(TestCase): + + def setUp(self) -> None: + self._subject = Iris() + + def tearDown(self): + self._subject.clear_database() + + def test_create_global_task_should_return_201(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body) + self.assertEqual(201, response.status_code) + + def test_get_global_task_should_return_200(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + response = self._subject.get(f'/api/v2/global-tasks/{identifier}') + self.assertEqual(200, response.status_code) + + def test_get_global_task_should_return_field_task_id(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + response = self._subject.get(f'/api/v2/global-tasks/{identifier}').json() + self.assertEqual(identifier, response['task_id']) + + def test_delete_global_task_should_return_204(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + response = self._subject.delete(f'/api/v2/global-tasks/{identifier}') + self.assertEqual(204, response.status_code) + + def test_get_global_task_should_return_404_when_deleted(self): + body = {'task_title': 'dummy title', 'task_status_id': 1, 'task_assignee_id': ADMINISTRATOR_USER_IDENTIFIER} + response = self._subject.create('/api/v2/global-tasks', body).json() + identifier = response['task_id'] + self._subject.delete(f'/api/v2/global-tasks/{identifier}') + response = self._subject.get(f'/api/v2/global-tasks/{identifier}') + self.assertEqual(404, response.status_code) diff --git a/tests/tests_rest_groups.py b/tests/tests_rest_groups.py index 048970893..0ed96e3fe 100644 --- a/tests/tests_rest_groups.py +++ b/tests/tests_rest_groups.py @@ -21,6 +21,7 @@ from iris import Iris from iris import IRIS_PERMISSION_SERVER_ADMINISTRATOR from iris import ADMINISTRATOR_USER_IDENTIFIER +from iris import GROUP_ANALYSTS_IDENTIFIER _IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 @@ -77,6 +78,10 @@ def test_get_group_should_return_200(self): response = self._subject.get(f'/api/v2/manage/groups/{identifier}') self.assertEqual(200, response.status_code) + def test_get_group_analysts_should_return_field_autofollow_to_false(self): + group_analysts = self._subject.get(f'/api/v2/manage/groups/{GROUP_ANALYSTS_IDENTIFIER}').json() + self.assertFalse(group_analysts['group_auto_follow']) + def test_get_group_should_return_group_name(self): body = {'group_name': 'name', 'group_description': 'description'} response = self._subject.create('/api/v2/manage/groups', body).json() @@ -118,7 +123,7 @@ def test_update_group_should_return_field_group_auto_follow(self): body = {'group_name': 'name', 'group_description': 'description'} response = self._subject.create('/api/v2/manage/groups', body).json() identifier = response['group_id'] - body = {'group_name': 'new_name', 'group_description': 'new_description', 'group_permissions': 1, 'group_auto_follow' : True} + body = {'group_name': 'new_name', 'group_description': 'new_description', 'group_permissions': 1, 'group_auto_follow': True} response = self._subject.update(f'/api/v2/manage/groups/{identifier}', body).json() self.assertEqual(True, response['group_auto_follow']) diff --git a/tests/tests_rest_module_tasks.py b/tests/tests_rest_module_tasks.py new file mode 100644 index 000000000..9ca5c9463 --- /dev/null +++ b/tests/tests_rest_module_tasks.py @@ -0,0 +1,41 @@ +# IRIS Source Code +# Copyright (C) 2025 - 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 unittest import TestCase +from iris import Iris + + +class TestsRestModuleTasks(TestCase): + + def setUp(self) -> None: + self._subject = Iris() + + def tearDown(self): + self._subject.clear_database() + + def test_get_module_tasks_should_return_case_identifier(self): + case_identifier = self._subject.create_dummy_case() + + module_identifier = self._subject.get_module_identifier_by_name('IrisCheck') + self._subject.create(f'/manage/modules/enable/{module_identifier}', {}) + body = {'asset_type_id': 1, 'asset_name': 'admin_laptop_test'} + self._subject.create(f'/api/v2/cases/{case_identifier}/assets', body) + self._subject.create(f'/manage/modules/disable/{module_identifier}', {}) + + response = self._subject.get('/dim/tasks/list/1').json() + self.assertEqual(f'Case #{case_identifier}', response['data'][0]['case']) diff --git a/tests/tests_rest_notes.py b/tests/tests_rest_notes.py index adc011949..8b4f7c881 100644 --- a/tests/tests_rest_notes.py +++ b/tests/tests_rest_notes.py @@ -263,4 +263,3 @@ def test_socket_io_join_notes_overview_should_not_fail(self): socket_io_client.emit('join-notes-overview', f'case-{case_identifier}-notes') message = socket_io_client.receive() self.assertEqual('administrator', message['user']) - diff --git a/tests/tests_rest_tasks.py b/tests/tests_rest_tasks.py index 3417ec82f..61e301b16 100644 --- a/tests/tests_rest_tasks.py +++ b/tests/tests_rest_tasks.py @@ -177,7 +177,7 @@ def test_get_tasks_should_honour_per_page_pagination_parameter(self): self._subject.create(f'/api/v2/cases/{case_identifier}/tasks', body).json() body = {'task_assignees_id': [], 'task_status_id': 1, 'task_title': 'task3'} self._subject.create(f'/api/v2/cases/{case_identifier}/tasks', body).json() - response = self._subject.get(f'/api/v2/cases/{case_identifier}/tasks', { 'per_page': 2 }).json() + response = self._subject.get(f'/api/v2/cases/{case_identifier}/tasks', {'per_page': 2}).json() self.assertEqual(2, len(response['data'])) def test_get_tasks_should_return_current_page(self): @@ -188,7 +188,7 @@ def test_get_tasks_should_return_current_page(self): self._subject.create(f'/api/v2/cases/{case_identifier}/tasks', body).json() body = {'task_assignees_id': [], 'task_status_id': 1, 'task_title': 'task3'} self._subject.create(f'/api/v2/cases/{case_identifier}/tasks', body).json() - response = self._subject.get(f'/api/v2/cases/{case_identifier}/tasks', { 'page': 2, 'per_page': 2 }).json() + response = self._subject.get(f'/api/v2/cases/{case_identifier}/tasks', {'page': 2, 'per_page': 2}).json() self.assertEqual(2, response['current_page']) def test_get_tasks_should_return_correct_task_uuid(self): diff --git a/tests_database_migration/test_harness/iris.py b/tests_database_migration/test_harness/iris.py index c87833603..41e688e1e 100644 --- a/tests_database_migration/test_harness/iris.py +++ b/tests_database_migration/test_harness/iris.py @@ -98,4 +98,3 @@ def clear_database(self): identifier = user['user_id'] self.get(f'/manage/users/deactivate/{identifier}') self.create(f'/manage/users/delete/{identifier}', {}) - diff --git a/upgrades/upgrade_to_2.0.0.py b/upgrades/upgrade_to_2.0.0.py index b49f86d47..3c272bd32 100644 --- a/upgrades/upgrade_to_2.0.0.py +++ b/upgrades/upgrade_to_2.0.0.py @@ -168,4 +168,3 @@ def check(silent=False): if args.check: iu.check() -