From e5e445bb4e522d92e5dedcaec2b7d81f5f939d79 Mon Sep 17 00:00:00 2001 From: Nikolay Proskurin Date: Fri, 24 Nov 2023 00:16:53 +0100 Subject: [PATCH] BI-4885: add basic RLS tests --- .../db/control_api/test_rls.py | 58 +++++++++++++++++++ .../dl_api_lib_testing/app.py | 52 +++++++++++++---- .../dl_api_lib_testing/rls.py | 4 ++ .../rls_configs/missing_login_updated | 2 +- .../rls_configs/missing_login_updated.json | 2 +- .../test_data/rls_configs/simple_updated | 2 +- .../test_data/rls_configs/simple_updated.json | 2 +- 7 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 lib/dl_api_lib/dl_api_lib_tests/db/control_api/test_rls.py diff --git a/lib/dl_api_lib/dl_api_lib_tests/db/control_api/test_rls.py b/lib/dl_api_lib/dl_api_lib_tests/db/control_api/test_rls.py new file mode 100644 index 000000000..078144e34 --- /dev/null +++ b/lib/dl_api_lib/dl_api_lib_tests/db/control_api/test_rls.py @@ -0,0 +1,58 @@ +import pytest + +from dl_api_lib_testing.rls import ( + RLS_CONFIG_CASES, + config_to_comparable, + load_rls_config, +) +from dl_api_lib_tests.db.base import DefaultApiTestBase + + +class TestDataset(DefaultApiTestBase): + @staticmethod + def add_rls_to_dataset(control_api, dataset, rls_config): + field_guid = dataset.result_schema[0].id + dataset.rls = {field_guid: rls_config} + resp = control_api.save_dataset(dataset, fail_ok=True) + return field_guid, resp + + @pytest.mark.parametrize("case", RLS_CONFIG_CASES, ids=[c["name"] for c in RLS_CONFIG_CASES]) + def test_create_and_update_rls(self, control_api, saved_dataset, case): + config = case["config"] + ds = saved_dataset + field_guid, rls_resp = self.add_rls_to_dataset(control_api, ds, config) + assert rls_resp.status_code == 200, rls_resp.json + + resp = control_api.load_dataset(ds) + assert resp.status_code == 200, resp.json + ds = resp.dataset + assert config_to_comparable(ds.rls[field_guid]) == config_to_comparable(case["config_to_compare"]) + + config_updated = case.get("config_updated") + if config_updated is None: + return + field_guid, rls_resp = self.add_rls_to_dataset(control_api, ds, config_updated) + assert rls_resp.status_code == 200, rls_resp.json + + resp = control_api.load_dataset(ds) + assert resp.status_code == 200, resp.json + assert config_to_comparable(resp.dataset.rls[field_guid]) == config_to_comparable(config_updated) + + def test_create_rls_for_nonexistent_user(self, control_api, saved_dataset): + config = load_rls_config("bad_login") + ds = saved_dataset + field_guid, rls_resp = self.add_rls_to_dataset(control_api, ds, config) + assert rls_resp.status_code == 200, rls_resp.json + + resp = control_api.load_dataset(ds) + assert resp.status_code == 200, resp.json + assert "!FAILED_robot-user2" in resp.dataset.rls[field_guid] + + def test_create_rls_from_invalid_config(self, control_api, saved_dataset): + config = load_rls_config("bad") + _, rls_resp = self.add_rls_to_dataset(control_api, saved_dataset, config) + + assert rls_resp.status_code == 400 + assert rls_resp.bi_status_code == "ERR.DS_API.RLS.PARSE" + assert rls_resp.json["message"] == "RLS: Parsing failed at line 2" + assert rls_resp.json["details"] == {"description": "Wrong format"} diff --git a/lib/dl_api_lib_testing/dl_api_lib_testing/app.py b/lib/dl_api_lib_testing/dl_api_lib_testing/app.py index 307c5635a..9daa920f7 100644 --- a/lib/dl_api_lib_testing/dl_api_lib_testing/app.py +++ b/lib/dl_api_lib_testing/dl_api_lib_testing/app.py @@ -13,10 +13,7 @@ from dl_api_lib.app.control_api.app import EnvSetupResult as ControlApiEnvSetupResult from dl_api_lib.app.data_api.app import DataApiAppFactory from dl_api_lib.app.data_api.app import EnvSetupResult as DataApiEnvSetupResult -from dl_api_lib.app_common import ( - SRFactoryBuilder, - StandaloneServiceRegistryFactory, -) +from dl_api_lib.app_common import SRFactoryBuilder from dl_api_lib.app_common_settings import ConnOptionsMutatorsFactory from dl_api_lib.app_settings import ( AppSettings, @@ -27,26 +24,33 @@ from dl_api_lib.connector_availability.base import ConnectorAvailabilityConfig from dl_api_lib_testing.configuration import ApiTestEnvironmentConfiguration from dl_configs.connectors_settings import ConnectorSettingsBase -from dl_configs.enums import ( - RedisMode, - RequiredService, -) +from dl_configs.enums import RequiredService from dl_configs.rqe import ( RQEBaseURL, RQEConfig, ) -from dl_configs.settings_submodels import RedisSettings from dl_constants.enums import ( ConnectionType, + RLSSubjectType, USAuthMode, ) from dl_core.aio.middlewares.services_registry import services_registry_middleware from dl_core.aio.middlewares.us_manager import service_us_manager_middleware from dl_core.data_processing.cache.primitives import CacheTTLConfig +from dl_core.rls import ( + RLS_FAILED_USER_NAME_PREFIX, + BaseSubjectResolver, + RLSSubject, +) +from dl_core.services_registry import ServicesRegistry from dl_core.services_registry.entity_checker import EntityUsageChecker from dl_core.services_registry.env_manager_factory_base import EnvManagerFactory -from dl_core.services_registry.inst_specific_sr import InstallationSpecificServiceRegistryFactory +from dl_core.services_registry.inst_specific_sr import ( + InstallationSpecificServiceRegistry, + InstallationSpecificServiceRegistryFactory, +) from dl_core.services_registry.rqe_caches import RQECachesSetting +from dl_core.utils import FutureRef from dl_core_testing.app_test_workarounds import TestEnvManagerFactory from dl_core_testing.fixture_server_runner import WSGIRunner @@ -98,6 +102,32 @@ def rqe_config_subprocess_cm(self) -> Generator[RQEConfig, None, None]: ) +@attr.s +class TestingSubjectResolver(BaseSubjectResolver): + def get_subjects_by_names(self, names: list[str]) -> list[RLSSubject]: + """Mock resolver. Considers a user real if his name starts with 'user'""" + return [ + RLSSubject( + subject_id="", + subject_type=RLSSubjectType.user if name.startswith("user") else RLSSubjectType.notfound, + subject_name=name if name.startswith("user") else RLS_FAILED_USER_NAME_PREFIX + name, + ) + for name in names + ] + + +@attr.s +class TestingServiceRegistry(InstallationSpecificServiceRegistry): + async def get_subject_resolver(self) -> BaseSubjectResolver: + return TestingSubjectResolver() + + +@attr.s +class TestingServiceRegistryFactory(InstallationSpecificServiceRegistryFactory): + def get_inst_specific_sr(self, sr_ref: FutureRef[ServicesRegistry]) -> TestingServiceRegistry: + return TestingServiceRegistry(service_registry_ref=sr_ref) + + class TestingSRFactoryBuilder(SRFactoryBuilder[AppSettings]): def _get_required_services(self, settings: AppSettings) -> set[RequiredService]: return {RequiredService.RQE_INT_SYNC, RequiredService.RQE_EXT_SYNC} @@ -109,7 +139,7 @@ def _get_inst_specific_sr_factory( self, settings: AppSettings, ) -> Optional[InstallationSpecificServiceRegistryFactory]: - return StandaloneServiceRegistryFactory() + return TestingServiceRegistryFactory() def _get_entity_usage_checker(self, settings: AppSettings) -> Optional[EntityUsageChecker]: return None diff --git a/lib/dl_api_lib_testing/dl_api_lib_testing/rls.py b/lib/dl_api_lib_testing/dl_api_lib_testing/rls.py index 15234d0de..00b77206e 100644 --- a/lib/dl_api_lib_testing/dl_api_lib_testing/rls.py +++ b/lib/dl_api_lib_testing/dl_api_lib_testing/rls.py @@ -65,6 +65,10 @@ def load_rls(name: str) -> list[RLSEntry]: MAIN_TEST_CASE = RLS_CONFIG_CASES[0] +def config_to_comparable(conf: str): + return set((line.split(": ")[0], ",".join(sorted(line.split(": ")[1]))) for line in conf.strip().split("\n")) + + def check_text_config_to_rls_entries(case: dict, subject_resolver: BaseSubjectResolver) -> None: field_guid, config, expected_rls_entries = case["field_guid"], case["config"], case["rls_entries"] entries = FieldRLSSerializer.from_text_config(config, field_guid, subject_resolver=subject_resolver) diff --git a/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/missing_login_updated b/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/missing_login_updated index 488798cc4..55501e1e7 100644 --- a/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/missing_login_updated +++ b/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/missing_login_updated @@ -1,3 +1,3 @@ 'Москва': user2, user1 'Самара': user3, !FAILED_someuser, user1 -'Омск': user5, pg +'Омск': user5, user7 diff --git a/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/missing_login_updated.json b/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/missing_login_updated.json index 1f058217f..71674aa91 100644 --- a/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/missing_login_updated.json +++ b/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/missing_login_updated.json @@ -52,7 +52,7 @@ "subject": { "subject_id": "1120000000000251", "subject_type": "user", - "subject_name": "pg" + "subject_name": "user7" } } ] diff --git a/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/simple_updated b/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/simple_updated index 7ab13577f..678a18b59 100644 --- a/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/simple_updated +++ b/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/simple_updated @@ -1,3 +1,3 @@ 'Москва': user2 'Самара': user1, user3 -'Омск': user5, pg +'Омск': user5, user7 diff --git a/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/simple_updated.json b/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/simple_updated.json index 2fdf870ec..aa287c3f2 100644 --- a/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/simple_updated.json +++ b/lib/dl_api_lib_testing/dl_api_lib_testing/test_data/rls_configs/simple_updated.json @@ -36,7 +36,7 @@ "subject": { "subject_id": "1120000000000251", "subject_type": "user", - "subject_name": "pg" + "subject_name": "user7" } } ]