diff --git a/apps/fhir/bluebutton/fixtures/fhir_bluebutton_new_testdata.json b/apps/fhir/bluebutton/fixtures/fhir_bluebutton_new_testdata.json index da55736a7..7f40bf9c6 100644 --- a/apps/fhir/bluebutton/fixtures/fhir_bluebutton_new_testdata.json +++ b/apps/fhir/bluebutton/fixtures/fhir_bluebutton_new_testdata.json @@ -47,9 +47,9 @@ "fields": { "user": 1, "fhir_source": 1, - "fhir_id": "Patient/9342511", + "_fhir_id": "Patient/9342511", "date_created": "2016-07-01T19:37:00.452Z", - "user_id_hash": "" + "_user_id_hash": "123456" } }, { @@ -58,9 +58,9 @@ "fields": { "user": 2, "fhir_source": 1, - "fhir_id": "Patient/9342511", + "_fhir_id": "Patient/9342511", "date_created": "2016-08-25T15:55:10.338Z", - "user_id_hash": "" + "_user_id_hash": "123457" } }, { @@ -69,9 +69,9 @@ "fields": { "user": 3, "fhir_source": 1, - "fhir_id": "Patient/9342703", + "_fhir_id": "Patient/9342703", "date_created": "2016-08-29T03:15:48.436Z", - "user_id_hash": "" + "_user_id_hash": "123458" } } ] diff --git a/apps/fhir/bluebutton/fixtures/fhir_bluebutton_test_rt.json b/apps/fhir/bluebutton/fixtures/fhir_bluebutton_test_rt.json index d3b86d718..ae4cdbe5f 100644 --- a/apps/fhir/bluebutton/fixtures/fhir_bluebutton_test_rt.json +++ b/apps/fhir/bluebutton/fixtures/fhir_bluebutton_test_rt.json @@ -69,7 +69,8 @@ "pk": 1, "fields": {"user": 1, "fhir_source": 1, - "fhir_id": "4995802", + "_fhir_id": "4995802", + "_user_id_hash": "139e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130", "date_created": "2016-05-25T05:03:49.342Z"} }, { diff --git a/apps/fhir/bluebutton/migrations/0003_auto_20191208_0010.py b/apps/fhir/bluebutton/migrations/0003_auto_20191208_0010.py new file mode 100644 index 000000000..df9aa3e69 --- /dev/null +++ b/apps/fhir/bluebutton/migrations/0003_auto_20191208_0010.py @@ -0,0 +1,33 @@ +# Generated by Django 2.1.11 on 2019-12-08 00:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bluebutton', '0002_auto_20180127_2032'), + ] + + operations = [ + migrations.AlterField( + model_name='crosswalk', + name='fhir_id', + field=models.CharField(db_column='fhir_id', db_index=True, default=None, max_length=80, null=True), + ), + migrations.AlterField( + model_name='crosswalk', + name='user_id_hash', + field=models.CharField(db_column='user_id_hash', db_index=True, default=None, max_length=64, unique=True, verbose_name='PBKDF2 of User ID'), + ), + migrations.RenameField( + model_name='crosswalk', + old_name='fhir_id', + new_name='_fhir_id', + ), + migrations.RenameField( + model_name='crosswalk', + old_name='user_id_hash', + new_name='_user_id_hash', + ), + ] diff --git a/apps/fhir/bluebutton/models.py b/apps/fhir/bluebutton/models.py index 03c72ee35..3ae5a7487 100644 --- a/apps/fhir/bluebutton/models.py +++ b/apps/fhir/bluebutton/models.py @@ -2,6 +2,7 @@ from requests import Response from django.conf import settings from django.db import models +from django.core.exceptions import ValidationError from apps.accounts.models import get_user_id_salt from apps.fhir.server.models import ResourceRouter from django.utils.crypto import pbkdf2 @@ -53,18 +54,23 @@ class Crosswalk(models.Model): blank=True, null=True) # default=settings.FHIR_SERVER_DEFAULT) - fhir_id = models.CharField(max_length=80, - blank=True, default="", db_index=True) + _fhir_id = models.CharField(max_length=80, + null=True, + default=None, + db_column="fhir_id", + db_index=True) date_created = models.DateTimeField(auto_now_add=True) user_id_type = models.CharField(max_length=1, default=settings.USER_ID_TYPE_DEFAULT, choices=settings.USER_ID_TYPE_CHOICES) - user_id_hash = models.CharField(max_length=64, - blank=True, - default="", - verbose_name="PBKDF2 of User ID", - db_index=True) + _user_id_hash = models.CharField(max_length=64, + verbose_name="PBKDF2 of User ID", + unique=True, + null=False, + default=None, + db_column="user_id_hash", + db_index=True) objects = models.Manager() # Default manager real_objects = RealCrosswalkManager() # Real bene manager @@ -73,10 +79,32 @@ class Crosswalk(models.Model): def __str__(self): return '%s %s' % (self.user.first_name, self.user.last_name) + @property + def fhir_id(self): + return self._fhir_id + + @fhir_id.setter + def fhir_id(self, value): + if self._fhir_id: + raise ValidationError("this value cannot be modified.") + self._fhir_id = value + + @property + def user_id_hash(self): + return self._user_id_hash + + @user_id_hash.setter + def user_id_hash(self, value): + if self.pk: + raise ValidationError("this value cannot be modified.") + if self._user_id_hash: + raise ValidationError("this value cannot be modified.") + self._user_id_hash = value + def set_hicn(self, hicn): - self.user_id_hash = binascii.hexlify(pbkdf2(hicn, - get_user_id_salt(), - settings.USER_ID_ITERATIONS)).decode("ascii") + if self.pk: + raise ValidationError("this value cannot be modified.") + self.user_id_hash = hash_hicn(hicn) def get_fhir_patient_url(self): # Return the fhir server url and {Resource_name}/{id} diff --git a/apps/fhir/bluebutton/permissions.py b/apps/fhir/bluebutton/permissions.py index 3734dea92..68e20608d 100644 --- a/apps/fhir/bluebutton/permissions.py +++ b/apps/fhir/bluebutton/permissions.py @@ -27,7 +27,7 @@ def has_permission(self, request, view): logger.info('Crosswalk for %s does not exist' % request.user) return False - if crosswalk.fhir_id == "": + if crosswalk.fhir_id is None: authenticate_crosswalk(crosswalk) request.crosswalk = crosswalk diff --git a/apps/fhir/bluebutton/tests/test_models.py b/apps/fhir/bluebutton/tests/test_models.py index 2616689c4..446828ce3 100644 --- a/apps/fhir/bluebutton/tests/test_models.py +++ b/apps/fhir/bluebutton/tests/test_models.py @@ -1,3 +1,5 @@ +from django.db.utils import IntegrityError +from django.core.exceptions import ValidationError from apps.test import BaseApiTest from ..models import Crosswalk @@ -19,7 +21,8 @@ def test_get_full_url_good(self): cw = Crosswalk.objects.create(user=user, fhir_source=fs, - fhir_id="123456") + fhir_id="-20000000000001", + user_id_hash="139e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130") fhir = Crosswalk.objects.get(user=user.pk) @@ -43,7 +46,8 @@ def test_get_full_url_bad(self): Crosswalk.objects.create(user=user, fhir_source=fs, - fhir_id="123456") + fhir_id="-20000000000001", + user_id_hash="139e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130") fhir = Crosswalk.objects.get(user=user.pk) @@ -51,3 +55,52 @@ def test_get_full_url_bad(self): invalid_match = "http://localhost:8000/fhir/" + "Practitioner/123456" self.assertNotEqual(url_info, invalid_match) + + def test_require_user_id_hash(self): + user = self._create_user('john', 'password', + first_name='John', + last_name='Smith', + email='john@smith.net') + with self.assertRaises(IntegrityError): + Crosswalk.objects.create(user=user) + + def test_immutable_fhir_id(self): + user = self._create_user('john', 'password', + first_name='John', + last_name='Smith', + email='john@smith.net') + + # created a default user + fs = ResourceRouter.objects.create(name="Main Server", + fhir_url="http://localhost:8000/fhir/", + shard_by="Patient", + server_search_expiry=1800) + + Crosswalk.objects.create(user=user, + fhir_source=fs, + fhir_id="-20000000000001", + user_id_hash="139e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130") + cw = Crosswalk.objects.get(_fhir_id="-20000000000001") + with self.assertRaises(ValidationError): + cw.fhir_id = "-20000000000002" + + def test_immuatble_user_id_hash(self): + user = self._create_user('john', 'password', + first_name='John', + last_name='Smith', + email='john@smith.net') + + # created a default user + fs = ResourceRouter.objects.create(name="Main Server", + fhir_url="http://localhost:8000/fhir/", + shard_by="Patient", + server_search_expiry=1800) + + cw = Crosswalk.objects.create(user=user, + fhir_source=fs, + fhir_id="-20000000000001", + user_id_hash="139e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130") + cw = Crosswalk.objects.get(_fhir_id="-20000000000001") + self.assertEqual(cw.user_id_hash, "139e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130") + with self.assertRaises(ValidationError): + cw.user_id_hash = "239e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130" diff --git a/apps/fhir/bluebutton/tests/test_utils.py b/apps/fhir/bluebutton/tests/test_utils.py index 5ee62b858..265c53c4f 100644 --- a/apps/fhir/bluebutton/tests/test_utils.py +++ b/apps/fhir/bluebutton/tests/test_utils.py @@ -1,4 +1,5 @@ import os +import uuid from django.conf import settings from django.contrib.auth.models import User from django.test import TestCase, RequestFactory @@ -405,6 +406,7 @@ def setUp(self): xwalk = Crosswalk() xwalk.user = self.user xwalk.fhir_id = "Patient/12345" + xwalk.set_hicn(uuid.uuid4()) xwalk.save() def test_crosswalk_fhir_id(self): @@ -422,6 +424,7 @@ def test_crosswalk_fhir_id(self): x = Crosswalk() x.user = u x.fhir_id = "Patient/23456" + x.set_hicn(uuid.uuid4()) x.save() result = crosswalk_patient_id(u) diff --git a/apps/fhir/bluebutton/utils.py b/apps/fhir/bluebutton/utils.py index 5b335db98..e0838e8b9 100644 --- a/apps/fhir/bluebutton/utils.py +++ b/apps/fhir/bluebutton/utils.py @@ -137,7 +137,7 @@ def generate_info_headers(request): crosswalk = get_crosswalk(user) if crosswalk: # we need to send the HicnHash or the fhir_id - if len(crosswalk.fhir_id) > 0: + if crosswalk.fhir_id is not None: result['BlueButton-BeneficiaryId'] = 'patientId:' + str(crosswalk.fhir_id) else: result['BlueButton-BeneficiaryId'] = 'hicnHash:' + str(crosswalk.user_id_hash) diff --git a/apps/mymedicare_cb/models.py b/apps/mymedicare_cb/models.py index 78a78a2d3..214cfa15d 100644 --- a/apps/mymedicare_cb/models.py +++ b/apps/mymedicare_cb/models.py @@ -70,14 +70,15 @@ def create_beneficiary_record(username=None, assert username is not None assert username != "" assert user_id_hash is not None + assert len(user_id_hash) == 64, "incorrect user id hash format" if User.objects.filter(username=username).exists(): raise ValidationError("user already exists", username) - if Crosswalk.objects.filter(user_id_hash=user_id_hash).exists(): + if Crosswalk.objects.filter(_user_id_hash=user_id_hash).exists(): raise ValidationError("user_id_hash already exists", user_id_hash) - if fhir_id and Crosswalk.objects.filter(fhir_id=fhir_id).exists(): + if fhir_id and Crosswalk.objects.filter(_fhir_id=fhir_id).exists(): raise ValidationError("fhir_id already exists", fhir_id) with transaction.atomic(): diff --git a/apps/mymedicare_cb/tests/test_callback.py b/apps/mymedicare_cb/tests/test_callback.py index c1f51c785..6172b7b7d 100644 --- a/apps/mymedicare_cb/tests/test_callback.py +++ b/apps/mymedicare_cb/tests/test_callback.py @@ -155,7 +155,7 @@ def sls_user_info_mock(url, request): return { 'status_code': 200, 'content': { - 'sub': '0123456789abcdefghijklmnopqrstuvwxyz', + 'sub': '00112233-4455-6677-8899-aabbccddeeff', 'given_name': '', 'family_name': '', 'email': 'bob@bobserver.bob', diff --git a/apps/test.py b/apps/test.py index bcb410c81..5857c7907 100644 --- a/apps/test.py +++ b/apps/test.py @@ -97,7 +97,8 @@ def create_token(self, first_name, last_name): last_name=last_name, email="%s@%s.net" % (first_name, last_name)) Crosswalk.objects.get_or_create(user=user, - fhir_id=settings.DEFAULT_SAMPLE_FHIR_ID, + _fhir_id=settings.DEFAULT_SAMPLE_FHIR_ID, + _user_id_hash=user.username, fhir_source=get_resourcerouter()) # create a oauth2 application and add capabilities @@ -116,7 +117,7 @@ def create_token_no_fhir(self, first_name, last_name): last_name=last_name, email="%s@%s.net" % (first_name, last_name)) Crosswalk.objects.get_or_create(user=user, - user_id_hash="139e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130", + _user_id_hash="139e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130", fhir_source=get_resourcerouter()) # create a oauth2 application and add capabilities diff --git a/apps/testclient/management/commands/create_test_user_and_application.py b/apps/testclient/management/commands/create_test_user_and_application.py index 93652d3db..5adaf6aed 100644 --- a/apps/testclient/management/commands/create_test_user_and_application.py +++ b/apps/testclient/management/commands/create_test_user_and_application.py @@ -45,7 +45,8 @@ def create_user(group): u.groups.add(group) c, g_o_c = Crosswalk.objects.get_or_create(user=u, - fhir_id=settings.DEFAULT_SAMPLE_FHIR_ID, + _fhir_id=settings.DEFAULT_SAMPLE_FHIR_ID, + _user_id_hash="139e178537ed3bc486e6a7195a47a82a2cd6f46e911660fe9775f6e0dd3f1130", fhir_source=get_resourcerouter()) return u