Skip to content

Commit dfb2314

Browse files
committed
update
1 parent c10fccd commit dfb2314

File tree

6 files changed

+86
-97
lines changed

6 files changed

+86
-97
lines changed

backend/src/fhir_controller.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
)
3636
from models.utils.generic_utils import check_keys_in_sources
3737
from models.utils.permissions import get_supplier_permissions
38-
from models.utils.permission_checker import VaccinePermissionChecker
38+
from models.utils.permission_checker import ApiOperationCode, validate_permissions, _expand_permissions
3939
from pds_service import PdsService
4040
from parameter_parser import process_params, process_search_params, create_query_string
4141
import urllib.parse
@@ -271,11 +271,8 @@ def update_immunization(self, aws_event):
271271
# Validate if the imms resource does not exist - end
272272

273273
# Check vaccine type permissions on the existing record - start
274-
try:
275-
checker = VaccinePermissionChecker(imms_vax_type_perms)
276-
checker.validate(existing_record["VaccineType"], "update")
277-
except UnauthorizedVaxOnRecordError as unauthorized:
278-
return self.create_response(403, unauthorized.to_operation_outcome())
274+
if not validate_permissions(imms_vax_type_perms, ApiOperationCode.UPDATE, [existing_record["VaccineType"]]):
275+
return self.create_response(403, UnauthorizedVaxOnRecordError().to_operation_outcome())
279276
# Check vaccine type permissions on the existing record - end
280277

281278
existing_resource_version = int(existing_record["Version"])
@@ -428,11 +425,15 @@ def search_immunizations(self, aws_event: APIGatewayProxyEventV1) -> dict:
428425
return self.create_response(403, unauthorized.to_operation_outcome())
429426
# Check vaxx type permissions on the existing record - start
430427
try:
431-
checker = VaccinePermissionChecker(imms_vax_type_perms)
432-
vax_type_perms = checker.expanded_permissions
433-
operation_code = VaccinePermissionChecker.mapped_operations.get("search")
434-
vax_type_perm = [ vaccine_type for vaccine_type in search_params.immunization_targets
435-
if f"{vaccine_type.lower()}.{operation_code}" in vax_type_perms ]
428+
expanded_permissions = _expand_permissions(imms_vax_type_perms)
429+
vax_type_perm = [
430+
vaccine_type
431+
for vaccine_type in search_params.immunization_targets
432+
if ApiOperationCode.SEARCH in expanded_permissions.get(vaccine_type.lower(), [])
433+
]
434+
# vax_type_perms = _expand_permissions(imms_vax_type_perms, ApiOperationCode.SEARCH)
435+
# vax_type_perm = [ vaccine_type for vaccine_type in search_params.immunization_targets
436+
# if f"{vaccine_type.lower()}.{ApiOperationCode.SEARCH}" in vax_type_perms ]
436437
if not vax_type_perm:
437438
raise UnauthorizedVaxError
438439
except UnauthorizedVaxError as unauthorized:

