diff --git a/.conf/.env.example b/.conf/.env.example index 902b60ee..5b917f87 100644 --- a/.conf/.env.example +++ b/.conf/.env.example @@ -1,7 +1,7 @@ SERVER_VERSION=prod DEBUG=1 ADMINS= - +META_SECURITY_PSEUDED=meta.security=PSEUDED ################## ENVIRONMENT ############################################################# INCLUDED_APPS=accesses,cohort,workspaces,exports diff --git a/.conf/.test.env b/.conf/.test.env index 550e7f7a..17a28748 100644 --- a/.conf/.test.env +++ b/.conf/.test.env @@ -50,7 +50,7 @@ OIDC_CLIENT_ID= OIDC_CLIENT_SECRET= OIDC_GRANT_TYPE= OIDC_REDIRECT_URI= - +META_SECURITY_PSEUDED=meta.security=PSEUDED ################## ACCESSES ################################################################# ACCESS_EXPIRY_FIRST_ALERT_IN_DAYS=30 ACCESS_EXPIRY_SECOND_ALERT_IN_DAYS=2 diff --git a/accesses/tools/perimeter_process.py b/accesses/tools/perimeter_process.py index 5e00e1be..3601493f 100644 --- a/accesses/tools/perimeter_process.py +++ b/accesses/tools/perimeter_process.py @@ -205,7 +205,7 @@ def get_perimeters_filtered_by_search(cohort_ids, owner_id, default_perimeters): return default_perimeters -def get_read_nominative_boolean_from_specific_logic_function(request, filter_queryset, +def get_read_nominative_boolean_from_specific_logic_function(perimeters_filtered_by_search, filter_queryset, all_read_patient_nominative_accesses, all_read_patient_pseudo_accesses, right_perimeter_compute_function) -> bool: @@ -215,9 +215,6 @@ def get_read_nominative_boolean_from_specific_logic_function(request, filter_que The right_perimeter_compute_function can be used to find right for all cohorts in "is-read-patient-pseudo" or at least on one perimeter in "is-one-read-patient-right" """ - - perimeters_filtered_by_search = get_perimeters_filtered_by_search(request.query_params.get("cohort_id"), - request.user, filter_queryset) if not perimeters_filtered_by_search: raise Http404("ERROR No Perimeters Found") return right_perimeter_compute_function(perimeters_filtered_by_search, diff --git a/accesses/views/perimeter.py b/accesses/views/perimeter.py index aed57724..814b41bd 100644 --- a/accesses/views/perimeter.py +++ b/accesses/views/perimeter.py @@ -21,7 +21,7 @@ get_top_perimeter_from_read_patient_accesses, is_pseudo_perimeter_in_top_perimeter, \ has_at_least_one_read_nominative_right, \ get_read_nominative_boolean_from_specific_logic_function, get_all_read_patient_accesses, \ - get_read_opposing_patient_accesses + get_read_opposing_patient_accesses, get_perimeters_filtered_by_search class PerimeterFilter(filters.FilterSet): @@ -147,12 +147,14 @@ def get_read_patient_pseudo_right(self, request, *args, **kwargs): request.user) is_opposing_patient_read = get_read_opposing_patient_accesses(request.user) if request.query_params: - is_read_patient_nominative = get_read_nominative_boolean_from_specific_logic_function(request, - self.filter_queryset( - self.get_queryset()), - all_read_patient_nominative_accesses, - all_read_patient_pseudo_accesses, - get_read_patient_right) + perimeters_filtered_by_search = get_perimeters_filtered_by_search(request.query_params.get("cohort_id"), + request.user, + self.filter_queryset(self.get_queryset())) + is_read_patient_nominative = get_read_nominative_boolean_from_specific_logic_function( + perimeters_filtered_by_search, + all_read_patient_nominative_accesses, + all_read_patient_pseudo_accesses, + get_read_patient_right) is_read_patient_pseudo = not is_read_patient_nominative else: is_read_patient_pseudo = is_pseudo_perimeter_in_top_perimeter(all_read_patient_nominative_accesses, @@ -172,12 +174,14 @@ def get_read_one_nominative_patient_right_access(self, request, *args, **kwargs) request.user) is_opposing_patient_read = get_read_opposing_patient_accesses(request.user) if request.query_params: - is_read_patient_nominative = get_read_nominative_boolean_from_specific_logic_function(request, - self.filter_queryset( - self.get_queryset()), - all_read_patient_nominative_accesses, - all_read_patient_pseudo_accesses, - has_at_least_one_read_nominative_right) + perimeters_filtered_by_search = get_perimeters_filtered_by_search(request.query_params.get("cohort_id"), + request.user, + self.filter_queryset(self.get_queryset())) + is_read_patient_nominative = get_read_nominative_boolean_from_specific_logic_function( + perimeters_filtered_by_search, + all_read_patient_nominative_accesses, + all_read_patient_pseudo_accesses, + has_at_least_one_read_nominative_right) return Response(data={"is_one_read_nominative_patient_right": is_read_patient_nominative, "is_opposing_patient_read": is_opposing_patient_read}, status=status.HTTP_200_OK) diff --git a/admin_cohort/auth/utils.py b/admin_cohort/auth/utils.py index 6e4ae854..d49f0cc0 100644 --- a/admin_cohort/auth/utils.py +++ b/admin_cohort/auth/utils.py @@ -138,6 +138,26 @@ def get_token_issuer(token: str) -> str: return issuer +def get_user_from_token(token: str, auth_method: str) -> Union[None, User]: + if auth_method == JWT_AUTH_MODE: + try: + decoded = jwt.decode(token, key=JWT_SIGNING_KEY, algorithms=JWT_ALGORITHMS, leeway=15) + return User.objects.get(pk=decoded["username"]) + + except User.DoesNotExist as e: + raise ServerError(f"Error verifying token. User not found - {e}") + elif auth_method == OIDC_AUTH_MODE: + try: + issuer = get_token_issuer(token=token) + decoded = decode_oidc_token(token=token, issuer=issuer) + return User.objects.get(pk=decoded["preferred_username"]) + except Exception as e: + _logger.info(f"Error decoding token: {e} - `{token}`") + raise e + else: + raise ValueError(f"Invalid authentication method : {auth_method}") + + def get_userinfo_from_token(token: str, auth_method: str) -> Union[None, UserInfo]: if token == env("ETL_TOKEN"): _logger.info("ETL token connexion") diff --git a/cohort/crb/cohort_requests/abstract_cohort_request.py b/cohort/crb/cohort_requests/abstract_cohort_request.py index fa083d12..401f550c 100644 --- a/cohort/crb/cohort_requests/abstract_cohort_request.py +++ b/cohort/crb/cohort_requests/abstract_cohort_request.py @@ -3,7 +3,9 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from admin_cohort.auth.utils import get_userinfo_from_token +from accesses.tools.perimeter_process import get_all_read_patient_accesses, \ + get_read_nominative_boolean_from_specific_logic_function, get_read_patient_right +from admin_cohort.auth.utils import get_userinfo_from_token, get_user_from_token from cohort.crb.enums import Mode from cohort.crb.exceptions import FhirException from cohort.crb.query_formatter import QueryFormatter @@ -14,6 +16,17 @@ from cohort.crb.schemas import CohortQuery +def is_cohort_request_pseudo_read(auth_headers: dict, source_population: list) -> bool: + user = get_user_from_token(auth_headers['Authorization'].replace('Bearer ', ''), + auth_headers['authorizationMethod']) + all_read_patient_nominative_accesses, all_read_patient_pseudo_accesses = get_all_read_patient_accesses( + user) + return not get_read_nominative_boolean_from_specific_logic_function(source_population, + all_read_patient_nominative_accesses, + all_read_patient_pseudo_accesses, + get_read_patient_right) + + class AbstractCohortRequest(ABC): def __init__(self, mode: Mode, sjs_client: SjsClient, auth_headers: dict): self.mode = mode @@ -33,7 +46,10 @@ def create_request_for_sjs(self, cohort_query: CohortQuery) -> str: if cohort_query is None: raise FhirException("No query received to format.") - sjs_request = QueryFormatter(self.auth_headers).format_to_fhir(cohort_query) + is_pseudo = is_cohort_request_pseudo_read(self.auth_headers, + cohort_query.source_population.care_site_cohort_list) + + sjs_request = QueryFormatter(self.auth_headers).format_to_fhir(cohort_query, is_pseudo) cohort_query.criteria = sjs_request spark_job_request = SparkJobObject( diff --git a/cohort/crb/query_formatter.py b/cohort/crb/query_formatter.py index 0466a68b..75d8ad2b 100644 --- a/cohort/crb/query_formatter.py +++ b/cohort/crb/query_formatter.py @@ -14,7 +14,9 @@ if TYPE_CHECKING: from cohort.crb.schemas import CohortQuery, Criteria, SourcePopulation -FHIR_URL = os.environ.get("FHIR_URL") +env = os.environ +FHIR_URL = env.get("FHIR_URL") +META_SECURITY_PSEUDED = env.get("META_SECURITY_PSEUDED") _logger = logging.getLogger("info") @@ -36,20 +38,25 @@ def query_fhir(resource: str, params: dict[str, list[str]], auth_headers: dict) return FhirParameters(**result) +def add_security_params_to_filter_fhir(sub_criteria: Criteria, source_population: SourcePopulation, is_pseudo: bool): + filter_fhir_enriched = sub_criteria.add_criteria(source_population) + return META_SECURITY_PSEUDED + "&" + filter_fhir_enriched if is_pseudo else filter_fhir_enriched + + class QueryFormatter: IDENTIFIER_VALUE = "identifier.value" def __init__(self, auth_headers: dict): self.auth_headers = auth_headers - def format_to_fhir(self, cohort_query: CohortQuery) -> Criteria | None: + def format_to_fhir(self, cohort_query: CohortQuery, is_pseudo: bool) -> Criteria | None: def build_solr_criteria(criteria: Criteria, source_population: SourcePopulation) -> Criteria | None: if criteria is None: return None for sub_criteria in criteria.criteria: if sub_criteria.criteria_type == CriteriaType.BASIC_RESOURCE: - filter_fhir_enriched = sub_criteria.add_criteria(source_population) + filter_fhir_enriched = add_security_params_to_filter_fhir(sub_criteria, source_population, is_pseudo) _logger.info(f"filterFhirEnriched {filter_fhir_enriched}") diff --git a/cohort/tests/test_crb.py b/cohort/tests/test_crb.py index e069ce3a..6eb77253 100644 --- a/cohort/tests/test_crb.py +++ b/cohort/tests/test_crb.py @@ -61,7 +61,17 @@ def load_query(filename: str) -> CohortQuery: @mock.patch("cohort.crb.query_formatter.query_fhir") def test_format_to_fhir_simple_query(self, query_fhir): query_fhir.return_value = self.mocked_query_fhir_result - res = self.query_formatter.format_to_fhir(self.cohort_query_simple) + res = self.query_formatter.format_to_fhir(self.cohort_query_simple, False) + self.assertEquals(1, len(res.criteria)) + res_criteria = res.criteria[0] + self.assertEquals(ResourceType.PATIENT, res_criteria.resource_type) + self.assertEquals(self.fq_value_string, res_criteria.filter_solr, ) + self.assertEquals("docstatus=final&type:not=doc-impor&empty=false&patient-active=true&_text=ok", + res_criteria.filter_fhir) + @mock.patch("cohort.crb.query_formatter.query_fhir") + def test_format_to_fhir_simple_query_pseudo(self, query_fhir): + query_fhir.return_value = self.mocked_query_fhir_result + res = self.query_formatter.format_to_fhir(self.cohort_query_simple, True) self.assertEquals(1, len(res.criteria)) res_criteria = res.criteria[0] self.assertEquals(ResourceType.PATIENT, res_criteria.resource_type) @@ -72,7 +82,7 @@ def test_format_to_fhir_simple_query(self, query_fhir): @mock.patch("cohort.crb.query_formatter.query_fhir") def test_format_to_fhir_complex_query(self, query_fhir): query_fhir.return_value = self.mocked_query_fhir_result - res = self.query_formatter.format_to_fhir(self.cohort_query_complex) + res = self.query_formatter.format_to_fhir(self.cohort_query_complex, False) self.assertEquals(6, len(res.criteria)) res_criteria = res.criteria[1] self.assertEquals(ResourceType.PATIENT, res_criteria.resource_type) diff --git a/requirements.txt b/requirements.txt index 4fdc5a95..a4da942c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -49,7 +49,7 @@ packaging==22.0 prompt-toolkit==3.0.36 psycopg2-binary==2.9.5 pycparser==2.21 -pydantic==2.3.0 +pydantic==2.4.0 PyJWT==2.6.0 pyspnego==0.7.0 python-json-logger==2.0.7