Skip to content

Commit 3b405a2

Browse files
Merge pull request #1169 from NHSDigital/feature/kabo5-NRL-1933-app-wide-auth
NRL-1993 use app level permissions if defined
2 parents a7cae09 + 6246105 commit 3b405a2

File tree

4 files changed

+241
-6
lines changed

4 files changed

+241
-6
lines changed

layer/nrlf/core/authoriser.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@ def get_pointer_permissions_v2(
2222
ods_code = connection_metadata.ods_code
2323
app_id = connection_metadata.nrl_app_id
2424

25-
key = f"{producer_or_consumer}/{app_id}/{ods_code}.json"
26-
logger.log(LogReference.V2PERMISSIONS011, key=key)
27-
25+
# check for app-wide permissions
26+
app_wide_key = f"{producer_or_consumer}/{app_id}.json"
27+
if path.isfile(f"/opt/python/nrlf_permissions/{app_wide_key}"):
28+
logger.log(LogReference.V2PERMISSIONS011, key=app_wide_key)
29+
key = app_wide_key
30+
else: # use org level
31+
key = f"{producer_or_consumer}/{app_id}/{ods_code}.json"
32+
logger.log(LogReference.V2PERMISSIONS011, key=key)
2833
file_path = f"/opt/python/nrlf_permissions/{key}"
2934

3035
pointer_permissions = {}

layer/nrlf/core/tests/test_authoriser.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def test_authoriser_parse_permission_file_with_permission_file():
2727
new_callable=mock_open,
2828
read_data='{"types": ["http://snomed.info/sct|736253001"]}',
2929
)
30-
def test_authoriser_get_v2_permissions_with_pointer_types(mock_file, mocker):
30+
def test_authoriser_get_v2_permissions_with_org_pointer_types(mock_file, mocker):
3131
spy = mocker.spy(logger, "log")
3232

3333
expected_lookup_key = "producer/ODS123-app-id/ODS123.json"
@@ -47,6 +47,35 @@ def test_authoriser_get_v2_permissions_with_pointer_types(mock_file, mocker):
4747
spy.assert_called_with(LogReference.V2PERMISSIONS011, key=expected_lookup_key)
4848

4949

50+
@patch(
51+
"builtins.open",
52+
new_callable=mock_open,
53+
read_data='{"types": ["http://snomed.info/sct|736253001"]}',
54+
)
55+
@patch("os.path.isfile")
56+
def test_authoriser_get_v2_permissions_with_app_pointer_types(
57+
mock_isfile, mock_file, mocker
58+
):
59+
spy = mocker.spy(logger, "log")
60+
mock_isfile.return_value = True
61+
62+
expected_lookup_key = "producer/ODS123-app-id.json"
63+
connection_metadata = parse_headers(
64+
create_headers(ods_code="ODS123", nrl_app_id="ODS123-app-id")
65+
)
66+
result = get_pointer_permissions_v2(
67+
connection_metadata=connection_metadata,
68+
request_path="/producer/DocumentReference/_search",
69+
)
70+
71+
mock_file.assert_called_once_with(
72+
f"/opt/python/nrlf_permissions/{expected_lookup_key}"
73+
)
74+
assert result.get("types") == ["http://snomed.info/sct|736253001"]
75+
76+
spy.assert_called_with(LogReference.V2PERMISSIONS011, key=expected_lookup_key)
77+
78+
5079
def test_authoriser_parse_v2_permission_file_with_no_permission_file(mocker):
5180
spy = mocker.spy(logger, "log")
5281
expected_lookup_key = "consumer/NotAnApp/NotFound.json"

