diff --git a/.github/workflows/backend-api-tests.yml b/.github/workflows/backend-api-tests.yml index 1153bfcaf..346356c2e 100644 --- a/.github/workflows/backend-api-tests.yml +++ b/.github/workflows/backend-api-tests.yml @@ -57,4 +57,10 @@ jobs: working-directory: ${{env.working-directory}} run: | export $(grep -v '^#' .env | xargs) - pytest app_tests/api \ No newline at end of file + pytest app_tests/api --html=pytest-report.html --self-contained-html + - uses: actions/upload-artifact@v4 + if: always() + with: + name: api-tests-report + path: ${{ env.working-directory }}/pytest-report.html + retention-days: 5 \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore index 66db79d2a..2ba5b3ed8 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -17,3 +17,4 @@ db/django_secret_key db/pg_password.txt ./db/ .coverage +pytest-report.html \ No newline at end of file diff --git a/backend/app_tests/api/test_api_assets.py b/backend/app_tests/api/test_api_assets.py index 48e12bf1c..9c2857cf7 100644 --- a/backend/app_tests/api/test_api_assets.py +++ b/backend/app_tests/api/test_api_assets.py @@ -3,7 +3,8 @@ from core.models import Asset from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic asset data for tests ASSET_NAME = "Test Asset" @@ -78,14 +79,15 @@ def test_delete_assets(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestAssetsAuthenticated: """Perform tests on Assets API endpoint with authentication""" - def test_get_assets(self, authenticated_client): + def test_get_assets(self, test): """test to get assets from the API with authentication""" EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Assets", Asset, { @@ -93,16 +95,17 @@ def test_get_assets(self, authenticated_client): "description": ASSET_DESCRIPTION, "business_value": ASSET_BUSINESS_VALUE, "type": ASSET_TYPE[0], - "folder": Folder.get_root_folder(), + "folder": test.folder, }, - {"folder": {"str": Folder.get_root_folder().name}, "type": ASSET_TYPE[1]}, + {"folder": {"id": str(test.folder.id), "str": test.folder.name}, "type": ASSET_TYPE[1]}, + user_group=test.user_group, ) - def test_create_assets(self, authenticated_client): - """test to create assets with the API with authentication""" + def test_create_assets(self, test): + """test to create assets without a parent asset the API with authentication""" EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Assets", Asset, { @@ -111,23 +114,24 @@ def test_create_assets(self, authenticated_client): "business_value": ASSET_BUSINESS_VALUE, "type": ASSET_TYPE[0], "parent_assets": [], - "folder": str(Folder.get_root_folder().id), + "folder": str(test.folder.id), }, - {"folder": {"str": Folder.get_root_folder().name}, "type": ASSET_TYPE[1]}, + {"folder": {"id": str(test.folder.id), "str": test.folder.name}, "type": ASSET_TYPE[1]}, + user_group=test.user_group, ) - def test_create_assets2(self, authenticated_client): - """test to create assets with the API with authentication""" + def test_create_assets_with_parent(self, test): + """test to create assets with a parent asset with the API with authentication""" root_asset = Asset.objects.create( name="root", description=ASSET_DESCRIPTION, type=ASSET_TYPE[0], - folder=Folder.get_root_folder(), + folder=test.folder, ) EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Assets", Asset, { @@ -136,23 +140,24 @@ def test_create_assets2(self, authenticated_client): "business_value": ASSET_BUSINESS_VALUE, "type": ASSET_TYPE2[0], "parent_assets": [str(root_asset.id)], - "folder": str(Folder.get_root_folder().id), + "folder": str(test.folder.id), }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "type": ASSET_TYPE2[1], "parent_assets": [{"id": str(root_asset.id), "str": root_asset.name}], }, base_count=1, + user_group=test.user_group, ) - def test_update_assets(self, authenticated_client): + def test_update_assets(self, test): """test to update assets with the API with authentication""" - folder = Folder.objects.create(name="test") + folder = Folder.objects.create(name="test2") EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Assets", Asset, { @@ -160,7 +165,7 @@ def test_update_assets(self, authenticated_client): "description": ASSET_DESCRIPTION, "business_value": ASSET_BUSINESS_VALUE, "type": ASSET_TYPE[0], - "folder": Folder.get_root_folder(), + "folder": test.folder, }, { "name": "new " + ASSET_NAME, @@ -169,22 +174,24 @@ def test_update_assets(self, authenticated_client): "type": ASSET_TYPE2[0], "folder": str(folder.id), }, - {"folder": {"str": Folder.get_root_folder().name}, "type": ASSET_TYPE[1]}, + {"folder": {"id": str(test.folder.id), "str": test.folder.name}, "type": ASSET_TYPE[1]}, + user_group=test.user_group, ) - def test_delete_assets(self, authenticated_client): + def test_delete_assets(self, test): """test to delete assets with the API with authentication""" EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Assets", Asset, - {"name": ASSET_NAME, "folder": Folder.get_root_folder()}, + {"name": ASSET_NAME, "folder": test.folder}, + user_group=test.user_group, ) - def test_get_type_choices(self, authenticated_client): + def test_get_type_choices(self, test): """test to get type choices from the API with authentication""" EndpointTestsQueries.Auth.get_object_options( - authenticated_client, "Assets", "type", Asset.Type.choices + test.client, "Assets", "type", Asset.Type.choices ) diff --git a/backend/app_tests/api/test_api_assessments.py b/backend/app_tests/api/test_api_compliance_assessments.py similarity index 84% rename from backend/app_tests/api/test_api_assessments.py rename to backend/app_tests/api/test_api_compliance_assessments.py index 6fa9ed4d8..6cb901b14 100644 --- a/backend/app_tests/api/test_api_assessments.py +++ b/backend/app_tests/api/test_api_compliance_assessments.py @@ -4,7 +4,8 @@ from core.models import Project from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic compliance assessment data for tests COMPLIANCE_ASSESSMENT_NAME = "Test Compliance Assessment" @@ -96,19 +97,20 @@ def test_delete_compliance_assessments(self, authenticated_client): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestComplianceAssessmentsAuthenticated: """Perform tests on ComplianceAssessments API endpoint with authentication""" - def test_get_compliance_assessments(self, authenticated_client): + def test_get_compliance_assessments(self, test): """test to get compliance assessments from the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") project = Project.objects.create( - name="test", folder=Folder.objects.create(name="test") + name="test", folder=test.folder ) EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Compliance Assessments", ComplianceAssessment, { @@ -125,18 +127,19 @@ def test_get_compliance_assessments(self, authenticated_client): "str": str(Framework.objects.all()[0]), }, }, + user_group=test.user_group, ) - def test_create_compliance_assessments(self, authenticated_client): + def test_create_compliance_assessments(self, test): """test to create compliance assessments with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") project = Project.objects.create( - name="test", folder=Folder.objects.create(name="test") + name="test", folder=test.folder ) EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Compliance Assessments", ComplianceAssessment, { @@ -153,23 +156,24 @@ def test_create_compliance_assessments(self, authenticated_client): "str": str(Framework.objects.all()[0]), }, }, + user_group=test.user_group, ) - def test_update_compliance_assessments(self, authenticated_client): + def test_update_compliance_assessments(self, test): """test to update compliance assessments with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Documents") - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework2") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Documents") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework2") project = Project.objects.create( - name="test", folder=Folder.objects.create(name="test") + name="test", folder=test.folder ) project2 = Project.objects.create( name="test2", folder=Folder.objects.create(name="test2") ) EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Compliance Assessments", ComplianceAssessment, { @@ -193,18 +197,19 @@ def test_update_compliance_assessments(self, authenticated_client): "str": str(Framework.objects.all()[0]), }, }, + user_group=test.user_group, ) - def test_delete_compliance_assessments(self, authenticated_client): + def test_delete_compliance_assessments(self, test): """test to delete compliance assessments with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") project = Project.objects.create( - name="test", folder=Folder.objects.create(name="test") + name="test", folder=test.folder ) EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Compliance Assessments", ComplianceAssessment, { @@ -212,4 +217,5 @@ def test_delete_compliance_assessments(self, authenticated_client): "project": project, "framework": Framework.objects.all()[0], }, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_evidences.py b/backend/app_tests/api/test_api_evidences.py index 0fd73c3d6..fa0cc0430 100644 --- a/backend/app_tests/api/test_api_evidences.py +++ b/backend/app_tests/api/test_api_evidences.py @@ -5,7 +5,8 @@ from core.models import Evidence from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic evidence data for tests EVIDENCE_NAME = "Test Evidence" @@ -91,28 +92,28 @@ def test_delete_evidences(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestEvidencesAuthenticated: """Perform tests on Evidences API endpoint with authentication""" - def test_get_evidences(self, authenticated_client): + def test_get_evidences(self, test): """test to get evidences from the API with authentication""" - folder = Folder.objects.create(name="test") - security_measure = SecurityMeasure.objects.create(name="test", folder=folder) + security_measure = SecurityMeasure.objects.create(name="test", folder=test.folder) EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Evidences", Evidence, { "name": EVIDENCE_NAME, "description": EVIDENCE_DESCRIPTION, "link": EVIDENCE_LINK, - "folder": folder, + "folder": test.folder, "security_measures": [security_measure], }, { - "folder": {"id": str(folder.id), "str": folder.name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "security_measures": [ { "id": str(security_measure.id), @@ -120,101 +121,101 @@ def test_get_evidences(self, authenticated_client): } ], }, + user_group=test.user_group, ) - def test_create_evidences(self, authenticated_client): + def test_create_evidences(self, test): """test to create evidences with the API with authentication""" - folder = Folder.objects.create(name="test") - security_measure = SecurityMeasure.objects.create(name="test", folder=folder) + security_measure = SecurityMeasure.objects.create(name="test", folder=test.folder) with open( path.join(path.dirname(path.dirname(__file__)), EVIDENCE_ATTACHMENT), "rb" ) as file: EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Evidences", Evidence, { "name": EVIDENCE_NAME, "description": EVIDENCE_DESCRIPTION, "link": EVIDENCE_LINK, - "folder": str(folder.id), - # "security_measures": [str(security_measure.id)], + "folder": str(test.folder.id), + "security_measures": [str(security_measure.id)], "attachment": file, }, { - "folder": {"id": str(folder.id), "str": folder.name}, - # "security_measures": [ - # { - # "id": str(security_measure.id), - # "str": security_measure.name, - # } - # ], + "folder": {"id": str(test.folder.id), "str": test.folder.name}, + "security_measures": [ + { + "id": str(security_measure.id), + "str": security_measure.name, + } + ], "attachment": EVIDENCE_ATTACHMENT, }, query_format="multipart", + user_group=test.user_group, ) - def test_update_evidences(self, authenticated_client): + def test_update_evidences(self, test): """test to update evidences with the API with authentication""" - folder = Folder.objects.create(name="test") - folder2 = Folder.objects.create(name="test2") - # security_measure = SecurityMeasure.objects.create(name="test", folder=folder) - # security_measure2 = SecurityMeasure.objects.create(name="test2", folder=folder2) + folder = Folder.objects.create(name="test2") + security_measure = SecurityMeasure.objects.create(name="test", folder=test.folder) + security_measure2 = SecurityMeasure.objects.create(name="test2", folder=folder) with open( path.join(path.dirname(path.dirname(__file__)), EVIDENCE_ATTACHMENT), "rb" ) as file: EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Evidences", Evidence, { "name": EVIDENCE_NAME, "description": EVIDENCE_DESCRIPTION, "link": EVIDENCE_LINK, - "folder": folder, - # "security_measures": [security_measure], + "folder": test.folder, + "security_measures": [security_measure], }, { "name": "new " + EVIDENCE_NAME, "description": "new " + EVIDENCE_DESCRIPTION, "link": EVIDENCE_LINK + "/new", - "folder": str(folder2.id), - # "security_measures": [str(security_measure2.id)], + "folder": str(folder.id), + "security_measures": [str(security_measure2.id)], "attachment": file, }, { - "folder": {"id": str(folder.id), "str": folder.name}, - # "security_measures": [ - # { - # "id": str(security_measure.id), - # "str": security_measure.name, - # } - # ], + "folder": {"id": str(test.folder.id), "str": test.folder.name}, + "security_measures": [ + { + "id": str(security_measure.id), + "str": security_measure.name, + } + ], }, { "attachment": EVIDENCE_ATTACHMENT, }, query_format="multipart", + user_group=test.user_group, ) - def test_delete_evidences(self, authenticated_client): + def test_delete_evidences(self, test): """test to delete evidences with the API with authentication""" - folder = Folder.objects.create(name="test") - EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Evidences", Evidence, { "name": EVIDENCE_NAME, - "folder": folder, + "folder": test.folder, "security_measures": [ - SecurityMeasure.objects.create(name="test", folder=folder) + SecurityMeasure.objects.create(name="test", folder=test.folder) ], }, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_folders.py b/backend/app_tests/api/test_api_folders.py index 8147defdc..47012a3f3 100644 --- a/backend/app_tests/api/test_api_folders.py +++ b/backend/app_tests/api/test_api_folders.py @@ -2,7 +2,8 @@ from rest_framework.test import APIClient from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic folder data for tests FOLDER_NAME = "Test Folder" @@ -61,67 +62,83 @@ def test_delete_folders(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestFoldersAuthenticated: """Perform tests on Folders API endpoint with authentication""" - def test_get_folders(self, authenticated_client): + def test_get_folders(self, test): """test to get folders from the API with authentication""" EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Folders", Folder, - {"name": FOLDER_NAME, "description": FOLDER_DESCRIPTION}, { - "parent_folder": {"str": Folder.get_root_folder().name}, + "name": FOLDER_NAME, + "description": FOLDER_DESCRIPTION, + "parent_folder": test.folder, + }, + { + "parent_folder": {"id": str(test.folder.id), "str": test.folder.name}, "content_type": FOLDER_CONTENT_TYPE, }, + base_count=-1, + user_group=test.user_group, ) - def test_create_folders(self, authenticated_client): + def test_create_folders(self, test): """test to create folders with the API with authentication""" EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Folders", Folder, { "name": FOLDER_NAME, "description": FOLDER_DESCRIPTION, - "parent_folder": str(Folder.get_root_folder().id), + "parent_folder": str(test.folder.id), }, { - "parent_folder": {"str": Folder.get_root_folder().name}, + "parent_folder": {"id": str(test.folder.id), "str": test.folder.name}, "content_type": FOLDER_CONTENT_TYPE, }, + base_count=-1, + user_group=test.user_group, ) - def test_update_folders(self, authenticated_client): + def test_update_folders(self, test): """test to update folders with the API with authentication""" EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Folders", Folder, { "name": FOLDER_NAME, "description": FOLDER_DESCRIPTION, + "parent_folder": test.folder, }, { "name": "new " + FOLDER_NAME, "description": "new " + FOLDER_DESCRIPTION, - "parent_folder": str(Folder.objects.create(name="test").id), + "parent_folder": str(Folder.objects.create(name="test2").id), + }, + { + "parent_folder": {"id": str(test.folder.id), "str": test.folder.name}, }, + user_group=test.user_group, ) - def test_delete_folders(self, authenticated_client): + def test_delete_folders(self, test): """test to delete folders with the API with authentication""" EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Folders", Folder, { "name": FOLDER_NAME, + "parent_folder": test.folder, }, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_libraries.py b/backend/app_tests/api/test_api_libraries.py index b5df402ed..f420ace81 100644 --- a/backend/app_tests/api/test_api_libraries.py +++ b/backend/app_tests/api/test_api_libraries.py @@ -4,7 +4,8 @@ from core.models import RiskMatrix from iam.models import Folder -from test_api import EndpointTestsQueries, EndpointTestsUtils +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries, EndpointTestsUtils @pytest.mark.django_db @@ -42,21 +43,22 @@ def test_delete_risk_matrix(self, authenticated_client): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestLibrariesAuthenticated: """Perform tests on Libraries API endpoint with authentication""" - def test_get_libraries(self, authenticated_client): + def test_get_libraries(self, test): """test to get libraries from the API with authentication""" EndpointTestsQueries.Auth.get_object( - authenticated_client, "Libraries", base_count=-1 + test.client, "Libraries", base_count=-1, user_group=test.user_group ) - def test_import_frameworks(self, authenticated_client): + def test_import_frameworks(self, test): """test to import frameworks with the API with authentication""" - # Uses the API endpoint to get library details with the authenticated client - lib_detail_response = authenticated_client.get( + # Uses the API endpoint to get library details with the admin client + lib_detail_response = test.admin_client.get( EndpointTestsUtils.get_object_urn("Framework") ).json()["objects"]["framework"] @@ -64,16 +66,16 @@ def test_import_frameworks(self, authenticated_client): assert ( Framework.objects.all().count() == 0 ), "libraries are already imported in the database" - EndpointTestsQueries.Auth.get_object(authenticated_client, "Frameworks") + EndpointTestsQueries.Auth.get_object(test.client, "Frameworks", user_group=test.user_group) - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") + EndpointTestsQueries.Auth.import_object(test.client, "Framework", user_group=test.user_group) # Uses the API endpoint to assert that the library was properly imported assert ( Framework.objects.all().count() == 1 ), "frameworks are not correctly imported in the database" EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Frameworks", test_params={ "name": lib_detail_response["name"], @@ -82,21 +84,22 @@ def test_import_frameworks(self, authenticated_client): "folder": {"str": Folder.get_root_folder().name}, }, base_count=1, + user_group=test.user_group, ) - def test_delete_frameworks(self, authenticated_client): + def test_delete_frameworks(self, test): """test to delete frameworks with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") + EndpointTestsQueries.Auth.import_object(test.client, "Framework", user_group=test.user_group) EndpointTestsQueries.Auth.delete_object( - authenticated_client, "Frameworks", Framework + test.client, "Frameworks", Framework, user_group=test.user_group ) - def test_import_risk_matrix(self, authenticated_client): + def test_import_risk_matrix(self, test): """test to import risk matrix with the API with authentication""" - # Uses the API endpoint to get library details with the authenticated client - lib_detail_response = authenticated_client.get( + # Uses the API endpoint to get library details with the admin client + lib_detail_response = test.admin_client.get( EndpointTestsUtils.get_object_urn("Risk matrix") ).json()["objects"]["risk_matrix"][0] @@ -104,15 +107,16 @@ def test_import_risk_matrix(self, authenticated_client): assert ( RiskMatrix.objects.all().count() == 0 ), "libraries are already imported in the database" - EndpointTestsQueries.Auth.get_object(authenticated_client, "Risk matrices") - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") + EndpointTestsQueries.Auth.get_object(test.client, "Risk matrices", user_group=test.user_group) + + EndpointTestsQueries.Auth.import_object(test.client, "Risk matrix", user_group=test.user_group) # Uses the API endpoint to assert that the library was properly imported assert ( RiskMatrix.objects.all().count() == 1 ), "Risk matrices are not correctly imported in the database" EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Risk matrices", test_params={ "name": lib_detail_response["name"], @@ -122,12 +126,13 @@ def test_import_risk_matrix(self, authenticated_client): # 'json_definition': lib_detail_response # TODO: restore this test }, base_count=1, + user_group=test.user_group, ) - def test_delete_matrix(self, authenticated_client): + def test_delete_matrix(self, test): """test to delete risk matrix with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") + EndpointTestsQueries.Auth.import_object(test.client, "Risk matrix", user_group=test.user_group) EndpointTestsQueries.Auth.delete_object( - authenticated_client, "Risk matrices", RiskMatrix + test.client, "Risk matrices", RiskMatrix, user_group=test.user_group ) diff --git a/backend/app_tests/api/test_api_policies.py b/backend/app_tests/api/test_api_policies.py index 94d3fa026..0074093e6 100644 --- a/backend/app_tests/api/test_api_policies.py +++ b/backend/app_tests/api/test_api_policies.py @@ -1,9 +1,10 @@ import pytest from rest_framework.test import APIClient -from core.models import SecurityFunction, Policy +from core.models import Policy from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic policy data for tests POLICY_NAME = "Test Policy" @@ -22,12 +23,12 @@ class TestPolicysUnauthenticated: client = APIClient() - def test_get_security_measures(self): + def test_get_policies(self): """test to get policies from the API without authentication""" EndpointTestsQueries.get_object( self.client, - "policies", + "Policies", Policy, { "name": POLICY_NAME, @@ -36,12 +37,12 @@ def test_get_security_measures(self): }, ) - def test_create_security_measures(self): + def test_create_policies(self): """test to create policies with the API without authentication""" EndpointTestsQueries.create_object( self.client, - "policies", + "Policies", Policy, { "name": POLICY_NAME, @@ -50,12 +51,12 @@ def test_create_security_measures(self): }, ) - def test_update_security_measures(self): + def test_update_policies(self): """test to update policies with the API without authentication""" EndpointTestsQueries.update_object( self.client, - "policies", + "Policies", Policy, { "name": POLICY_NAME, @@ -69,12 +70,12 @@ def test_update_security_measures(self): }, ) - def test_delete_security_measures(self): + def test_delete_policies(self): """test to delete policies with the API without authentication""" EndpointTestsQueries.delete_object( self.client, - "policies", + "Policies", Policy, { "name": POLICY_NAME, @@ -84,15 +85,16 @@ def test_delete_security_measures(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestPolicysAuthenticated: """Perform tests on policies API endpoint with authentication""" - def test_get_security_measures(self, authenticated_client): + def test_get_policies(self, test): """test to get policies from the API with authentication""" EndpointTestsQueries.Auth.get_object( - authenticated_client, - "policies", + test.client, + "Policies", Policy, { "name": POLICY_NAME, @@ -101,26 +103,23 @@ def test_get_security_measures(self, authenticated_client): "link": POLICY_LINK, "eta": POLICY_ETA, "effort": POLICY_EFFORT[0], - "folder": Folder.get_root_folder(), + "folder": test.folder, }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "security_function": None, "status": POLICY_STATUS[1], "effort": POLICY_EFFORT[1], }, + user_group=test.user_group, ) - def test_create_security_measures(self, authenticated_client): + def test_create_policies(self, test): """test to create policies with the API with authentication""" - security_function = SecurityFunction.objects.create( - name="test", typical_evidence={}, folder=Folder.objects.create(name="test") - ) - EndpointTestsQueries.Auth.create_object( - authenticated_client, - "policies", + test.client, + "Policies", Policy, { "name": POLICY_NAME, @@ -129,26 +128,24 @@ def test_create_security_measures(self, authenticated_client): "link": POLICY_LINK, "eta": POLICY_ETA, "effort": POLICY_EFFORT[0], - "folder": str(Folder.get_root_folder().id), + "folder": str(test.folder.id), }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "status": POLICY_STATUS[1], "effort": POLICY_EFFORT[1], }, + user_group=test.user_group, ) - def test_update_security_measures(self, authenticated_client): + def test_update_policies(self, test): """test to update policies with the API with authentication""" - folder = Folder.objects.create(name="test") - security_function = SecurityFunction.objects.create( - name="test", typical_evidence={}, folder=folder - ) + folder = Folder.objects.create(name="test2") EndpointTestsQueries.Auth.update_object( - authenticated_client, - "policies", + test.client, + "Policies", Policy, { "name": POLICY_NAME, @@ -157,7 +154,7 @@ def test_update_security_measures(self, authenticated_client): "link": POLICY_LINK, "eta": POLICY_ETA, "effort": POLICY_EFFORT[0], - "folder": Folder.get_root_folder(), + "folder": test.folder, }, { "name": "new " + POLICY_NAME, @@ -169,38 +166,56 @@ def test_update_security_measures(self, authenticated_client): "folder": str(folder.id), }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "status": POLICY_STATUS[1], "effort": POLICY_EFFORT[1], }, + user_group=test.user_group, ) - def test_delete_security_measures(self, authenticated_client): + def test_delete_policies(self, test): """test to delete policies with the API with authentication""" EndpointTestsQueries.Auth.delete_object( - authenticated_client, - "policies", + test.client, + "Policies", Policy, { "name": POLICY_NAME, - "folder": Folder.objects.create(name="test"), + "folder": test.folder, }, + user_group=test.user_group, + ) + + def test_get_category_choices(self, test): + """test to get policies category choices from the API with authentication""" + + EndpointTestsQueries.Auth.get_object_options( + test.client, + "Policies", + "category", + Policy.CATEGORY, + user_group=test.user_group ) - def test_get_effort_choices(self, authenticated_client): + def test_get_effort_choices(self, test): """test to get policies effort choices from the API with authentication""" EndpointTestsQueries.Auth.get_object_options( - authenticated_client, "policies", "effort", Policy.EFFORT + test.client, + "Policies", + "effort", + Policy.EFFORT, + user_group=test.user_group ) - def test_get_status_choices(self, authenticated_client): + def test_get_status_choices(self, test): """test to get policies status choices from the API with authentication""" EndpointTestsQueries.Auth.get_object_options( - authenticated_client, - "policies", + test.client, + "Policies", "status", Policy.Status.choices, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_projects.py b/backend/app_tests/api/test_api_projects.py index 85a4bacad..507dd414e 100644 --- a/backend/app_tests/api/test_api_projects.py +++ b/backend/app_tests/api/test_api_projects.py @@ -3,7 +3,8 @@ from core.models import Project from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic project data for tests PROJECT_NAME = "Test Project" @@ -76,63 +77,67 @@ def test_delete_projects(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestProjectsAuthenticated: """Perform tests on Projects API endpoint with authentication""" - def test_get_projects(self, authenticated_client): + def test_get_projects(self, test): """test to get projects from the API with authentication""" EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Projects", Project, { "name": PROJECT_NAME, "description": PROJECT_DESCRIPTION, - "folder": Folder.get_root_folder(), + "folder": test.folder, "internal_reference": PROJECT_REFERENCE, "lc_status": PROJECT_STATUS[0], }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "lc_status": PROJECT_STATUS[1], }, + user_group=test.user_group, ) + - def test_create_projects(self, authenticated_client): + def test_create_projects(self, test): """test to create projects with the API with authentication""" EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Projects", Project, { "name": PROJECT_NAME, "description": PROJECT_DESCRIPTION, - "folder": str(Folder.get_root_folder().id), + "folder": str(test.folder.id), "internal_reference": PROJECT_REFERENCE, "lc_status": PROJECT_STATUS[0], }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "lc_status": PROJECT_STATUS[1], }, + user_group=test.user_group, ) - def test_update_projects(self, authenticated_client): + def test_update_projects(self, test): """test to update projects with the API with authentication""" status = ("in_dev", "Development") - folder = Folder.objects.create(name="test") + folder = Folder.objects.create(name="test2") EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Projects", Project, { "name": PROJECT_NAME, "description": PROJECT_DESCRIPTION, - "folder": Folder.get_root_folder(), + "folder": test.folder, "internal_reference": PROJECT_REFERENCE, "lc_status": PROJECT_STATUS[0], }, @@ -144,24 +149,26 @@ def test_update_projects(self, authenticated_client): "lc_status": status[0], }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "lc_status": PROJECT_STATUS[1], }, + user_group=test.user_group, ) - def test_delete_projects(self, authenticated_client): + def test_delete_projects(self, test): """test to delete projects with the API with authentication""" EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Projects", Project, - {"name": PROJECT_NAME, "folder": Folder.get_root_folder()}, + {"name": PROJECT_NAME, "folder": test.folder}, + user_group=test.user_group, ) - def test_get_status_choices(self, authenticated_client): + def test_get_status_choices(self, test): """test to get projects status choices from the API with authentication""" EndpointTestsQueries.Auth.get_object_options( - authenticated_client, "Projects", "lc_status", Project.PRJ_LC_STATUS + test.client, "Projects", "lc_status", Project.PRJ_LC_STATUS ) diff --git a/backend/app_tests/api/test_api_requirement_assessments.py b/backend/app_tests/api/test_api_requirement_assessments.py index 6f29fa154..49c5ce1e8 100644 --- a/backend/app_tests/api/test_api_requirement_assessments.py +++ b/backend/app_tests/api/test_api_requirement_assessments.py @@ -1,5 +1,5 @@ import pytest -from rest_framework.status import HTTP_400_BAD_REQUEST +from rest_framework.status import HTTP_403_FORBIDDEN from rest_framework.test import APIClient from core.models import ( ComplianceAssessment, @@ -10,7 +10,8 @@ from core.models import Project, SecurityMeasure from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic requirement assessment data for tests REQUIREMENT_ASSESSMENT_STATUS = "partially_compliant" @@ -91,63 +92,63 @@ def test_update_requirement_assessments(self, authenticated_client): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestRequirementAssessmentsAuthenticated: """Perform tests on Requirement Assessments API endpoint with authentication""" - def test_get_requirement_assessments(self, authenticated_client): + def test_get_requirement_assessments(self, test): """test to get requirement assessments from the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") - folder = Folder.objects.create(name="test") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") compliance_assessment = ComplianceAssessment.objects.create( name="test", - project=Project.objects.create(name="test", folder=folder), + project=Project.objects.create(name="test", folder=test.folder), framework=Framework.objects.all()[0], ) EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Requirement Assessments", RequirementAssessment, { "status": REQUIREMENT_ASSESSMENT_STATUS, "observation": REQUIREMENT_ASSESSMENT_OBSERVATION, - "folder": folder, + "folder": test.folder, "compliance_assessment": compliance_assessment, "requirement": RequirementNode.objects.all()[0], }, { - "folder": str(folder.id), + "folder": str(test.folder.id), "compliance_assessment": { "id": str(compliance_assessment.id), "str": compliance_assessment.name, }, "requirement": str(RequirementNode.objects.all()[0].id), }, - -1, + base_count=-1, + user_group=test.user_group, ) - def test_create_requirement_assessments(self, authenticated_client): + def test_create_requirement_assessments(self, test): """test to create requirement assessments with the API with authentication""" """nobody has permission to do that, so it will fail""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") - folder = Folder.objects.create(name="test") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") compliance_assessment = ComplianceAssessment.objects.create( name="test", - project=Project.objects.create(name="test", folder=folder), + project=Project.objects.create(name="test", folder=test.folder), framework=Framework.objects.all()[0], ) - security_measure = SecurityMeasure.objects.create(name="test", folder=folder) + security_measure = SecurityMeasure.objects.create(name="test", folder=test.folder) EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Requirement Assessments", RequirementAssessment, { "status": REQUIREMENT_ASSESSMENT_STATUS, "observation": REQUIREMENT_ASSESSMENT_OBSERVATION, - "folder": str(folder.id), + "folder": str(test.folder.id), "compliance_assessment": str(compliance_assessment.id), "requirement": str(RequirementNode.objects.all()[0].id), "security_measures": [str(security_measure.id)], @@ -160,18 +161,18 @@ def test_create_requirement_assessments(self, authenticated_client): }, base_count=-1, fails=True, - expected_status=HTTP_400_BAD_REQUEST + expected_status=HTTP_403_FORBIDDEN ) - def test_update_requirement_assessments(self, authenticated_client): + def test_update_requirement_assessments(self, test): """test to update requirement assessments with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") - folder = Folder.objects.create(name="test") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") + folder = Folder.objects.create(name="test2") compliance_assessment = ComplianceAssessment.objects.create( name="test", project=Project.objects.create( - name="test", folder=Folder.get_root_folder() + name="test", folder=test.folder ), framework=Framework.objects.all()[0], ) @@ -183,13 +184,13 @@ def test_update_requirement_assessments(self, authenticated_client): security_measure = SecurityMeasure.objects.create(name="test", folder=folder) EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Requirement Assessments", RequirementAssessment, { "status": REQUIREMENT_ASSESSMENT_STATUS, "observation": REQUIREMENT_ASSESSMENT_OBSERVATION, - "folder": Folder.get_root_folder(), + "folder": test.folder, "compliance_assessment": compliance_assessment, "requirement": RequirementNode.objects.all()[0], }, @@ -202,21 +203,23 @@ def test_update_requirement_assessments(self, authenticated_client): "security_measures": [str(security_measure.id)], }, { - "folder": str(Folder.get_root_folder().id), + "folder": str(test.folder.id), "compliance_assessment": { "id": str(compliance_assessment.id), "str": compliance_assessment.name, }, "requirement": str(RequirementNode.objects.all()[0].id), }, + user_group=test.user_group, ) - def test_get_status_choices(self, authenticated_client): + def test_get_status_choices(self, test): """test to get requirement assessments status choices from the API with authentication""" EndpointTestsQueries.Auth.get_object_options( - authenticated_client, + test.client, "Requirement Assessments", "status", RequirementAssessment.Status.choices, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_requirement_nodes.py b/backend/app_tests/api/test_api_requirement_nodes.py index d4a34ab4f..421183bab 100644 --- a/backend/app_tests/api/test_api_requirement_nodes.py +++ b/backend/app_tests/api/test_api_requirement_nodes.py @@ -3,10 +3,11 @@ from core.models import RequirementNode, Framework from iam.models import Folder -from test_api import EndpointTestsQueries, EndpointTestsUtils +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries, EndpointTestsUtils # Generic requirement data for tests -REQUIREMENT_NODE_NAME = "Test Requiremen Node" +REQUIREMENT_NODE_NAME = "Test Requirement Node" REQUIREMENT_NODE_DESCRIPTION = "Test Description" REQUIREMENT_NODE_URN = "urn:test:req_node.t:1" REQUIREMENT_NODE_PARENT_URN = "urn:test:req_node.t" @@ -39,15 +40,16 @@ def test_get_requirement_nodes(self, authenticated_client): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestRequirementNodesAuthenticated: """Perform tests on RequirementNodes API endpoint with authentication""" - def test_get_requirement_nodes(self, authenticated_client): + def test_get_requirement_nodes(self, test): """test to get requirement nodes from the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Requirement nodes", RequirementNode, { @@ -58,21 +60,22 @@ def test_get_requirement_nodes(self, authenticated_client): "order_id": REQUIREMENT_NODE_ORDER_ID, "level": REQUIREMENT_NODE_LEVEL, "assessable": True, - "folder": Folder.get_root_folder(), + "folder": test.folder, "framework": Framework.objects.all()[0], }, { - "folder": str(Folder.get_root_folder().id), + "folder": str(test.folder.id), "framework": str(Framework.objects.all()[0].id), }, base_count=-1, + user_group=test.user_group, ) - def test_import_requirement_nodes(self, authenticated_client): + def test_import_requirement_nodes(self, test): """test that the requirements values imported from a library are correct""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") + EndpointTestsQueries.Auth.import_object(test.client, "Framework", user_group=test.user_group) EndpointTestsQueries.Auth.compare_results( - authenticated_client, + test.client, "Requirement nodes", EndpointTestsUtils.get_endpoint_url("Requirement nodes"), EndpointTestsUtils.get_object_urn("Framework"), diff --git a/backend/app_tests/api/test_api_risk_acceptance.py b/backend/app_tests/api/test_api_risk_acceptances.py similarity index 79% rename from backend/app_tests/api/test_api_risk_acceptance.py rename to backend/app_tests/api/test_api_risk_acceptances.py index 128bd7f7d..6dbbc1d53 100644 --- a/backend/app_tests/api/test_api_risk_acceptance.py +++ b/backend/app_tests/api/test_api_risk_acceptances.py @@ -7,11 +7,11 @@ RiskScenario, RiskMatrix, RiskAssessment, - Threat, ) from iam.models import Folder, UserGroup -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic risk acceptance data for tests RISK_ACCEPTANCE_NAME = "Test Risk Acceptance" @@ -92,18 +92,18 @@ def test_delete_risk_acceptances(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestRiskAcceptanceAuthenticated: """Perform tests on Risk Acceptance API endpoint with authentication""" - def test_get_risk_acceptances(self, authenticated_client): + def test_get_risk_acceptances(self, test): """test to get risk acceptances from the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") - folder = Folder.objects.create(name="test") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") approver = User.objects.create_user(email="approver@test.com") EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Risk Acceptances", RiskAcceptance, { @@ -114,40 +114,35 @@ def test_get_risk_acceptances(self, authenticated_client): # 'rejected_date': RISK_ACCEPTANCE_REJECTED_DATE, # 'revoked_date': RISK_ACCEPTANCE_REVOKED_DATE, "state": RISK_ACCEPTANCE_STATE[0], - "folder": folder, + "folder": test.folder, "approver": approver, }, { - "folder": {"id": str(folder.id), "str": folder.name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "approver": {"id": str(approver.id), "str": approver.email}, "state": RISK_ACCEPTANCE_STATE[1], }, + user_group=test.user_group, ) - @pytest.mark.skip( - reason="Everything is working fine on the API but the approver field is problematic in tests" - ) - # NOTE: It is related to roles and approver permissions somewhere in the test context - def test_create_risk_acceptances(self, authenticated_client): + def test_create_risk_acceptances(self, test): """test to create risk acceptances with the API with authentication""" approver = User.objects.create_user(email="approver@test.com") UserGroup.objects.get(name="BI-UG-GVA").user_set.add(approver) - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") - folder = Folder.objects.create(name="test") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") risk_scenario = RiskScenario.objects.create( name="test scenario", description="test description", risk_assessment=RiskAssessment.objects.create( name="test", - project=Project.objects.create(name="test", folder=folder), - risk_matrix=RiskMatrix.objects.create(name="test", folder=folder), + project=Project.objects.create(name="test", folder=test.folder), + risk_matrix=RiskMatrix.objects.create(name="test", folder=test.folder), ), - threat=Threat.objects.create(name="test", folder=folder), ) EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Risk Acceptances", RiskAcceptance, { @@ -158,30 +153,26 @@ def test_create_risk_acceptances(self, authenticated_client): # 'rejected_date': RISK_ACCEPTANCE_REJECTED_DATE, # 'revoked_date': RISK_ACCEPTANCE_REVOKED_DATE, # 'state': RISK_ACCEPTANCE_STATE[0], - "folder": str(folder.id), + "folder": str(test.folder.id), "approver": str(approver.id), "risk_scenarios": [str(risk_scenario.id)], }, { - "folder": {"id": str(folder.id), "str": folder.name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "approver": {"id": str(approver.id), "str": approver.email}, "risk_scenarios": [ {"id": str(risk_scenario.id), "str": str(risk_scenario)} ], # 'state': RISK_ACCEPTANCE_STATE[1], }, + user_group=test.user_group, ) - @pytest.mark.skip( - reason="Everything is working fine on the API but the approver field is problematic in tests" - ) - # NOTE: It is related to roles and approver permissions somewhere in the test context - def test_update_risk_acceptances(self, authenticated_client): + def test_update_risk_acceptances(self, test): """test to update risk acceptances with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Framework") - folder = Folder.objects.create(name="test") - folder2 = Folder.objects.create(name="test2") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Framework") + folder = Folder.objects.create(name="test2") approver = User.objects.create_user(email="approver@test.com") UserGroup.objects.get(name="BI-UG-GVA").user_set.add(approver) approver2 = User.objects.create_user(email="approver2@test.com") @@ -191,14 +182,13 @@ def test_update_risk_acceptances(self, authenticated_client): description="test description", risk_assessment=RiskAssessment.objects.create( name="test", - project=Project.objects.create(name="test", folder=folder2), - risk_matrix=RiskMatrix.objects.create(name="test", folder=folder2), + project=Project.objects.create(name="test", folder=folder), + risk_matrix=RiskMatrix.objects.create(name="test", folder=folder), ), - threat=Threat.objects.create(name="test", folder=folder2), ) EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Risk Acceptances", RiskAcceptance, { @@ -206,33 +196,35 @@ def test_update_risk_acceptances(self, authenticated_client): "description": RISK_ACCEPTANCE_DESCRIPTION, "expiry_date": RISK_ACCEPTANCE_EXPIRY_DATE, # 'state': RISK_ACCEPTANCE_STATE[0], - "folder": folder, + "folder": test.folder, "approver": approver, }, { "name": "new " + RISK_ACCEPTANCE_NAME, "description": "new " + RISK_ACCEPTANCE_DESCRIPTION, "expiry_date": "2024-05-05", - "folder": str(folder2.id), + "folder": str(folder.id), "approver": str(approver2.id), "risk_scenarios": [str(risk_scenario.id)], }, { - "folder": {"id": str(folder.id), "str": folder.name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "approver": {"id": str(approver.id), "str": approver.email}, # 'state': RISK_ACCEPTANCE_STATE[1], }, + user_group=test.user_group, ) - def test_delete_risk_acceptances(self, authenticated_client): + def test_delete_risk_acceptances(self, test): """test to delete risk acceptances with the API with authentication""" EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Risk Acceptances", RiskAcceptance, { "name": RISK_ACCEPTANCE_NAME, - "folder": Folder.objects.create(name="test"), + "folder": test.folder, }, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_risk_assessments.py b/backend/app_tests/api/test_api_risk_assessments.py index 5b8e85bd4..cd08703e8 100644 --- a/backend/app_tests/api/test_api_risk_assessments.py +++ b/backend/app_tests/api/test_api_risk_assessments.py @@ -3,7 +3,8 @@ from core.models import Project, RiskAssessment, RiskMatrix from iam.models import Folder, User -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic project data for tests RISK_ASSESSMENT_NAME = "Test risk_assessment" @@ -98,20 +99,21 @@ def test_delete_risk_assessments(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestRiskAssessmentAuthenticated: """Perform tests on Risk Assessment API endpoint with authentication""" - def test_get_risk_assessments(self, authenticated_client): + def test_get_risk_assessments(self, test): """test to get risk assessments from the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix") project = Project.objects.create( - name="test", folder=Folder.objects.create(name="test") + name="test", folder=test.folder ) risk_matrix = RiskMatrix.objects.all()[0] EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Risk Assessment", RiskAssessment, { @@ -125,19 +127,20 @@ def test_get_risk_assessments(self, authenticated_client): "project": {"id": str(project.id), "str": project.name}, "risk_matrix": {"id": str(risk_matrix.id), "str": str(risk_matrix)}, }, + user_group=test.user_group, ) - def test_create_risk_assessments(self, authenticated_client): + def test_create_risk_assessments(self, test): """test to create risk assessments with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix") project = Project.objects.create( - name="test", folder=Folder.objects.create(name="test") + name="test", folder=test.folder ) risk_matrix = RiskMatrix.objects.all()[0] EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Risk Assessment", RiskAssessment, { @@ -151,15 +154,16 @@ def test_create_risk_assessments(self, authenticated_client): "project": {"id": str(project.id), "str": project.name}, "risk_matrix": {"id": str(risk_matrix.id), "str": str(risk_matrix)}, }, + user_group=test.user_group, ) - def test_update_risk_assessments(self, authenticated_client): + def test_update_risk_assessments(self, test): """test to update risk assessments with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix2") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix2") project = Project.objects.create( - name="test", folder=Folder.objects.create(name="test") + name="test", folder=test.folder ) project2 = Project.objects.create( name="test2", folder=Folder.objects.create(name="test2") @@ -168,7 +172,7 @@ def test_update_risk_assessments(self, authenticated_client): risk_matrix2 = RiskMatrix.objects.all()[1] EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Risk Assessment", RiskAssessment, { @@ -183,25 +187,26 @@ def test_update_risk_assessments(self, authenticated_client): "description": "new " + RISK_ASSESSMENT_DESCRIPTION, "version": RISK_ASSESSMENT_VERSION + ".1", "project": str(project2.id), - "risk_matrix": str(risk_matrix.id), + "risk_matrix": str(risk_matrix2.id), }, { "project": {"id": str(project.id), "str": project.name}, "risk_matrix": {"id": str(risk_matrix.id), "str": str(risk_matrix)}, }, + user_group=test.user_group, ) - def test_delete_risk_assessments(self, authenticated_client): + def test_delete_risk_assessments(self, test): """test to delete risk assessments with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix") project = Project.objects.create( - name="test", folder=Folder.objects.create(name="test") + name="test", folder=test.folder ) risk_matrix = RiskMatrix.objects.all()[0] EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Risk Assessment", RiskAssessment, { @@ -209,10 +214,11 @@ def test_delete_risk_assessments(self, authenticated_client): "project": project, "risk_matrix": risk_matrix, }, + user_group=test.user_group, ) # TODO add option quality_check (endpoint /api/risk-assessments/quality_check/ not working) - # def test_get_status_choice(self, authenticated_client): + # def test_get_status_choice(self, test): # """test to get risk assessments status choices from the API with authentication""" - # EndpointTestsQueries.get_object_options_auth(authenticated_client, "Risk Assessment", "lc_status", Project.PRJ_LC_STATUS) + # EndpointTestsQueries.get_object_options_auth(test.client, "Risk Assessment", "lc_status", Project.PRJ_LC_STATUS) diff --git a/backend/app_tests/api/test_api_risk_scenarios.py b/backend/app_tests/api/test_api_risk_scenarios.py index f5b612d9f..297b9ad66 100644 --- a/backend/app_tests/api/test_api_risk_scenarios.py +++ b/backend/app_tests/api/test_api_risk_scenarios.py @@ -11,7 +11,8 @@ ) from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic project data for tests RISK_SCENARIO_NAME = "Test scenario" @@ -147,23 +148,23 @@ def test_delete_risk_scenarios(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestRiskScenariosAuthenticated: """Perform tests on Risk Scenarios API endpoint with authentication""" - def test_get_risk_scenarios(self, authenticated_client): + def test_get_risk_scenarios(self, test): """test to get risk scenarios from the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") - folder = Folder.objects.create(name="testFolder") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix") risk_assessment = RiskAssessment.objects.create( name="test", - project=Project.objects.create(name="testProject", folder=folder), + project=Project.objects.create(name="testProject", folder=test.folder), risk_matrix=RiskMatrix.objects.all()[0], ) - threat = Threat.objects.create(name="test", folder=folder) + threat = Threat.objects.create(name="test", folder=test.folder) EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Risk Scenarios", RiskScenario, { @@ -199,24 +200,24 @@ def test_get_risk_scenarios(self, authenticated_client): "str": risk_assessment.risk_matrix.name, }, }, + user_group=test.user_group, ) - def test_create_risk_scenarios(self, authenticated_client): + def test_create_risk_scenarios(self, test): """test to create risk scenarios with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") - folder = Folder.objects.create(name="test") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix") risk_assessment = RiskAssessment.objects.create( name="test", - project=Project.objects.create(name="test", folder=folder), + project=Project.objects.create(name="test", folder=test.folder), risk_matrix=RiskMatrix.objects.all()[0], ) - threat = Threat.objects.create(name="test", folder=folder) - asset = Asset.objects.create(name="test", folder=folder) - security_measures = SecurityMeasure.objects.create(name="test", folder=folder) + threat = Threat.objects.create(name="test", folder=test.folder) + asset = Asset.objects.create(name="test", folder=test.folder) + security_measures = SecurityMeasure.objects.create(name="test", folder=test.folder) EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Risk Scenarios", RiskScenario, { @@ -258,18 +259,19 @@ def test_create_risk_scenarios(self, authenticated_client): {"id": str(security_measures.id), "str": security_measures.name} ], }, + user_group=test.user_group, ) - def test_update_risk_scenarios(self, authenticated_client): + def test_update_risk_scenarios(self, test): """test to update risk scenarios with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix2") - folder = Folder.objects.create(name="test") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix2") + folder = Folder.objects.create(name="test2") risk_assessment = RiskAssessment.objects.create( name="test", project=Project.objects.create( - name="test", folder=Folder.get_root_folder() + name="test", folder=test.folder ), risk_matrix=RiskMatrix.objects.all()[0], ) @@ -278,13 +280,13 @@ def test_update_risk_scenarios(self, authenticated_client): project=Project.objects.create(name="test2", folder=folder), risk_matrix=RiskMatrix.objects.all()[1], ) - threat = Threat.objects.create(name="test", folder=Folder.get_root_folder()) + threat = Threat.objects.create(name="test", folder=test.folder) threat2 = Threat.objects.create(name="test2", folder=folder) asset = Asset.objects.create(name="test", folder=folder) security_measures = SecurityMeasure.objects.create(name="test", folder=folder) EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Risk Scenarios", RiskScenario, { @@ -337,13 +339,14 @@ def test_update_risk_scenarios(self, authenticated_client): "str": risk_assessment.risk_matrix.name, }, }, + user_group=test.user_group, ) - def test_delete_risk_scenarios(self, authenticated_client): + def test_delete_risk_scenarios(self, test): """test to delete risk scenarios with the API with authentication""" - EndpointTestsQueries.Auth.import_object(authenticated_client, "Risk matrix") - folder = Folder.objects.create(name="testFolder") + EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix") + folder = test.folder risk_assessment = RiskAssessment.objects.create( name="test", project=Project.objects.create(name="testProject", folder=folder), @@ -352,7 +355,7 @@ def test_delete_risk_scenarios(self, authenticated_client): threat = Threat.objects.create(name="test", folder=folder) EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Risk Scenarios", RiskScenario, { @@ -360,14 +363,16 @@ def test_delete_risk_scenarios(self, authenticated_client): "risk_assessment": risk_assessment, "threats": [threat], }, + user_group=test.user_group, ) - def test_get_treatment_choices(self, authenticated_client): + def test_get_treatment_choices(self, test): """test to get risk scenarios treatment choices from the API with authentication""" EndpointTestsQueries.Auth.get_object_options( - authenticated_client, + test.client, "Risk Scenarios", "treatment", RiskScenario.TREATMENT_OPTIONS, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_security_functions.py b/backend/app_tests/api/test_api_security_functions.py index a7a49c68e..4f7c855ad 100644 --- a/backend/app_tests/api/test_api_security_functions.py +++ b/backend/app_tests/api/test_api_security_functions.py @@ -1,10 +1,11 @@ import pytest -from rest_framework import status +from rest_framework.status import HTTP_403_FORBIDDEN from rest_framework.test import APIClient from core.models import SecurityFunction from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic security function data for tests SECURITY_FUNCTION_REF_ID = "Test-Security-Function" @@ -85,14 +86,15 @@ def test_delete_security_functions(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestSecurityFunctionsAuthenticated: """Perform tests on Security Functions API endpoint with authentication""" - def test_get_security_functions(self, authenticated_client): + def test_get_security_functions(self, test): """test to get security functions from the API with authentication""" EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Security functions", SecurityFunction, { @@ -105,13 +107,14 @@ def test_get_security_functions(self, authenticated_client): { "folder": {"str": Folder.get_root_folder().name}, }, + user_group=test.user_group, ) - def test_create_security_functions(self, authenticated_client): + def test_create_security_functions(self, test): """test to create security functions with the API with authentication""" EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Security functions", SecurityFunction, { @@ -120,17 +123,18 @@ def test_create_security_functions(self, authenticated_client): "description": SECURITY_FUNCTION_DESCRIPTION, "urn": SECURITY_FUNCTION_URN, "provider": SECURITY_FUNCTION_PROVIDER, - "folder": str(Folder.get_root_folder().id), + "folder": str(test.folder.id), }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, }, + user_group=test.user_group, ) - def test_update_security_function_with_url(self, authenticated_client): + def test_update_security_function_with_urn(self, test): """test to update an imported security function (with a URN) with the API with authentication""" EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Security functions", SecurityFunction, { @@ -146,18 +150,18 @@ def test_update_security_function_with_url(self, authenticated_client): "description": "new " + SECURITY_FUNCTION_DESCRIPTION, "urn": SECURITY_FUNCTION_URN, "provider": "new " + SECURITY_FUNCTION_PROVIDER, - "folder": str(Folder.objects.create(name="test").id), }, { "folder": {"str": Folder.get_root_folder().name}, }, fails=True, - expected_status=status.HTTP_400_BAD_REQUEST, + expected_status=HTTP_403_FORBIDDEN, # Imported objects cannot be updated + user_group=test.user_group, ) - def test_update_security_function(self, authenticated_client): + def test_update_security_function(self, test): EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Security functions", SecurityFunction, { @@ -165,29 +169,34 @@ def test_update_security_function(self, authenticated_client): "name": SECURITY_FUNCTION_NAME, "description": SECURITY_FUNCTION_DESCRIPTION, "provider": SECURITY_FUNCTION_PROVIDER, + "folder": test.folder, }, { "ref_id": SECURITY_FUNCTION_REF_ID, "name": SECURITY_FUNCTION_NAME, "description": "new " + SECURITY_FUNCTION_DESCRIPTION, "provider": "new " + SECURITY_FUNCTION_PROVIDER, - "folder": str(Folder.objects.create(name="test").id), }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, }, + { + "folder": str(test.folder.id) + }, + user_group=test.user_group, ) - def test_delete_security_functions(self, authenticated_client): + def test_delete_security_functions(self, test): """test to delete security functions with the API with authentication""" EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Security functions", SecurityFunction, { "ref_id": SECURITY_FUNCTION_REF_ID, "name": SECURITY_FUNCTION_NAME, - "folder": Folder.objects.create(name="test"), + "folder": test.folder, }, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_security_measures.py b/backend/app_tests/api/test_api_security_measures.py index 83d846bc5..779be5e19 100644 --- a/backend/app_tests/api/test_api_security_measures.py +++ b/backend/app_tests/api/test_api_security_measures.py @@ -3,7 +3,8 @@ from core.models import SecurityFunction, SecurityMeasure from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic security measure data for tests SECURITY_MEASURE_NAME = "Test Security Measure" @@ -86,14 +87,15 @@ def test_delete_security_measures(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestSecurityMeasuresAuthenticated: """Perform tests on Security Measures API endpoint with authentication""" - def test_get_security_measures(self, authenticated_client): + def test_get_security_measures(self, test): """test to get security measures from the API with authentication""" EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Security measures", SecurityMeasure, { @@ -104,26 +106,27 @@ def test_get_security_measures(self, authenticated_client): "link": SECURITY_MEASURE_LINK, "eta": SECURITY_MEASURE_ETA, "effort": SECURITY_MEASURE_EFFORT[0], - "folder": Folder.get_root_folder(), + "folder": test.folder, }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "security_function": None, "category": SECURITY_MEASURE_CATEGORY[1], "status": SECURITY_MEASURE_STATUS[1], "effort": SECURITY_MEASURE_EFFORT[1], }, + user_group=test.user_group, ) - def test_create_security_measures(self, authenticated_client): + def test_create_security_measures(self, test): """test to create security measures with the API with authentication""" security_function = SecurityFunction.objects.create( - name="test", typical_evidence={}, folder=Folder.objects.create(name="test") + name="test", typical_evidence={}, folder=Folder.objects.create(name="test2") ) EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Security measures", SecurityMeasure, { @@ -134,26 +137,27 @@ def test_create_security_measures(self, authenticated_client): "link": SECURITY_MEASURE_LINK, "eta": SECURITY_MEASURE_ETA, "effort": SECURITY_MEASURE_EFFORT[0], - "folder": str(Folder.get_root_folder().id), + "folder": str(test.folder.id), }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "category": SECURITY_MEASURE_CATEGORY[1], "status": SECURITY_MEASURE_STATUS[1], "effort": SECURITY_MEASURE_EFFORT[1], }, + user_group=test.user_group, ) - def test_update_security_measures(self, authenticated_client): + def test_update_security_measures(self, test): """test to update security measures with the API with authentication""" - folder = Folder.objects.create(name="test") + folder = Folder.objects.create(name="test2") security_function = SecurityFunction.objects.create( name="test", typical_evidence={}, folder=folder ) EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Security measures", SecurityMeasure, { @@ -164,7 +168,7 @@ def test_update_security_measures(self, authenticated_client): "link": SECURITY_MEASURE_LINK, "eta": SECURITY_MEASURE_ETA, "effort": SECURITY_MEASURE_EFFORT[0], - "folder": Folder.get_root_folder(), + "folder": test.folder, }, { "name": "new " + SECURITY_MEASURE_NAME, @@ -177,49 +181,57 @@ def test_update_security_measures(self, authenticated_client): "folder": str(folder.id), }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "category": SECURITY_MEASURE_CATEGORY[1], "status": SECURITY_MEASURE_STATUS[1], "effort": SECURITY_MEASURE_EFFORT[1], }, + user_group=test.user_group, ) - def test_delete_security_measures(self, authenticated_client): + def test_delete_security_measures(self, test): """test to delete security measures with the API with authentication""" EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Security measures", SecurityMeasure, { "name": SECURITY_MEASURE_NAME, - "folder": Folder.objects.create(name="test"), + "folder": test.folder, }, + user_group=test.user_group, ) - def test_get_effort_choices(self, authenticated_client): + def test_get_effort_choices(self, test): """test to get security measures effort choices from the API with authentication""" EndpointTestsQueries.Auth.get_object_options( - authenticated_client, "Security measures", "effort", SecurityMeasure.EFFORT + test.client, + "Security measures", + "effort", + SecurityMeasure.EFFORT, + user_group=test.user_group ) - def test_get_status_choices(self, authenticated_client): + def test_get_status_choices(self, test): """test to get security measures status choices from the API with authentication""" EndpointTestsQueries.Auth.get_object_options( - authenticated_client, + test.client, "Security measures", "status", SecurityMeasure.Status.choices, + user_group=test.user_group, ) - def test_get_type_choices(self, authenticated_client): + def test_get_type_choices(self, test): """test to get security measures type choices from the API with authentication""" EndpointTestsQueries.Auth.get_object_options( - authenticated_client, + test.client, "Security measures", "category", SecurityMeasure.CATEGORY, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_threats.py b/backend/app_tests/api/test_api_threats.py index 75558941e..5d709f727 100644 --- a/backend/app_tests/api/test_api_threats.py +++ b/backend/app_tests/api/test_api_threats.py @@ -1,10 +1,11 @@ import pytest -from rest_framework.status import HTTP_400_BAD_REQUEST +from rest_framework.status import HTTP_403_FORBIDDEN from rest_framework.test import APIClient from core.models import Threat from iam.models import Folder -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS +from test_utils import EndpointTestsQueries # Generic threat data for tests THREAT_REF_ID = "Test-Threat-ID" @@ -88,14 +89,15 @@ def test_delete_threats(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestThreatsAuthenticated: """Perform tests on Threats API endpoint with authentication""" - def test_get_threats(self, authenticated_client): + def test_get_threats(self, test): """test to get threats from the API with authentication""" EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Threats", Threat, { @@ -108,13 +110,14 @@ def test_get_threats(self, authenticated_client): { "folder": {"str": Folder.get_root_folder().name}, }, + user_group=test.user_group, ) - def test_create_threats(self, authenticated_client): + def test_create_threats(self, test): """test to create threats with the API with authentication""" EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Threats", Threat, { @@ -122,20 +125,21 @@ def test_create_threats(self, authenticated_client): "name": THREAT_NAME, "description": THREAT_DESCRIPTION, "provider": THREAT_PROVIDER, - "folder": str(Folder.get_root_folder().id), + "folder": str(test.folder.id), }, { - "folder": {"str": Folder.get_root_folder().name}, + "folder": {"id": str(test.folder.id), "str": test.folder.name}, "urn": None, }, + user_group=test.user_group, ) - def test_update_threats_with_url(self, authenticated_client): + def test_update_threats_with_urn(self, test): """test to update imported threat (with URN) with the API with authentication""" - folder = Folder.objects.create(name="test") + folder = Folder.objects.create(name="test2") EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Threats", Threat, { @@ -154,16 +158,17 @@ def test_update_threats_with_url(self, authenticated_client): "folder": str(folder.id), }, fails=True, - expected_status=HTTP_400_BAD_REQUEST, + expected_status=HTTP_403_FORBIDDEN, # Imported objects cannot be modified + user_group=test.user_group, ) - def test_update_threats(self, authenticated_client): + def test_update_threats(self, test): """test to update threats with the API with authentication""" - folder = Folder.objects.create(name="test") + folder = Folder.objects.create(name="test2") EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Threats", Threat, { @@ -171,6 +176,7 @@ def test_update_threats(self, authenticated_client): "name": THREAT_NAME, "description": THREAT_DESCRIPTION, "provider": THREAT_PROVIDER, + "folder": test.folder }, { "ref_id": "new " + THREAT_REF_ID, @@ -179,14 +185,19 @@ def test_update_threats(self, authenticated_client): "provider": "new " + THREAT_PROVIDER, "folder": str(folder.id), }, + { + "folder": {"id": str(test.folder.id), "str": test.folder.name}, + }, + user_group=test.user_group, ) - def test_delete_threats(self, authenticated_client): + def test_delete_threats(self, test): """test to delete threats with the API with authentication""" EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Threats", Threat, - {"name": THREAT_NAME, "folder": Folder.objects.create(name="test")}, + {"name": THREAT_NAME, "folder": test.folder}, + user_group=test.user_group, ) diff --git a/backend/app_tests/api/test_api_user_groups.py b/backend/app_tests/api/test_api_user_groups.py new file mode 100644 index 000000000..74703aa74 --- /dev/null +++ b/backend/app_tests/api/test_api_user_groups.py @@ -0,0 +1,18 @@ +import pytest +from core.models import User + +from iam.models import Folder, UserGroup, RoleAssignment +from test_vars import GROUPS_PERMISSIONS, TEST_USER_EMAIL +from test_utils import EndpointTestsQueries + +@pytest.mark.django_db +class TestUserGroups: + """Perform tests on User Groups API endpoint with authentication""" + + @pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) + def test_group_permissions(self, test): + """test that a user with a specific role has the correct permissions""" + + user_permissions = RoleAssignment.get_permissions(User.objects.get(email=TEST_USER_EMAIL)) + for perm in GROUPS_PERMISSIONS[test.user_group]['perms']: + assert perm in user_permissions.keys(), f"Permission {perm} not found in user permissions (group: {test.user_group})" diff --git a/backend/app_tests/api/test_api_users.py b/backend/app_tests/api/test_api_users.py index 160027f20..8646f523d 100644 --- a/backend/app_tests/api/test_api_users.py +++ b/backend/app_tests/api/test_api_users.py @@ -4,8 +4,8 @@ from rest_framework.test import APIClient from iam.models import User -from test_vars import USERS_ENDPOINT as API_ENDPOINT -from test_api import EndpointTestsQueries +from test_vars import GROUPS_PERMISSIONS, USERS_ENDPOINT as API_ENDPOINT +from test_utils import EndpointTestsQueries # Generic user data for tests USER_FIRSTNAME = "John" @@ -72,57 +72,68 @@ def test_delete_users(self): @pytest.mark.django_db +@pytest.mark.parametrize("test", GROUPS_PERMISSIONS.keys(), ids=[GROUPS_PERMISSIONS[key]["name"] for key in GROUPS_PERMISSIONS.keys()], indirect=True) class TestUsersAuthenticated: """Perform tests on Users API endpoint with authentication""" - def test_get_users(self, authenticated_client): + def test_get_users(self, test): """test to get users from the API with authentication""" EndpointTestsQueries.Auth.get_object( - authenticated_client, + test.client, "Users", User, {"email": USER_EMAIL, "first_name": USER_FIRSTNAME, "last_name": USER_NAME}, - base_count=1, + base_count=2, + item_search_field="email", + user_group=test.user_group, ) - def test_create_users(self, authenticated_client): + def test_create_users(self, test): """test to create users with the API with authentication""" EndpointTestsQueries.Auth.create_object( - authenticated_client, + test.client, "Users", User, {"email": USER_EMAIL, "first_name": USER_FIRSTNAME, "last_name": USER_NAME}, - base_count=1, + base_count=2, + item_search_field="email", + user_group=test.user_group, ) - def test_update_users(self, authenticated_client): + def test_update_users(self, test): """test to update users with the API with authentication""" EndpointTestsQueries.Auth.update_object( - authenticated_client, + test.client, "Users", User, - {"email": USER_EMAIL, "first_name": USER_FIRSTNAME, "last_name": USER_NAME}, + { + "email": USER_EMAIL, + "first_name": USER_FIRSTNAME, + "last_name": USER_NAME + }, { "email": "new" + USER_EMAIL, "first_name": "new" + USER_FIRSTNAME, "last_name": "new" + USER_NAME, }, + user_group=test.user_group, ) - def test_delete_users(self, authenticated_client): + def test_delete_users(self, test): """test to delete users with the API with authentication""" EndpointTestsQueries.Auth.delete_object( - authenticated_client, + test.client, "Users", User, {"email": USER_EMAIL, "first_name": USER_FIRSTNAME, "last_name": USER_NAME}, + user_group=test.user_group, ) - def test_uniqueness_emails(self, authenticated_client): + def test_uniqueness_emails(self, test): """test to create users with the API with authentication and already existing email""" url = reverse(API_ENDPOINT) @@ -133,11 +144,11 @@ def test_uniqueness_emails(self, authenticated_client): } # Uses the API endpoint to create a user - response = authenticated_client.post(url, data, format="json") + response = test.admin_client.post(url, data, format="json") assert response.status_code == status.HTTP_201_CREATED # Uses the API endpoint to create another user with the same email - response = authenticated_client.post(url, data, format="json") + response = test.admin_client.post(url, data, format="json") # Asserts that the user was not created assert ( @@ -147,7 +158,7 @@ def test_uniqueness_emails(self, authenticated_client): "email": ["user with this email already exists."] }, "users can be created with an already used email" - def test_invalid_emails(self, authenticated_client): + def test_invalid_emails(self, test): """test to create users with the API with authentication and invalid emails""" url = reverse(API_ENDPOINT) @@ -169,7 +180,7 @@ def test_invalid_emails(self, authenticated_client): } # Uses the API endpoint to create a user - response = authenticated_client.post(url, data, format="json") + response = test.admin_client.post(url, data, format="json") # Asserts that the user was not created assert ( diff --git a/backend/app_tests/api/test_api.py b/backend/app_tests/api/test_utils.py similarity index 63% rename from backend/app_tests/api/test_api.py rename to backend/app_tests/api/test_utils.py index 1737b1671..045da3711 100644 --- a/backend/app_tests/api/test_api.py +++ b/backend/app_tests/api/test_utils.py @@ -1,8 +1,10 @@ +import pytest import json import re from django.db.models.fields.related_descriptors import ManyToManyDescriptor from django.urls import reverse from rest_framework import status +from rest_framework.test import APIClient from ciso_assistant.settings import EMAIL_HOST, EMAIL_HOST_RESCUE from test_vars import * @@ -10,7 +12,7 @@ class EndpointTestsUtils: """Provides utils functions for API endpoints testing""" - + def get_endpoint_url(verbose_name: str, resolved: bool = True): """Get the endpoint URL for the given object""" @@ -25,6 +27,31 @@ def get_object_urn(object_name: str, resolved: bool = True): urn = get_var(urn_varname) return f"{reverse(LIBRARIES_ENDPOINT)}{urn}/" if resolved else eval(urn) + @pytest.mark.django_db + def get_test_client_and_folder(authenticated_client, role: str): + """Get an authenticated client with a specific role and the folder associated to the role""" + from iam.models import Folder, User, UserGroup + + EndpointTestsQueries.Auth.create_object(authenticated_client, "Folders", Folder, {"name": "test"}) + folder = Folder.objects.get(name="test") + + user = User.objects.create_user(TEST_USER_EMAIL) + UserGroup.objects.get(name=role, folder=Folder.objects.get(name=GROUPS_PERMISSIONS[role]["folder"])).user_set.add(user) + client = APIClient() + client.force_login(user) + return client, folder + + def expected_request_response(action: str, object: str, user_group, expected_status: int = status.HTTP_200_OK): + """Get the expected request response""" + perm_name = f"{action}_{get_singular_name(object).lower().replace(' ', '')}" + + if perm_name in GROUPS_PERMISSIONS[user_group]['perms']: + fails = False + expected_status = expected_status + else: + fails = True + expected_status = status.HTTP_403_FORBIDDEN + return fails, expected_status class EndpointTestsQueries: """Provides tests functions for API endpoints testing""" @@ -259,9 +286,11 @@ def get_object( build_params: dict = {}, test_params: dict = {}, base_count: int = 0, + item_search_field: str = None, endpoint: str = None, fails: bool = False, expected_status: int = status.HTTP_200_OK, + user_group: str = None, ): """Test to get object from the API with authentication @@ -274,26 +303,38 @@ def get_object( -1 means that the number of objects is unknown :param endpoint: the endpoint URL of the object to test (optional) """ + user_perm_fails, user_perm_expected_status = None, 0 + + if user_group: + user_perm_fails, user_perm_expected_status = EndpointTestsUtils.expected_request_response("view", verbose_name, user_group, expected_status) url = endpoint or EndpointTestsUtils.get_endpoint_url(verbose_name) # Uses the API endpoint to assert that objects are accessible response = authenticated_client.get(url) - assert ( - response.status_code == expected_status - ), f"{verbose_name} are not accessible with authentication" + if not user_group or user_perm_expected_status == status.HTTP_200_OK: + # User has permission to view the object + assert ( + response.status_code == expected_status + ), f"{verbose_name} are not accessible with permission" if expected_status == status.HTTP_200_OK else f"{verbose_name} should not be accessible (expected status: {expected_status})" + else: + # User does not have permission to view the object + assert ( + response.status_code == user_perm_expected_status + ), f"{verbose_name} are accessible without permission" if response.status_code == status.HTTP_200_OK else f"Accessing {verbose_name.lower()} should give a status {user_perm_expected_status}" + if ( base_count == 0 and not (object and build_params) and test_params - and not fails + and not (fails or user_perm_fails) ): # perfom a test with an externally created object assert ( response.json()["count"] == base_count + 1 ), f"{verbose_name} are not accessible with authentication" - elif base_count > 0 and not fails: + elif base_count > 0 and not (fails or user_perm_fails): assert ( response.json()["count"] == base_count ), f"{verbose_name} are not accessible with authentication" @@ -326,11 +367,18 @@ def get_object( # Uses the API endpoint to assert that the test object is accessible response = authenticated_client.get(url) - assert ( - response.status_code == status.HTTP_200_OK - ), f"{verbose_name} are not accessible with authentication" + if not user_group or user_perm_expected_status == status.HTTP_200_OK: + # User has permission to view the object + assert ( + response.status_code == expected_status + ), f"{verbose_name} are not accessible with permission" if expected_status == status.HTTP_200_OK else f"{verbose_name} should not be accessible (expected status: {expected_status})" + else: + # User does not have permission to view the object + assert ( + response.status_code == user_perm_expected_status + ), f"{verbose_name} are accessible without permission" if response.status_code == status.HTTP_200_OK else f"Accessing {verbose_name.lower()} should give a status {user_perm_expected_status}" - if not fails: + if not (fails or user_perm_fails): if base_count < 0: assert ( len(response.json()["results"]) != 0 @@ -340,18 +388,23 @@ def get_object( response.json()["count"] == base_count + 1 ), f"{verbose_name} are not accessible with authentication" - if not fails: - for key, value in {**build_params, **test_params}.items(): + if not (fails or user_perm_fails) and len(response.json()["results"]) != 0: + params = {**build_params, **test_params} + if len(response.json()["results"]) > 0 and item_search_field: + response_item = [res for res in response.json()["results"] if res[item_search_field] == params[item_search_field]][0] + else: + response_item = response.json()["results"][-1] + for key, value in params.items(): if ( type(value) == dict - and type(response.json()["results"][-1][key]) == str + and type(response_item[key]) == str ): assert ( - json.loads(response.json()["results"][-1][key]) == value + json.loads(response_item[key]) == value ), f"{verbose_name} {key.replace('_', ' ')} queried from the API don't match {verbose_name.lower()} {key.replace('_', ' ')} in the database" else: assert ( - response.json()["results"][-1][key] == value + response_item[key] == value ), f"{verbose_name} {key.replace('_', ' ')} queried from the API don't match {verbose_name.lower()} {key.replace('_', ' ')} in the database" def get_object_options( @@ -362,6 +415,7 @@ def get_object_options( endpoint: str = None, fails: bool = False, expected_status: int = status.HTTP_200_OK, + user_group: str = None, ): """Test to get object options from the API with authentication @@ -371,17 +425,28 @@ def get_object_options( :param option: the option to test :param endpoint: the endpoint URL of the object to test (optional) """ + user_perm_fails, user_perm_expected_status = None, 0 + + if user_group and not fails: + user_perm_fails, user_perm_expected_status = EndpointTestsUtils.expected_request_response("view", verbose_name, user_group, expected_status) url = endpoint or EndpointTestsUtils.get_endpoint_url(verbose_name) # Uses the API endpoint to assert that the object options are accessible response = authenticated_client.get(url + option + "/") - assert ( - response.status_code == expected_status - ), f"{verbose_name} {option} choices are not accessible with authentication" + if not user_group or user_perm_expected_status == status.HTTP_200_OK: + # User has permission to view the object + assert ( + response.status_code == expected_status + ), f"{verbose_name} {option} choices are not accessible with permission" if expected_status == status.HTTP_200_OK else f"{verbose_name} {option} should not be accessible (expected status: {expected_status})" + else: + # User does not have permission to view the object + assert ( + response.status_code == user_perm_expected_status + ), f"{verbose_name} {option} choices are accessible without permission" if response.status_code == status.HTTP_200_OK else f"Accessing {verbose_name.lower()} {option} should give a status {user_perm_expected_status}" - if not fails: + if not (fails or user_perm_fails): for choice in choices: assert ( choice[0] in response.json() @@ -397,10 +462,12 @@ def create_object( build_params: dict, test_params: dict = {}, base_count: int = 0, + item_search_field: str = None, endpoint: str | None = None, query_format: str = "json", fails: bool = False, expected_status: int = status.HTTP_201_CREATED, + user_group: str = None, ): """Test to create object with the API with authentication @@ -413,6 +480,10 @@ def create_object( -1 means that the number of objects is unknown :param endpoint: the endpoint URL of the object to test (optional) """ + user_perm_fails, user_perm_expected_status = None, 0 + + if user_group: + user_perm_fails, user_perm_expected_status = EndpointTestsUtils.expected_request_response("add", verbose_name, user_group, expected_status) url = endpoint or EndpointTestsUtils.get_endpoint_url(verbose_name) @@ -427,25 +498,33 @@ def create_object( return # Asserts that the object was created successfully - assert ( - response.status_code == expected_status - ), f"{verbose_name} can not be created with authentication" - - for key, value in build_params.items(): - if key == "attachment": - # Asserts that the value file name is present in the JSON response - assert ( - value.name.split("/")[-1].split(".")[0] in response.json()[key] - ), f"{verbose_name} {key.replace('_', ' ')} returned by the API after object creation don't match the provided {key.replace('_', ' ')}" - else: - assert ( - response.json()[key] == value - ), f"{verbose_name} {key.replace('_', ' ')} returned by the API after object creation don't match the provided {key.replace('_', ' ')}" + if not user_group or user_perm_expected_status == status.HTTP_201_CREATED: + # User has permission to create the object + assert ( + response.status_code == expected_status + ), f"{verbose_name} can not be created with authentication" if expected_status == status.HTTP_201_CREATED else f"{verbose_name} should not be created (expected status: {expected_status})" + else: + # User does not have permission to create the object + assert ( + response.status_code == user_perm_expected_status + ), f"{verbose_name} can be created without permission" if response.status_code == status.HTTP_201_CREATED else f"Creating {verbose_name.lower()} should give a status {user_perm_expected_status}" + + if not (fails or user_perm_fails): + for key, value in build_params.items(): + if key == "attachment": + # Asserts that the value file name is present in the JSON response + assert ( + value.name.split("/")[-1].split(".")[0] in response.json()[key] + ), f"{verbose_name} {key.replace('_', ' ')} returned by the API after object creation don't match the provided {key.replace('_', ' ')}" + else: + assert ( + response.json()[key] == value + ), f"{verbose_name} {key.replace('_', ' ')} returned by the API after object creation don't match the provided {key.replace('_', ' ')}" - # Checks that the object was created in the database - assert ( - object.objects.filter(id=response.json()["id"]).exists() - ), f"{verbose_name} created with the API are not saved in the database" + # Checks that the object was created in the database + assert ( + object.objects.filter(id=response.json()["id"]).exists() + ), f"{verbose_name} created with the API are not saved in the database" # Uses the API endpoint to assert that the created object is accessible response = authenticated_client.get(url) @@ -454,24 +533,30 @@ def create_object( response.status_code == status.HTTP_200_OK ), f"{verbose_name} are not accessible with authentication" - for key, value in {**build_params, **test_params}.items(): - if ( - key == "attachment" - and response.json()["results"][base_count][key] != value - ): - # Asserts that the value file name is present in the JSON response - assert ( - re.sub( - r"_([a-z]|[A-Z]|[0-9]){7}(?:\.)", - ".", - response.json()["results"][base_count][key], - ) - == value - ), f"{verbose_name} {key.replace('_', ' ')} queried from the API don't match {verbose_name.lower()} {key.replace('_', ' ')} in the database" + if not (fails or user_perm_fails) and len(response.json()["results"]) != 0: + params = {**build_params, **test_params} + if response.json()["count"] > 0 and item_search_field: + response_item = [res for res in response.json()["results"] if res[item_search_field] == params[item_search_field]][0] else: - assert ( - response.json()["results"][base_count][key] == value - ), f"{verbose_name} {key.replace('_', ' ')} queried from the API don't match {verbose_name.lower()} {key.replace('_', ' ')} in the database" + response_item = response.json()["results"][base_count] + for key, value in params.items(): + if ( + key == "attachment" + and response_item[key] != value + ): + # Asserts that the value file name is present in the JSON response + assert ( + re.sub( + r"_([a-z]|[A-Z]|[0-9]){7}(?:\.)", + ".", + response_item[key], + ) + == value + ), f"{verbose_name} {key.replace('_', ' ')} queried from the API don't match {verbose_name.lower()} {key.replace('_', ' ')} in the database" + else: + assert ( + response_item[key] == value + ), f"{verbose_name} {key.replace('_', ' ')} queried from the API don't match {verbose_name.lower()} {key.replace('_', ' ')} in the database" def update_object( authenticated_client, @@ -485,6 +570,7 @@ def update_object( query_format: str = "json", fails: bool = False, expected_status: int = status.HTTP_200_OK, + user_group: str = None, ): """Test to update object with the API with authentication @@ -496,6 +582,10 @@ def update_object( the test_params can ovveride the build_params :param endpoint: the endpoint URL of the object to test (optional) """ + user_perm_fails, user_perm_expected_status = None, 0 + + if user_group: + user_perm_fails, user_perm_expected_status = EndpointTestsUtils.expected_request_response("change", verbose_name, user_group, expected_status) # Creates a test object from the model m2m_fields = {} @@ -523,29 +613,44 @@ def update_object( response = authenticated_client.get(url) - assert ( - response.status_code == status.HTTP_200_OK - ), f"{verbose_name} can not be updated with authentication" - for key, value in {**build_params, **test_build_params}.items(): - if key == "attachment": - # Asserts that the value file name is present in the JSON response - assert ( - value.name.split("/")[-1].split(".")[0] in response.json()[key] - ), f"{verbose_name} {key.replace('_', ' ')} returned by the API after object creation don't match the provided {key.replace('_', ' ')}" - else: - assert ( - response.json()[key] == value - ), f"{verbose_name} {key.replace('_', ' ')} queried from the API don't match {verbose_name.lower()} {key.replace('_', ' ')} in the database" + if not user_group or EndpointTestsUtils.expected_request_response("view", verbose_name, user_group) == (False, status.HTTP_200_OK): + assert ( + response.status_code == status.HTTP_200_OK + ), f"{verbose_name} object detail can not be accessed with permission" + else: + assert ( + response.status_code == status.HTTP_403_FORBIDDEN + ), f"{verbose_name} object detail can be accessed without permission" + + if not (fails or user_perm_fails): + for key, value in {**build_params, **test_build_params}.items(): + if key == "attachment": + # Asserts that the value file name is present in the JSON response + assert ( + value.name.split("/")[-1].split(".")[0] in response.json()[key] + ), f"{verbose_name} {key.replace('_', ' ')} returned by the API after object creation don't match the provided {key.replace('_', ' ')}" + else: + assert ( + response.json()[key] == value + ), f"{verbose_name} {key.replace('_', ' ')} queried from the API don't match {verbose_name.lower()} {key.replace('_', ' ')} in the database" update_response = authenticated_client.patch( url, update_params, format=query_format ) - assert ( - update_response.status_code == expected_status - ), f"{verbose_name} can not be updated with authentication" - for key, value in {**build_params, **update_params, **test_params}.items(): - if not fails: + if not user_group or user_perm_expected_status == status.HTTP_200_OK: + # User has permission to update the object + assert ( + update_response.status_code == expected_status + ), f"{verbose_name} can not be updated with authentication" if expected_status == status.HTTP_200_OK else f"{verbose_name} should not be updated (expected status: {expected_status})" + else: + # User does not have permission to update the object + assert ( + update_response.status_code == user_perm_expected_status + ), f"{verbose_name} can be updated without permission" if update_response.status_code == status.HTTP_200_OK else f"Updating {verbose_name.lower()} should give a status {user_perm_expected_status}" + + if not (fails or user_perm_fails): + for key, value in {**build_params, **update_params, **test_params}.items(): if key == "attachment" and update_response.json()[key] != value: # Asserts that the value file name is present in the JSON response assert ( @@ -565,6 +670,7 @@ def delete_object( endpoint: str = None, fails: bool = False, expected_status: int = status.HTTP_204_NO_CONTENT, + user_group: str = None, ): """Test to delete object with the API with authentication @@ -573,6 +679,10 @@ def delete_object( :param build_params: the parameters to build the object :param endpoint: the endpoint URL of the object to test (optional) """ + user_perm_fails, user_perm_expected_status = None, 0 + + if user_group: + user_perm_fails, user_perm_expected_status = EndpointTestsUtils.expected_request_response("delete", verbose_name, user_group, expected_status) if build_params: # Creates a test object from the model @@ -601,21 +711,36 @@ def delete_object( # Asserts that the objects exists response = authenticated_client.get(url) - assert ( - response.status_code == status.HTTP_200_OK - ), f"{verbose_name} can not be deleted with authentication" + + if not user_group or EndpointTestsUtils.expected_request_response("view", verbose_name, user_group) == (False, status.HTTP_200_OK): + assert ( + response.status_code == status.HTTP_200_OK + ), f"{verbose_name} object detail can not be accessed with permission" + else: + assert ( + response.status_code == status.HTTP_403_FORBIDDEN + ), f"{verbose_name} object detail can be accessed without permission" # Asserts that the object was deleted successfully delete_response = authenticated_client.delete(url) - assert ( - delete_response.status_code == expected_status - ), f"{verbose_name} can not be deleted with authentication" - # Asserts that the objects does not exists anymore - response = authenticated_client.get(url) - assert ( - response.status_code == status.HTTP_404_NOT_FOUND - ), f"{verbose_name} has not been properly deleted with authentication" + if not user_group or user_perm_expected_status == status.HTTP_204_NO_CONTENT: + # User has permission to delete the object + assert ( + delete_response.status_code == expected_status + ), f"{verbose_name} can not be deleted with authentication" if expected_status == status.HTTP_204_NO_CONTENT else f"{verbose_name} should not be deleted (expected status: {expected_status})" + else: + # User does not have permission to delete the object + assert ( + delete_response.status_code == user_perm_expected_status + ), f"{verbose_name} can be deleted without permission" if delete_response.status_code == status.HTTP_204_NO_CONTENT else f"Deleting {verbose_name.lower()} should give a status {user_perm_expected_status}" + + if not (fails or user_perm_fails): + # Asserts that the objects does not exists anymore + response = authenticated_client.get(url) + assert ( + response.status_code == status.HTTP_404_NOT_FOUND + ), f"{verbose_name} has not been properly deleted with authentication" def import_object( authenticated_client, @@ -623,6 +748,7 @@ def import_object( urn: str | None = None, fails: bool = False, expected_status: int = status.HTTP_200_OK, + user_group: str = None, ): """Imports object with the API with authentication @@ -630,6 +756,10 @@ def import_object( :param verbose_name: the verbose name of the object to test :param urn: the endpoint URL of the object to test (optional) """ + user_perm_fails, user_perm_expected_status = None, 0 + + if user_group: + user_perm_fails, user_perm_expected_status = EndpointTestsUtils.expected_request_response("add", "library", user_group, expected_status) url = urn or EndpointTestsUtils.get_object_urn(verbose_name) @@ -637,10 +767,18 @@ def import_object( response = authenticated_client.get(url + "import/") # Asserts that the object was imported successfully - assert ( - response.status_code == expected_status - ), f"{verbose_name} can not be imported with authentication" - if not fails: + if not user_group or user_perm_expected_status == status.HTTP_200_OK: + # User has permission to import the library + assert ( + response.status_code == expected_status + ), f"{verbose_name} can not be imported with authentication" if expected_status == status.HTTP_200_OK else f"{verbose_name} should not be imported (expected status: {expected_status})" + else: + # User does not have permission to import the library + assert ( + response.status_code == user_perm_expected_status + ), f"{verbose_name} can be imported without permission" if response.status_code == status.HTTP_200_OK else f"Importing {verbose_name.lower()} should give a status {user_perm_expected_status}" + + if not (fails or user_perm_fails): assert response.json() == { "status": "success" }, f"{verbose_name} can not be imported with authentication" @@ -670,13 +808,13 @@ def compare_results( reference = authenticated_client.get(reference_url) assert ( reference.status_code == status.HTTP_200_OK - ), f"reference endpoint is not accessible with authentication" + ), f"reference endpoint is not accessible" for object in reference.json()["objects"]["framework"][ object_name.lower().replace(" ", "_") ][:count]: comparelist = authenticated_client.get(compare_url) - compare = None + compare = dict() assert ( comparelist.status_code == expected_status ), f"{object['name']} is not in {compare_url} results" diff --git a/backend/app_tests/conftest.py b/backend/app_tests/conftest.py index 452a051d3..ec31873a8 100644 --- a/backend/app_tests/conftest.py +++ b/backend/app_tests/conftest.py @@ -1,8 +1,13 @@ import pytest from rest_framework.test import APIClient +from api.test_utils import EndpointTestsUtils from iam.models import User, UserGroup from core.apps import startup +class Test(dict): + def __init__(self, *args, **kwargs): + super(Test, self).__init__(*args, **kwargs) + self.__dict__ = self @pytest.fixture def app_config(): @@ -24,3 +29,10 @@ def authenticated_client(app_config): client = APIClient() client.force_login(admin) return client + + +@pytest.fixture +def test(authenticated_client, request) -> Test: + """Get the elements used by the tests such as client and associated folder""" + client, folder = EndpointTestsUtils.get_test_client_and_folder(authenticated_client, request.param) + return Test({"client": client, "admin_client": authenticated_client, "folder": folder, "user_group": request.param}) \ No newline at end of file diff --git a/backend/app_tests/test_vars.py b/backend/app_tests/test_vars.py index a2bd354ca..41ba43555 100644 --- a/backend/app_tests/test_vars.py +++ b/backend/app_tests/test_vars.py @@ -1,4 +1,5 @@ from typing import Any +from core.apps import AUDITOR_PERMISSIONS_LIST, APPROVER_PERMISSIONS_LIST, ANALYST_PERMISSIONS_LIST, DOMAIN_MANAGER_PERMISSIONS_LIST, ADMINISTRATOR_PERMISSIONS_LIST # API endpoint to test COMPLIANCE_ASSESSMENTS_ENDPOINT = "compliance-assessments-list" @@ -28,6 +29,45 @@ TEST_RISK_MATRIX2_URN = "urn:intuitem:risk:library:critical_risk_matrix_5x5" TEST_REQUIREMENT_NODE_URN = "urn:intuitem:risk:req_node:nist-csf-1.1:rs.an-1" +TEST_USER_EMAIL = "user@tests.com" +GROUPS_PERMISSIONS = { + "BI-UG-ADM": { + "folder": "Global", + "name": "Global_administrator", + "perms": ADMINISTRATOR_PERMISSIONS_LIST + }, + "BI-UG-GAD": { + "folder": "Global", + "name": "Global_auditor", + "perms": AUDITOR_PERMISSIONS_LIST + }, + "BI-UG-GVA": { + "folder": "Global", + "name": "Global_validator", + "perms": APPROVER_PERMISSIONS_LIST + }, + "BI-UG-AUD": { + "folder": "test", + "name": "Auditor", + "perms": AUDITOR_PERMISSIONS_LIST + }, + "BI-UG-VAL": { + "folder": "test", + "name": "Validator", + "perms": APPROVER_PERMISSIONS_LIST + }, + "BI-UG-ANA": { + "folder": "test", + "name": "Analyst", + "perms": ANALYST_PERMISSIONS_LIST + }, + "BI-UG-DMA": { + "folder": "test", + "name": "Domain_Manager", + "perms": DOMAIN_MANAGER_PERMISSIONS_LIST + }, +} + __globals__ = globals() @@ -45,3 +85,12 @@ def get_var(varname: str) -> Any: f"The test_vars module doesn't contain any variable named '{varname}' !" ) return value + + +def get_singular_name(plural_name: str) -> str: + exceptions = { + "Libraries": "Library", + "Risk matrices": "Risk matrix", + "Policies": "Policy", + } + return exceptions.get(plural_name, plural_name[:-1] if plural_name.endswith("s") else plural_name) diff --git a/backend/core/apps.py b/backend/core/apps.py index 418b5ec74..d1a52b9a6 100644 --- a/backend/core/apps.py +++ b/backend/core/apps.py @@ -3,6 +3,226 @@ from ciso_assistant.settings import CISO_ASSISTANT_SUPERUSER_EMAIL import os +AUDITOR_PERMISSIONS_LIST = [ + "view_project", + "view_riskassessment", + "view_securitymeasure", + "view_policy", + "view_riskscenario", + "view_riskacceptance", + "view_asset", + "view_threat", + "view_securityfunction", + "view_folder", + "view_usergroup", + "view_riskmatrix", + "view_complianceassessment", + "view_requirementassessment", + "view_requirementnode", + "view_evidence", + "view_framework", + "view_library", +] + +APPROVER_PERMISSIONS_LIST = [ + "view_project", + "view_riskassessment", + "view_securitymeasure", + "view_policy", + "view_riskscenario", + "view_riskacceptance", + "approve_riskacceptance", + "view_asset", + "view_threat", + "view_securityfunction", + "view_folder", + "view_usergroup", + "view_riskmatrix", + "view_complianceassessment", + "view_requirementassessment", + "view_requirementnode", + "view_evidence", + "view_framework", + "view_library", +] + +ANALYST_PERMISSIONS_LIST = [ + "add_project", + "view_project", + "change_project", + "delete_project", + "add_riskassessment", + "view_riskassessment", + "change_riskassessment", + "delete_riskassessment", + "add_securitymeasure", + "view_securitymeasure", + "change_securitymeasure", + "delete_securitymeasure", + "add_policy", + "view_policy", + "change_policy", + "delete_policy", + "add_riskscenario", + "view_riskscenario", + "change_riskscenario", + "delete_riskscenario", + "add_riskacceptance", + "view_riskacceptance", + "change_riskacceptance", + "delete_riskacceptance", + "add_complianceassessment", + "view_complianceassessment", + "change_complianceassessment", + "delete_complianceassessment", + "view_requirementassessment", + "change_requirementassessment", + "add_evidence", + "view_evidence", + "change_evidence", + "delete_evidence", + "view_asset", + "view_threat", + "view_securityfunction", + "view_folder", + "view_usergroup", + "view_riskmatrix", + "view_requirementnode", + "view_framework", + "view_library", +] + +DOMAIN_MANAGER_PERMISSIONS_LIST = [ + "change_usergroup", + "view_usergroup", + "add_project", + "change_project", + "delete_project", + "view_project", + "add_riskassessment", + "view_riskassessment", + "change_riskassessment", + "delete_riskassessment", + "add_securitymeasure", + "view_securitymeasure", + "change_securitymeasure", + "delete_securitymeasure", + "add_policy", + "view_policy", + "change_policy", + "delete_policy", + "add_riskscenario", + "view_riskscenario", + "change_riskscenario", + "delete_riskscenario", + "add_riskacceptance", + "view_riskacceptance", + "change_riskacceptance", + "delete_riskacceptance", + "view_asset", + "view_threat", + "view_securityfunction", + "view_folder", + "change_folder", + "add_riskmatrix", + "view_riskmatrix", + "change_riskmatrix", + "delete_riskmatrix", + "add_complianceassessment", + "view_complianceassessment", + "change_complianceassessment", + "delete_complianceassessment", + "view_requirementassessment", + "change_requirementassessment", + "add_evidence", + "view_evidence", + "change_evidence", + "delete_evidence", + "view_requirementnode", + "view_framework", + "view_library", +] + +ADMINISTRATOR_PERMISSIONS_LIST = [ + "add_user", + "view_user", + "change_user", + "delete_user", + "add_usergroup", + "view_usergroup", + "change_usergroup", + "delete_usergroup", + "add_event", + "view_event", + "change_event", + "delete_event", + "add_asset", + "view_asset", + "change_asset", + "delete_asset", + "add_threat", + "view_threat", + "change_threat", + "delete_threat", + "add_securityfunction", + "view_securityfunction", + "change_securityfunction", + "delete_securityfunction", + "add_folder", + "change_folder", + "view_folder", + "delete_folder", + "add_project", + "change_project", + "delete_project", + "view_project", + "add_riskassessment", + "view_riskassessment", + "change_riskassessment", + "delete_riskassessment", + "add_securitymeasure", + "view_securitymeasure", + "change_securitymeasure", + "delete_securitymeasure", + "add_policy", + "view_policy", + "change_policy", + "delete_policy", + "add_riskscenario", + "view_riskscenario", + "change_riskscenario", + "delete_riskscenario", + "add_riskacceptance", + "view_riskacceptance", + "change_riskacceptance", + "delete_riskacceptance", + "approve_riskacceptance", + "add_riskmatrix", + "view_riskmatrix", + "change_riskmatrix", + "delete_riskmatrix", + "add_complianceassessment", + "view_complianceassessment", + "change_complianceassessment", + "delete_complianceassessment", + "view_requirementassessment", + "change_requirementassessment", + "add_evidence", + "view_evidence", + "change_evidence", + "delete_evidence", + "add_framework", + "view_framework", + "delete_framework", + "view_requirementnode", + "view_requirementlevel", # Permits to see the object on api by an admin + "view_library", + "add_library", + "delete_library", + "backup", + "restore", +] + def startup(**kwargs): """ Implement CISO Assistant 1.0 default Roles and User Groups during migrate @@ -14,232 +234,23 @@ def startup(**kwargs): print("startup handler: initialize database") auditor_permissions = Permission.objects.filter( - codename__in=[ - "view_project", - "view_riskassessment", - "view_securitymeasure", - "view_policy", - "view_riskscenario", - "view_riskacceptance", - "view_asset", - "view_threat", - "view_securityfunction", - "view_folder", - "view_usergroup", - "view_riskmatrix" "view_complianceassessment", - "view_requirementassessment", - "view_requirement", - "view_evidence", - "view_framework", - "view_library", - ] + codename__in=AUDITOR_PERMISSIONS_LIST ) approver_permissions = Permission.objects.filter( - codename__in=[ - "view_project", - "view_riskassessment", - "view_securitymeasure", - "view_policy", - "view_riskscenario", - "view_riskacceptance", - "approve_riskacceptance", - "view_asset", - "view_threat", - "view_securityfunction", - "view_folder", - "view_usergroup", - "view_riskmatrix", - "view_complianceassessment", - "view_requirementassessment", - "view_requirement", - "view_evidence", - "view_framework", - "view_library", - ] + codename__in=APPROVER_PERMISSIONS_LIST ) analyst_permissions = Permission.objects.filter( - codename__in=[ - "add_project", - "view_project", - "change_project", - "delete_project", - "add_riskassessment", - "view_riskassessment", - "change_riskassessment", - "delete_riskassessment", - "add_securitymeasure", - "view_securitymeasure", - "change_securitymeasure", - "delete_securitymeasure", - "add_policy", - "view_policy", - "change_policy", - "delete_policy", - "add_riskscenario", - "view_riskscenario", - "change_riskscenario", - "delete_riskscenario", - "add_riskacceptance", - "view_riskacceptance", - "change_riskacceptance", - "delete_riskacceptance", - "add_complianceassessment", - "view_complianceassessment", - "change_complianceassessment", - "delete_complianceassessment", - "view_requirementassessment", - "change_requirementassessment", - "add_evidence", - "view_evidence", - "change_evidence", - "delete_evidence", - "view_asset", - "view_threat", - "view_securityfunction", - "view_folder", - "view_usergroup", - "view_riskmatrix", - "view_requirement", - "view_framework", - "view_library", - ] + codename__in=ANALYST_PERMISSIONS_LIST ) domain_manager_permissions = Permission.objects.filter( - codename__in=[ - "change_usergroup", - "view_usergroup", - "add_project", - "change_project", - "delete_project", - "view_project", - "add_riskassessment", - "view_riskassessment", - "change_riskassessment", - "delete_riskassessment", - "add_securitymeasure", - "view_securitymeasure", - "change_securitymeasure", - "delete_securitymeasure", - "add_policy", - "view_policy", - "change_policy", - "delete_policy", - "add_riskscenario", - "view_riskscenario", - "change_riskscenario", - "delete_riskscenario", - "add_riskacceptance", - "view_riskacceptance", - "change_riskacceptance", - "delete_riskacceptance", - "view_asset", - "view_threat", - "view_securityfunction", - "view_folder", - "change_folder", - "add_riskmatrix", - "view_riskmatrix", - "change_riskmatrix", - "delete_riskmatrix", - "add_complianceassessment", - "view_complianceassessment", - "change_complianceassessment", - "delete_complianceassessment", - "view_requirementassessment", - "change_requirementassessment", - "add_evidence", - "view_evidence", - "change_evidence", - "delete_evidence", - "view_requirement", - "view_framework", - "view_library", - ] + codename__in=DOMAIN_MANAGER_PERMISSIONS_LIST ) administrator_permissions = Permission.objects.filter( - codename__in=[ - "add_user", - "view_user", - "change_user", - "delete_user", - "add_usergroup", - "view_usergroup", - "change_usergroup", - "delete_usergroup", - "add_event", - "view_event", - "change_event", - "delete_event", - "add_asset", - "view_asset", - "change_asset", - "delete_asset", - "add_threat", - "view_threat", - "change_threat", - "delete_threat", - "add_securityfunction", - "view_securityfunction", - "change_securityfunction", - "delete_securityfunction", - "add_folder", - "change_folder", - "view_folder", - "delete_folder", - "add_project", - "change_project", - "delete_project", - "view_project", - "add_riskassessment", - "view_riskassessment", - "change_riskassessment", - "delete_riskassessment", - "add_securitymeasure", - "view_securitymeasure", - "change_securitymeasure", - "delete_securitymeasure", - "add_policy", - "view_policy", - "change_policy", - "delete_policy", - "add_riskscenario", - "view_riskscenario", - "change_riskscenario", - "delete_riskscenario", - "add_riskacceptance", - "view_riskacceptance", - "change_riskacceptance", - "delete_riskacceptance", - "approve_riskacceptance", - "add_riskmatrix", - "view_riskmatrix", - "change_riskmatrix", - "delete_riskmatrix", - "add_complianceassessment", - "view_complianceassessment", - "change_complianceassessment", - "delete_complianceassessment", - "view_requirementassessment", - "change_requirementassessment", - "add_evidence", - "view_evidence", - "change_evidence", - "delete_evidence", - "add_framework", - "view_framework", - "delete_framework", - "view_requirementnode", - "view_requirementlevel", # Permits to see the object on api by an admin - "view_library", - "add_library", - "delete_library", - "backup", - "restore", - ] + codename__in=ADMINISTRATOR_PERMISSIONS_LIST ) # if root folder does not exist, then create it diff --git a/backend/core/serializers.py b/backend/core/serializers.py index 1c8c0e377..49a920cf6 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -5,6 +5,7 @@ from iam.models import * from rest_framework import serializers +from rest_framework.exceptions import PermissionDenied from django.contrib.auth import get_user_model from django.db import models from core.serializer_fields import FieldsRelatedField @@ -36,7 +37,7 @@ def create(self, validated_data: Any): folder=folder, ) if not can_create_in_folder: - raise serializers.ValidationError( + raise PermissionDenied( { "folder": "You do not have permission to create objects in this folder" } diff --git a/backend/pytest.ini b/backend/pytest.ini index 0c8d5e8d8..cd7dc895f 100644 --- a/backend/pytest.ini +++ b/backend/pytest.ini @@ -1,6 +1,7 @@ # pytest.ini [pytest] DJANGO_SETTINGS_MODULE = ciso_assistant.settings +generate_report_on_test=True # -- recommended but optional: python_files = *_test.py test_*.py addopts = -p no:warnings diff --git a/backend/requirements.txt b/backend/requirements.txt index 40dbc4fd1..0e716e4a0 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,6 +3,7 @@ weasyprint==60.2 psycopg2-binary==2.9.9 gunicorn==21.2.0 pytest-django==4.8.0 +pytest-html==4.1.1 django-filter==23.5 whitenoise==6.6.0 argon2-cffi==23.1.0