Skip to content

Commit b0c5f2a

Browse files
authored
Merge branch 'master' into loganbertram/BB2-3321-oauth-revoke-endpoint
2 parents 2f592cf + 2ece510 commit b0c5f2a

File tree

12 files changed

+345
-12
lines changed

12 files changed

+345
-12
lines changed

apps/capabilities/permissions.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ def has_permission(self, request, view):
3232
return True
3333

3434
if hasattr(token, "scope"): # OAuth 2
35+
token_scopes = token.scope.split()
3536
scopes = list(ProtectedCapability.objects.filter(
36-
slug__in=token.scope.split()
37+
slug__in=token_scopes
3738
).values_list('protected_resources', flat=True).all())
3839

3940
# this is a shorterm fix to reject all tokens that do not have either
4041
# patient/coverage.read or patient/ExplanationOfBenefit.read
41-
if ("patient/Coverage.read" or "patient/ExplanationOfBenefit.read") in token.scope.split():
42+
if ("patient/Coverage.read" in token_scopes) or ("patient/ExplanationOfBenefit.read" in token_scopes):
4243
for scope in scopes:
4344
for method, path in json.loads(scope):
4445
if method != request.method:
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import re
2+
import logging
3+
import logging.config
4+
5+
MBI_WITH_HYPHEN_PATTERN = r"""\b
6+
[1-9](?![SLOIBZsloibz])[A-Za-z](?![SLOIBZsloibz)])[A-Za-z\d]\d
7+
-(?![SLOIBZsloibz])[A-Za-z](?![SLOIBZsloibz])[A-Za-z\d]\d
8+
-((?![SLOIBZsloibz])[A-Za-z]){2}\d{2}
9+
\b
10+
"""
11+
12+
MBI_WITHOUT_HYPHEN_PATTERN = r"""\b
13+
[1-9](?![SLOIBZsloibz])[A-Za-z](?![SLOIBZsloibz)])[A-Za-z\d]\d
14+
(?![SLOIBZsloibz])[A-Za-z](?![SLOIBZsloibz])[A-Za-z\d]\d
15+
((?![SLOIBZsloibzd])[A-Za-z]){2}\d{2}
16+
\b"""
17+
18+
MBI_PATTERN = f'({MBI_WITH_HYPHEN_PATTERN}|{MBI_WITHOUT_HYPHEN_PATTERN})'
19+
SENSITIVE_DATA_FILTER = "sensitive_data_filter"
20+
21+
22+
def mask_if_has_mbi(text):
23+
return re.sub(MBI_PATTERN, '***MBI***', str(text), flags=re.VERBOSE)
24+
25+
26+
def mask_mbi(value_to_mask):
27+
if isinstance(value_to_mask, str):
28+
return mask_if_has_mbi(value_to_mask)
29+
30+
if isinstance(value_to_mask, tuple):
31+
return tuple([mask_if_has_mbi(arg) for arg in value_to_mask])
32+
33+
if isinstance(value_to_mask, list):
34+
return [mask_if_has_mbi(arg) for arg in value_to_mask]
35+
36+
if isinstance(value_to_mask, dict):
37+
for key, value in value_to_mask.items():
38+
value_to_mask[key] = mask_mbi(value)
39+
40+
return value_to_mask
41+
42+
43+
class SensitiveDataFilter(logging.Filter):
44+
45+
def filter(self, record):
46+
try:
47+
record.args = mask_mbi(record.args)
48+
record.msg = mask_mbi(record.msg)
49+
return True
50+
except Exception:
51+
pass

hhs_oauth_server/settings/base.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from apps.logging.sensitive_logging_filters import SENSITIVE_DATA_FILTER, SensitiveDataFilter
23
import dj_database_url
34
import socket
45
import datetime
@@ -377,6 +378,12 @@
377378
"console": {
378379
"class": "logging.StreamHandler",
379380
"formatter": "verbose",
381+
"filters": [SENSITIVE_DATA_FILTER],
382+
}
383+
},
384+
"filters": {
385+
"sensitive_data_filter": {
386+
"()": SensitiveDataFilter,
380387
}
381388
},
382389
"loggers": {
@@ -421,6 +428,10 @@
421428
"handlers": ["console"],
422429
"level": "INFO",
423430
},
431+
'django': {
432+
'handlers': ['console'],
433+
'level': 'INFO',
434+
},
424435
},
425436
},
426437
)