scripts/get_s3_permissions.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,20 @@ def add_feature_test_files(local_path):
6262
"""
6363

6464
print("Adding feature test v2 permissions to temporary directory...")
65-
permissions = {
65+
org_permissions = {
6666
"consumer": [
6767
(
6868
"z00z-y11y-x22x",
6969
"RX898",
7070
[PointerTypes.MENTAL_HEALTH_PLAN.value],
7171
[],
7272
), # http://snomed.info/sct|736253002
73+
(
74+
"app-t004",
75+
"ODS1",
76+
[PointerTypes.PERSONALISED_CARE_AND_SUPPORT_PLAN.value],
77+
[],
78+
),
7379
(
7480
"z00z-y11y-x22x",
7581
"4LLTYP35C",
@@ -84,6 +90,12 @@ def add_feature_test_files(local_path):
8490
[PointerTypes.EOL_CARE_PLAN.value],
8591
[],
8692
), # http://snomed.info/sct|736373009
93+
(
94+
"app-t004",
95+
"ODS1",
96+
[PointerTypes.PERSONALISED_CARE_AND_SUPPORT_PLAN.value],
97+
[],
98+
),
8799
(
88100
"z00z-y11y-x22x",
89101
"4LLTYP35P",
@@ -99,9 +111,47 @@ def add_feature_test_files(local_path):
99111
pointer_types,
100112
access_controls,
101113
)
102-
for actor_type, entries in permissions.items()
114+
for actor_type, entries in org_permissions.items()
103115
for app_id, ods_code, pointer_types, access_controls in entries
104116
]
117+
app_permissions = {
118+
"consumer": [
119+
("app-t001", [PointerTypes.MENTAL_HEALTH_PLAN.value], []),
120+
(
121+
"app-t002",
122+
[
123+
PointerTypes.ADVANCE_CARE_PLAN.value,
124+
PointerTypes.EMERGENCY_HEALTHCARE_PLAN.value,
125+
PointerTypes.NEWS2_CHART.value,
126+
],
127+
[],
128+
),
129+
("app-t004", [PointerTypes.APPOINTMENT.value], []),
130+
],
131+
"producer": [
132+
("app-t001", [PointerTypes.EOL_COORDINATION_SUMMARY.value], []),
133+
(
134+
"app-t003",
135+
[
136+
PointerTypes.ADVANCE_CARE_PLAN.value,
137+
PointerTypes.EMERGENCY_HEALTHCARE_PLAN.value,
138+
PointerTypes.NEWS2_CHART.value,
139+
],
140+
[],
141+
),
142+
("app-t004", [PointerTypes.APPOINTMENT.value], []),
143+
],
144+
}
145+
[
146+
_write_permission_file(
147+
Path.joinpath(local_path, actor_type),
148+
app_id,
149+
pointer_types,
150+
access_controls,
151+
)
152+
for actor_type, entries in app_permissions.items()
153+
for app_id, pointer_types, access_controls in entries
154+
]
105155

106156

107157
def download_files(s3_client, bucket_name, local_path, file_names, folders):
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
Feature: Producer v2 APP-LEVEL permissions by pointer type - Success and Failure Scenarios
2+
For the v2 permissions model, permissions are resolved from a JSON file stored in the
3+
nrlf_permissions Lambda layer. Permissions for the feature tests are baked into the layer by
4+
`scripts/get_s3_permissions.py` at build time, so no dynamic seeding step is required for
5+
success scenarios.
6+
7+
Scenario: HAPPY PATH V2 Permissions with access for pointer type - createDocumentReference
8+
Given the application 'ProducerTest001' (ID 'app-t001') is registered to access the API
9+
When producer v2 'ORGA' creates a DocumentReference with values:
10+
| property | value |
11+
| subject | 9278693472 |
12+
| status | current |
13+
| type | 861421000000109 |
14+
| category | 734163000 |
15+
| custodian | ORGA |
16+
| author | HAR1 |
17+
| url | https://example.org/my-doc.pdf |
18+
| practiceSetting | 788002001 |
19+
Then the response status code is 201
20+
And the response is an OperationOutcome with 1 issue
21+
And the OperationOutcome contains the issue:
22+
"""
23+
{
24+
"severity": "information",
25+
"code": "informational",
26+
"details": {
27+
"coding": [
28+
{
29+
"system": "https://fhir.nhs.uk/ValueSet/NRL-ResponseCode",
30+
"code": "RESOURCE_CREATED",
31+
"display": "Resource created"
32+
}
33+
]
34+
},
35+
"diagnostics": "The document has been created"
36+
}
37+
"""
38+
And the response has a Location header
39+
And the Location header starts with '/DocumentReference/ORGA-'
40+
And the resource in the Location header exists with values:
41+
| property | value |
42+
| subject | 9278693472 |
43+
| status | current |
44+
| type | 861421000000109 |
45+
| category | 734163000 |
46+
| custodian | ORGA |
47+
| author | HAR1 |
48+
| url | https://example.org/my-doc.pdf |
49+
| practiceSetting | 788002001 |
50+
51+
Scenario: V2 Permissions with no producer access at all (but app level consumer access for specified type)
52+
Given the application 'ProducerTest002' (ID 'app-t002') is registered to access the API
53+
When producer v2 'ORGA' creates a DocumentReference with values:
54+
| property | value |
55+
| subject | 9278693472 |
56+
| status | current |
57+
| type | 736366004 |
58+
| category | 734163000 |
59+
| custodian | ORGA |
60+
| author | HAR1 |
61+
| url | https://example.org/my-doc.pdf |
62+
| practiceSetting | 788002001 |
63+
Then the response status code is 403
64+
And the response is an OperationOutcome with 1 issue
65+
And the OperationOutcome contains the issue:
66+
"""
67+
{
68+
"severity": "error",
69+
"code": "forbidden",
70+
"details": {
71+
"coding": [
72+
{
73+
"system": "https://fhir.nhs.uk/CodeSystem/Spine-ErrorOrWarningCode",
74+
"code": "ACCESS DENIED",
75+
"display": "Access has been denied to process this request"
76+
}
77+
]
78+
},
79+
"diagnostics": "Your organisation 'ORGA' does not have permission to access this resource. Contact the onboarding team."
80+
}
81+
"""
82+
83+
Scenario: V2 Permissions with no access to specified type
84+
Given the application 'ProducerTest003' (ID 'app-t003') is registered to access the API
85+
When producer v2 'ORGA' creates a DocumentReference with values:
86+
| property | value |
87+
| subject | 9278693472 |
88+
| status | current |
89+
| type | 749001000000101 |
90+
| category | 419891008 |
91+
| custodian | ORGA |
92+
| author | HAR1 |
93+
| url | https://example.org/my-doc.pdf |
94+
| practiceSetting | 788002001 |
95+
Then the response status code is 403
96+
And the response is an OperationOutcome with 1 issue
97+
And the OperationOutcome contains the issue:
98+
"""
99+
{
100+
"severity": "error",
101+
"code": "forbidden",
102+
"details": {
103+
"coding": [
104+
{
105+
"system": "https://fhir.nhs.uk/CodeSystem/Spine-ErrorOrWarningCode",
106+
"code": "AUTHOR_CREDENTIALS_ERROR",
107+
"display": "Author credentials error"
108+
}
109+
]
110+
},
111+
"diagnostics": "The type of the provided DocumentReference is not in the list of allowed types for this organisation",
112+
"expression": [
113+
"type.coding[0].code"
114+
]
115+
}
116+
"""
117+
118+
Scenario: V2 Permissions with org-level permissions for requested type but app level permissions for other types
119+
Given the application 'ProducerTest004' (ID 'app-t004') is registered to access the API
120+
When producer v2 'ODS1' creates a DocumentReference with values:
121+
| property | value |
122+
| subject | 9278693472 |
123+
| status | current |
124+
| type | 2181441000000107 |
125+
| category | 734163000 |
126+
| custodian | ODS1 |
127+
| author | HAR1 |
128+
| url | https://example.org/my-doc.pdf |
129+
| practiceSetting | 788002001 |
130+
Then the response status code is 403
131+
And the response is an OperationOutcome with 1 issue
132+
And the OperationOutcome contains the issue:
133+
"""
134+
{
135+
"severity": "error",
136+
"code": "forbidden",
137+
"details": {
138+
"coding": [
139+
{
140+
"system": "https://fhir.nhs.uk/CodeSystem/Spine-ErrorOrWarningCode",
141+
"code": "AUTHOR_CREDENTIALS_ERROR",
142+
"display": "Author credentials error"
143+
}
144+
]
145+
},
146+
"diagnostics": "The type of the provided DocumentReference is not in the list of allowed types for this organisation",
147+
"expression": [
148+
"type.coding[0].code"
149+
]
150+
}
151+
"""

0 commit comments

Comments
 (0)