backend/src/fhir_repository.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import boto3
99
import botocore.exceptions
1010
from boto3.dynamodb.conditions import Attr, Key
11-
from models.utils.permission_checker import VaccinePermissionChecker
11+
from models.utils.permission_checker import ApiOperationCode, validate_permissions
1212
from botocore.config import Config
1313
from models.errors import (
1414
ResourceNotFoundError,
@@ -95,8 +95,8 @@ def get_immunization_by_identifier(
9595
item = response["Items"][0]
9696
resp = dict()
9797
vaccine_type = self._vaccine_type(item["PatientSK"])
98-
checker = VaccinePermissionChecker(imms_vax_type_perms)
99-
checker.validate(vaccine_type, "search")
98+
if not validate_permissions(imms_vax_type_perms,ApiOperationCode.SEARCH, [vaccine_type]):
99+
raise UnauthorizedVaxError()
100100
resource = json.loads(item["Resource"])
101101
resp["id"] = resource.get("id")
102102
resp["version"] = int(response["Items"][0]["Version"])
@@ -112,17 +112,17 @@ def get_immunization_by_id(self, imms_id: str, imms_vax_type_perms: str) -> Opti
112112
if "DeletedAt" in response["Item"]:
113113
if response["Item"]["DeletedAt"] == "reinstated":
114114
vaccine_type = self._vaccine_type(response["Item"]["PatientSK"])
115-
checker = VaccinePermissionChecker(imms_vax_type_perms)
116-
checker.validate(vaccine_type, "read")
115+
if not validate_permissions(imms_vax_type_perms,ApiOperationCode.READ, [vaccine_type]):
116+
raise UnauthorizedVaxError()
117117
resp["Resource"] = json.loads(response["Item"]["Resource"])
118118
resp["Version"] = response["Item"]["Version"]
119119
return resp
120120
else:
121121
return None
122122
else:
123123
vaccine_type = self._vaccine_type(response["Item"]["PatientSK"])
124-
checker = VaccinePermissionChecker(imms_vax_type_perms)
125-
checker.validate(vaccine_type, "read")
124+
if not validate_permissions(imms_vax_type_perms,ApiOperationCode.READ, [vaccine_type]):
125+
raise UnauthorizedVaxError()
126126
resp["Resource"] = json.loads(response["Item"]["Resource"])
127127
resp["Version"] = response["Item"]["Version"]
128128
return resp
@@ -168,8 +168,8 @@ def create_immunization(
168168
new_id = str(uuid.uuid4())
169169
immunization["id"] = new_id
170170
attr = RecordAttributes(immunization, patient)
171-
checker = VaccinePermissionChecker(imms_vax_type_perms)
172-
checker.validate(attr.vaccine_type, "create")
171+
if not validate_permissions(imms_vax_type_perms,ApiOperationCode.CREATE, [attr.vaccine_type]):
172+
raise UnauthorizedVaxError()
173173
query_response = _query_identifier(self.table, "IdentifierGSI", "IdentifierPK", attr.identifier)
174174

175175
if query_response is not None:
@@ -270,8 +270,7 @@ def update_reinstated_immunization(
270270
)
271271

272272
def _handle_permissions(self, imms_vax_type_perms: list[str], attr: RecordAttributes):
273-
checker = VaccinePermissionChecker(imms_vax_type_perms)
274-
checker.validate(attr.vaccine_type, "update")
273+
validate_permissions(imms_vax_type_perms, ApiOperationCode.UPDATE, [attr.vaccine_type])
275274

276275
def _build_update_expression(self, is_reinstate: bool) -> str:
277276
if is_reinstate:
@@ -367,8 +366,8 @@ def delete_immunization(
367366
if resp["Item"]["DeletedAt"] == "reinstated":
368367
pass
369368
vaccine_type = self._vaccine_type(resp["Item"]["PatientSK"])
370-
checker = VaccinePermissionChecker(imms_vax_type_perms)
371-
checker.validate(vaccine_type, "delete")
369+
if not validate_permissions(imms_vax_type_perms, ApiOperationCode.DELETE, [vaccine_type]):
370+
raise UnauthorizedVaxError()
372371

373372
response = self.table.update_item(
374373
Key={"PK": _make_immunization_pk(imms_id)},
Lines changed: 28 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,29 @@
1-
import json
2-
from clients import redis_client
3-
from models.errors import UnauthorizedVaxOnRecordError
4-
5-
6-
class VaccinePermissionChecker:
7-
"""Centralized vaccine permission checker."""
8-
9-
mapped_operations = {
10-
"create": "c",
11-
"read": "r",
12-
"update": "u",
13-
"delete": "d",
14-
"search": "s"
15-
}
16-
17-
def __init__(self, supplier_system: str):
18-
self.supplier_permissions = supplier_system
19-
self.expanded_permissions = self._expand_permissions(self.supplier_permissions)
20-
21-
# Expand permissions from the supplier's permission list
22-
@staticmethod
23-
def _expand_permissions(supplier_permissions: list[str]) -> set[str]:
24-
expanded = set()
25-
for permissions in supplier_permissions:
26-
if '.' not in permissions:
27-
continue # skip invalid format
28-
vaccine_type, allowed_operations = permissions.split('.', 1)
1+
from enum import StrEnum
2+
3+
class ApiOperationCode(StrEnum):
4+
CREATE = "c"
5+
READ = "r"
6+
UPDATE = "u"
7+
DELETE = "d"
8+
SEARCH = "s"
9+
10+
def _expand_permissions(permissions: list[str]) -> dict[str, list[ApiOperationCode]]:
11+
expanded_permissions = {}
12+
for permission in permissions:
13+
vaccine_type, operation_codes_str = permission.split(".", maxsplit=1)
2914
vaccine_type = vaccine_type.lower()
30-
for operation in allowed_operations.lower():
31-
if operation not in {'c', 'r', 'u', 'd', 's'}:
32-
raise ValueError(f"Unknown operation code: {operation} in a permission {permissions}")
33-
expanded.add(f"{vaccine_type}.{operation}")
34-
return expanded
35-
36-
# Check if the requested permission is a subset of the expanded permissions
37-
def _vaccine_permission(self, vaccine_type, operation) -> set:
38-
39-
operation = self.mapped_operations.get(operation.lower())
40-
if not operation:
41-
raise ValueError(f"Unsupported operation: {operation}")
42-
43-
vaccine_permission = set()
44-
if isinstance(vaccine_type, list):
45-
for x in vaccine_type:
46-
vaccine_permission.add(str.lower(f"{x}.{operation}"))
47-
return vaccine_permission
48-
else:
49-
vaccine_permission.add(str.lower(f"{vaccine_type}.{operation}"))
50-
return vaccine_permission
51-
52-
# Check if the requested permission is allowed
53-
def _check_permission(self, requested: set[str]) -> None:
54-
if not requested.issubset(self.expanded_permissions):
55-
raise UnauthorizedVaxOnRecordError()
56-
return None
57-
58-
# Validate the requested vaccine type and operation against the supplier permissions
59-
def validate(self, vaccine_type, operation) -> None:
60-
requested_perm = self._vaccine_permission(vaccine_type, operation)
61-
self._check_permission(requested_perm)
15+
operation_codes = [
16+
operation_code
17+
for operation_code in operation_codes_str.lower()
18+
if operation_code in list(ApiOperationCode)
19+
]
20+
expanded_permissions[vaccine_type] = operation_codes
21+
return expanded_permissions
22+
23+
def validate_permissions(permissions: list[str], operation: ApiOperationCode, vaccine_types: list[str]):
24+
expanded_permissions = _expand_permissions(permissions)
25+
print(f"operation: {operation}, expanded_permissions: {expanded_permissions}, vaccine_types: {vaccine_types}")
26+
return all([
27+
operation in expanded_permissions.get(vaccine_type.lower(), [])
28+
for vaccine_type in vaccine_types
29+
])

backend/tests/sample_data/permissions_config.py

Whitespace-only changes.

backend/tests/test_fhir_controller.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ def setUp(self):
883883
@patch("fhir_controller.get_supplier_permissions")
884884
def test_create_immunization(self,mock_get_permissions):
885885
"""it should create Immunization and return resource's location"""
886-
mock_get_permissions.return_value = ["COVID19.CRUDS"]
886+
mock_get_permissions.return_value = ["COVID19.CRUDS", "FLU.CRUDS"]
887887
imms_id = str(uuid.uuid4())
888888
imms = create_covid_19_immunization(imms_id)
889889
aws_event = {
@@ -896,7 +896,7 @@ def test_create_immunization(self,mock_get_permissions):
896896

897897
imms_obj = json.loads(aws_event["body"])
898898
mock_get_permissions.assert_called_once_with("Test")
899-
self.service.create_immunization.assert_called_once_with(imms_obj, ["COVID19.CRUDS"], "Test")
899+
self.service.create_immunization.assert_called_once_with(imms_obj, ["COVID19.CRUDS", "FLU.CRUDS"], "Test")
900900
self.assertEqual(response["statusCode"], 201)
901901
self.assertTrue("body" not in response)
902902
self.assertTrue(response["headers"]["Location"].endswith(f"Immunization/{imms_id}"))
@@ -1092,7 +1092,7 @@ def test_update_immunization_etag_missing(self, mock_get_supplier_permissions):
10921092
@patch("fhir_controller.get_supplier_permissions")
10931093
def test_update_immunization_duplicate(self, mock_get_supplier_permissions):
10941094
"""it should not update the Immunization record"""
1095-
mock_get_supplier_permissions.return_value = ["Covid19.U"]
1095+
mock_get_supplier_permissions.return_value = ["COVID19.U"]
10961096
# Given
10971097
imms_id = "valid-id"
10981098
imms = {"id": "valid-id"}
@@ -1146,7 +1146,7 @@ def test_update_immunization_UnauthorizedVaxError(self, mock_get_supplier_permis
11461146
@patch("fhir_controller.get_supplier_permissions")
11471147
def test_update_immunization_UnauthorizedVaxError_check_for_non_batch(self, mock_get_supplier_permissions):
11481148
"""it should not update the Immunization record"""
1149-
mock_get_supplier_permissions.return_value = ["COVID19.U"]
1149+
mock_get_supplier_permissions.return_value = ["COVID19.CRDS"]
11501150
imms_id = "valid-id"
11511151
imms = {"id": "valid-id"}
11521152
aws_event = {
@@ -1161,7 +1161,7 @@ def test_update_immunization_UnauthorizedVaxError_check_for_non_batch(self, mock
11611161
@patch("fhir_controller.get_supplier_permissions")
11621162
def test_update_immunization_Unauthorizedsystem_check_for_non_batch(self, mock_get_supplier_permissions):
11631163
"""it should not update the Immunization record"""
1164-
mock_get_supplier_permissions.return_value = ["COVID19.CRUD"]
1164+
mock_get_supplier_permissions.return_value = ["COVID19.CRD"]
11651165
imms_id = "valid-id"
11661166
imms = {"id": "valid-id"}
11671167
aws_event = {
@@ -1612,7 +1612,7 @@ def setUp(self):
16121612
def test_get_search_immunizations(self, mock_get_supplier_permissions):
16131613
"""it should search based on patient_identifier and immunization_target"""
16141614

1615-
mock_get_supplier_permissions.return_value = ["covid19.s"]
1615+
mock_get_supplier_permissions.return_value = ["COVID19.S"]
16161616
search_result = Bundle.construct()
16171617
self.service.search_immunizations.return_value = search_result
16181618

@@ -1975,7 +1975,7 @@ def test_search_immunizations_returns_200_remove_vaccine_not_done(self, mock_get
19751975
"This method should return 200 but remove the data which has status as not done."
19761976
search_result = load_json_data("sample_immunization_response _for _not_done_event.json")
19771977
bundle = Bundle.parse_obj(search_result)
1978-
mock_get_supplier_permissions.return_value = ["covid19.cruds"]
1978+
mock_get_supplier_permissions.return_value = ["COVID19.CRUDS"]
19791979
self.service.search_immunizations.return_value = bundle
19801980
vaccine_type = "COVID19"
19811981
lambda_event = {

backend/tests/test_fhir_repository.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def test_unauthorized_get_immunization_by_identifier(self):
7979
]
8080
}
8181
)
82-
with self.assertRaises(UnauthorizedVaxOnRecordError) as e:
82+
with self.assertRaises(UnauthorizedVaxError) as e:
8383
# When
8484
self.repository.get_immunization_by_identifier(imms_id, ["FLU.CRUD"])
8585

@@ -112,7 +112,7 @@ def test_get_immunization_by_id(self):
112112
}
113113
}
114114
)
115-
imms = self.repository.get_immunization_by_id(imms_id, ["COVID19.CRUD"])
115+
imms = self.repository.get_immunization_by_id(imms_id, ["COVID19.CRUDS"])
116116

117117
# Validate the results
118118
self.assertDictEqual(resource, imms)
@@ -133,7 +133,7 @@ def test_unauthorized_get_immunization_by_id(self):
133133
}
134134
}
135135
)
136-
with self.assertRaises(UnauthorizedVaxOnRecordError) as e:
136+
with self.assertRaises(UnauthorizedVaxError) as e:
137137
# When
138138
self.repository.get_immunization_by_id(imms_id, ["FLU.CRUD"])
139139

@@ -328,10 +328,22 @@ def test_create_patient_with_unauthorised_vaccine_type_permissions(self):
328328
"""Patient record should not be created"""
329329
imms = create_covid_19_immunization_dict("an-id")
330330

331+
self.repository.table.query.return_value = {
332+
"Count": 0,
333+
"Items": []
334+
}
335+
336+
self.repository.table.put_item.return_value = {
337+
"ResponseMetadata": {
338+
"HTTPStatusCode": 200
339+
}
340+
}
341+
331342
update_target_disease_code(imms, DiseaseCodes.flu)
332-
with self.assertRaises(UnauthorizedVaxOnRecordError) as e:
343+
with self.assertRaises(UnauthorizedVaxError) as e:
333344
# When
334-
self.repository.create_immunization(imms, self.patient, ["COVID.CRUD"], "Test")
345+
self.repository.create_immunization(imms, self.patient, ["COVID19.CRUD"], "Test")
346+
335347

336348

337349
class TestUpdateImmunization(unittest.TestCase):
@@ -515,7 +527,16 @@ def test_unauthorised_vax_delete(self):
515527
}
516528
)
517529

518-
with self.assertRaises(UnauthorizedVaxOnRecordError) as e:
530+
self.repository.table.update_item.return_value = {
531+
"ResponseMetadata": {
532+
"HTTPStatusCode": 200
533+
},
534+
"Attributes": {
535+
"Resource": json.dumps({"id": "valid-id", "status": "deleted"})
536+
}
537+
}
538+
539+
with self.assertRaises(UnauthorizedVaxError) as e:
519540
self.repository.delete_immunization(imms_id, ["COVID19.CRUD"], "Test")
520541

521542
def test_multiple_delete_should_not_update_timestamp(self):

0 commit comments

Comments
 (0)