hhs_oauth_server/settings/logging_it.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
raise ValueError("Bad settings, expecting handlers defined in settings.LOGGING")
2222

2323
logging_handlers['file'] = {'class': 'logging.FileHandler',
24-
'filename': logfile_path, }
24+
'filename': logfile_path,
25+
"filters": [SENSITIVE_DATA_FILTER]}
2526

2627
loggers = LOGGING.get('loggers')
2728

hhs_oauth_server/tests.py

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
"""
88

99
from django.test import TestCase
10+
11+
from apps.logging.sensitive_logging_filters import mask_mbi
12+
13+
1014
from .utils import bool_env, TRUE_LIST, FALSE_LIST, int_env
1115

1216

@@ -45,3 +49,268 @@ def test_int_values(self):
4549
for x, y in int_list:
4650
result = int_env(x)
4751
self.assertEqual(result, y)
52+
53+
54+
class MBI_tests(TestCase):
55+
56+
def test_mbi_match_dict(self):
57+
valid_mbi = "1EG4-TE5-MK74"
58+
59+
my_dict = {
60+
'key1': valid_mbi,
61+
'key2': {
62+
'key4': valid_mbi
63+
},
64+
'key3': (valid_mbi, valid_mbi),
65+
'key5': [valid_mbi, valid_mbi]
66+
}
67+
68+
masked_mbi_dict = mask_mbi(my_dict)
69+
masked_mbi_string = str(masked_mbi_dict)
70+
self.assertIn('***MBI***', masked_mbi_string)
71+
self.assertNotIn(valid_mbi, masked_mbi_string)
72+
73+
mbi_list = [valid_mbi, valid_mbi]
74+
masked_mbi_list = mask_mbi(mbi_list)
75+
self.assertIn('***MBI***', masked_mbi_list)
76+
self.assertNotIn(valid_mbi, masked_mbi_list)
77+
78+
mbi_tuple = (valid_mbi, valid_mbi)
79+
masked_mbi_tuple = mask_mbi(mbi_tuple)
80+
self.assertIn('***MBI***', masked_mbi_tuple)
81+
self.assertNotIn(valid_mbi, masked_mbi_tuple)
82+
83+
def test_mbi_match(self):
84+
85+
mbi_test_list = [
86+
# Valid MBI
87+
("1EG4-TE5-MK74", True),
88+
89+
# Valid MBI Position 3 as 0
90+
# Position 3 – alpha-numeric values 0 thru 9and A thru Z (minus S, L, O, I, B, Z)
91+
("1E04-TE5-MK74", True),
92+
93+
# Valid MBI Position 4 as 0
94+
# Position 4 – numeric values 0 thru 9
95+
("1EG0-TE5-MK74", True),
96+
97+
# Valid MBI Position 6 as 0
98+
# Position 6 – alpha-numeric values 0 thru 9and A thru Z (minus S, L, O, I, B, Z)
99+
("1EG4-T05-MK74", True),
100+
101+
# Valid MBI Position 7 as 0
102+
# Position 7 – numeric values 0 thru 9
103+
("1EG4-TE0-MK74", True),
104+
105+
# Valid MBI Position 10 as 0
106+
# Position 10 – numeric values 0 thru 9
107+
("1EG4-TE5-MK04", True),
108+
109+
# Valid MBI Position 11 as 0
110+
# Position 11 – numeric values 0 thru 9
111+
("1EG4-TE5-MK70", True),
112+
113+
114+
# Position 1 is invalid
115+
# Position 1 – numeric values 1 thru 9
116+
("AEG4-TE5-MK74", False),
117+
118+
# Position 2 is invalid
119+
# P osition 2 – alphabetic values A thru Z (minus S, L, O, I, B, Z)
120+
("1SG4-TE5-MK74", False),
121+
("1LG4-TE5-MK74", False),
122+
("1OG4-TE5-MK74", False),
123+
("1IG4-TE5-MK74", False),
124+
("1BG4-TE5-MK74", False),
125+
("1ZG4-TE5-MK74", False),
126+
("11G4-TE5-MK74", False),
127+
128+
# Position 3 is invalid
129+
# Position 3 – alpha-numeric values 0 thru 9and A thru Z (minus S, L, O, I, B, Z)
130+
("1ES4-TE5-MK74", False),
131+
("1EL4-TE5-MK74", False),
132+
("1EO4-TE5-MK74", False),
133+
("1EI4-TE5-MK74", False),
134+
("1EB4-TE5-MK74", False),
135+
("1EZ4-TE5-MK74", False),
136+
137+
# Position 4 is invalid
138+
# Position 4 – numeric values 0 thru 9
139+
("1EGA-TE5-MK74", False),
140+
141+
# Position 5 is invalid
142+
# Position 5 – alphabetic values A thru Z (minus S, L, O, I, B, Z)
143+
("1EG4-1E5-MK74", False),
144+
("1EG4-SE5-MK74", False),
145+
("1EG4-LE5-MK74", False),
146+
("1EG4-OE5-MK74", False),
147+
("1EG4-IE5-MK74", False),
148+
("1EG4-BE5-MK74", False),
149+
("1EG4-ZE5-MK74", False),
150+
151+
# Position 6 is invalid
152+
# Position 6 – alpha-numeric values 0 thru 9and A thru Z (minus S, L, O, I, B, Z)
153+
("1EG4-TS5-MK74", False),
154+
("1EG4-TL5-MK74", False),
155+
("1EG4-TO5-MK74", False),
156+
("1EG4-TI5-MK74", False),
157+
("1EG4-TB5-MK74", False),
158+
("1EG4-TZ5-MK74", False),
159+
160+
# Position 7 is invalid
161+
# Position 7 – numeric values 0 thru 9
162+
("1EG4-TEA-MK74", False),
163+
164+
# Position 8 is invalid
165+
# Position 8 – alphabetic values A thru Z (minus S, L, O, I, B, Z)
166+
("1EG4-TE5-1K74", False),
167+
("1EG4-TE5-SK74", False),
168+
("1EG4-TE5-LK74", False),
169+
("1EG4-TE5-OK74", False),
170+
("1EG4-TE5-IK74", False),
171+
("1EG4-TE5-BK74", False),
172+
("1EG4-TE5-ZK74", False),
173+
174+
# Position 9 is invalid
175+
# Position 9 – alphabetic values A thru Z (minus S, L, O, I, B, Z)
176+
("1EG4-TE5-M174", False),
177+
("1EG4-TE5-MS74", False),
178+
("1EG4-TE5-ML74", False),
179+
("1EG4-TE5-MO74", False),
180+
("1EG4-TE5-MOI74", False),
181+
("1EG4-TE5-MKB4", False),
182+
("1EG4-TE5-MKZ4", False),
183+
184+
# Position 10 is invalid
185+
# Position 10 – numeric values 0 thru 9
186+
("1EG4-TE5-MKA4", False),
187+
188+
# Position 11 is invalid
189+
# Position 11 – numeric values 0 thru 9
190+
("1EG4-TE5-MK7A", False),
191+
192+
# WITHOUT HYPHEN MBI TEST CASES BELOW
193+
# Valid MBI
194+
("1EG4TE5MK74", True),
195+
196+
# Valid MBI Position 3 as 0
197+
# Position 3 – alphanumeric values 0 thru 9and A thru Z (minus S, L, O, I, B, Z)
198+
("1E04TE5MK74", True),
199+
200+
# Valid MBI Position 4 as 0
201+
# Position 4 – numeric values 0 thru 9
202+
("1EG0TE5MK74", True),
203+
204+
# Valid MBI Position 6 as 0
205+
# Position 6 – alphanumeric values 0 thru 9and A thru Z (minus S, L, O, I, B, Z)
206+
("1EG4T05MK74", True),
207+
208+
# Valid MBI Position 7 as 0
209+
# Position 7 – numeric values 0 thru 9
210+
("1EG4TE0MK74", True),
211+
212+
# Valid MBI Position 10 as 0
213+
# Position 10 – numeric values 0 thru 9
214+
("1EG4TE5MK04", True),
215+
216+
# Valid MBI Position 11 as 0
217+
# Position 11 – numeric values 0 thru 9
218+
("1EG4TE5MK70", True),
219+
220+
221+
# Position 1 is invalid
222+
# Position 1 – numeric values 1 thru 9
223+
("AEG4TE5MK74", False),
224+
225+
# Position 2 is invalid
226+
# P osition 2 – alphabetic values A thru Z (minus S, L, O, I, B, Z)
227+
("1SG4TE5MK74", False),
228+
("1LG4TE5MK74", False),
229+
("1OG4TE5MK74", False),
230+
("1IG4TE5MK74", False),
231+
("1BG4TE5MK74", False),
232+
("1ZG4TE5MK74", False),
233+
("11G4TE5MK74", False),
234+
235+
# Position 3 is invalid
236+
# Position 3 – alphanumeric values 0 thru 9and A thru Z (minus S, L, O, I, B, Z)
237+
("1ES4TE5MK74", False),
238+
("1EL4TE5MK74", False),
239+
("1EO4TE5MK74", False),
240+
("1EI4TE5MK74", False),
241+
("1EB4TE5MK74", False),
242+
("1EZ4TE5MK74", False),
243+
244+
# Position 4 is invalid
245+
# Position 4 – numeric values 0 thru 9
246+
("1EGATE5MK74", False),
247+
248+
# Position 5 is invalid
249+
# Position 5 – alphabetic values A thru Z (minus S, L, O, I, B, Z)
250+
("1EG41E5MK74", False),
251+
("1EG4SE5MK74", False),
252+
("1EG4LE5MK74", False),
253+
("1EG4OE5MK74", False),
254+
("1EG4IE5MK74", False),
255+
("1EG4BE5MK74", False),
256+
("1EG4ZE5MK74", False),
257+
258+
# Position 6 is invalid
259+
# Position 6 – alphanumeric values 0 thru 9and A thru Z (minus S, L, O, I, B, Z)
260+
("1EG4TS5MK74", False),
261+
("1EG4TL5MK74", False),
262+
("1EG4TO5MK74", False),
263+
("1EG4TI5MK74", False),
264+
("1EG4TB5MK74", False),
265+
("1EG4TZ5MK74", False),
266+
267+
# Position 7 is invalid
268+
# Position 7 – numeric values 0 thru 9
269+
("1EG4TEAMK74", False),
270+
271+
# Position 8 is invalid
272+
# Position 8 – alphabetic values A thru Z (minus S, L, O, I, B, Z)
273+
("1EG4TE51K74", False),
274+
("1EG4TE5SK74", False),
275+
("1EG4TE5LK74", False),
276+
("1EG4TE5OK74", False),
277+
("1EG4TE5IK74", False),
278+
("1EG4TE5BK74", False),
279+
("1EG4TE5ZK74", False),
280+
281+
# Position 9 is invalid
282+
# Position 9 – alphabetic values A thru Z (minus S, L, O, I, B, Z)
283+
("1EG4TE5M174", False),
284+
("1EG4TE5MS74", False),
285+
("1EG4TE5ML74", False),
286+
("1EG4TE5MO74", False),
287+
("1EG4TE5MOI74", False),
288+
("1EG4TE5MKB4", False),
289+
("1EG4TE5MKZ4", False),
290+
291+
# Position 10 is invalid
292+
# Position 10 – numeric values 0 thru 9
293+
("1EG4TE5MKA4", False),
294+
295+
# Position 11 is invalid
296+
# Position 11 – numeric values 0 thru 9
297+
("1EG4TE5MK7A", False),
298+
]
299+
300+
for mbi_value, expected in mbi_test_list:
301+
# Create a text that contains the MBI
302+
uppercase_mbi_text = f"This is a test string with MBI: {mbi_value}, expected: {expected}."
303+
masked_uppercase_text = mask_mbi(uppercase_mbi_text)
304+
lowercase_mbi_text = uppercase_mbi_text.lower()
305+
masked_mbi_lowercase_text = mask_mbi(lowercase_mbi_text)
306+
# Check if the MBI was masked
307+
if expected:
308+
self.assertIn('***MBI***', masked_uppercase_text)
309+
self.assertIn('***MBI***', masked_mbi_lowercase_text)
310+
self.assertNotIn(mbi_value, masked_uppercase_text)
311+
self.assertNotIn(mbi_value.lower(), masked_mbi_lowercase_text)
312+
else:
313+
self.assertNotIn('***MBI***', masked_uppercase_text)
314+
self.assertNotIn('***MBI***', masked_mbi_lowercase_text)
315+
self.assertIn(mbi_value, masked_uppercase_text)
316+
self.assertIn(mbi_value.lower(), masked_mbi_lowercase_text)

0 commit comments

Comments
 (0)