Skip to content

Commit

Permalink
Merge pull request #35804 from dimagi/nh/mapping
Browse files Browse the repository at this point in the history
Safe mapping
  • Loading branch information
kaapstorm authored Feb 19, 2025
2 parents 1d2c951 + b06292d commit 945da40
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 88 deletions.
2 changes: 1 addition & 1 deletion corehq/apps/integration/kyc/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Meta:
api_field_to_user_data_map = JsonField(
label=_('API Field to User Data Map'),
required=True,
expected_type=list,
expected_type=dict,
)
connection_settings = forms.ModelChoiceField(
label=_('Connection Settings'),
Expand Down
2 changes: 1 addition & 1 deletion corehq/apps/integration/kyc/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class KycConfig(models.Model):
domain = models.CharField(max_length=126, db_index=True)
user_data_store = models.CharField(max_length=25, choices=UserDataStore.CHOICES)
other_case_type = models.CharField(max_length=126, null=True)
api_field_to_user_data_map = jsonfield.JSONField(default=list)
api_field_to_user_data_map = jsonfield.JSONField(default=dict)
provider = models.CharField(
max_length=25,
choices=KycProviders.choices,
Expand Down
31 changes: 24 additions & 7 deletions corehq/apps/integration/kyc/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from corehq.apps.hqcase.utils import update_case
from corehq.apps.integration.kyc.models import UserDataStore
from corehq.apps.users.models import CommCareUser


class UserCaseNotFound(Exception):
Expand Down Expand Up @@ -129,17 +130,33 @@ def get_user_data_for_api(source, config):
Returns a dictionary of user data for the API.
``source`` is a CommCareUser or a CommCareCase.
"""
# CommCareUser properties that could map to API fields
safe_commcare_user_properties = {
'first_name',
'last_name',
'full_name',
'name',
'email',
'username', # For CommCareUsers this is an email address
'phone_number',
'default_phone_number',
}
source_data = _get_source_data(source, config)
user_data_for_api = {}
for mapping in config.api_field_to_user_data_map:
try:
if mapping['source'] == 'standard':
user_data_for_api[mapping['fieldName']] = getattr(source, mapping['mapsTo'])
else:
user_data_for_api[mapping['fieldName']] = source_data[mapping['mapsTo']]
except (AttributeError, KeyError):
for api_field, user_data_property in config.api_field_to_user_data_map.items():
if user_data_property in source_data:
# Fetch value from usercase / custom user data by default
value = source_data[user_data_property]
elif (
isinstance(source, CommCareUser)
and user_data_property in safe_commcare_user_properties
):
# Fall back to CommCareUser
value = getattr(source, user_data_property)
else:
# Conservative approach to skip the API field if data is not available for the user
continue
user_data_for_api[api_field] = value
return user_data_for_api


Expand Down
34 changes: 19 additions & 15 deletions corehq/apps/integration/kyc/tests/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@ def tearDownClass(cls):

def setUp(self):
super().setUp()
self.user = CommCareUser.create(self.domain, 'test-kyc', '123', None, None, first_name='abc')
self.user = CommCareUser.create(
self.domain, 'test-kyc', '123',
None, None,
first_name='abc',
)
self.config = KycConfig.objects.create(
domain=self.domain,
user_data_store=UserDataStore.CUSTOM_USER_DATA,
Expand All @@ -149,24 +153,25 @@ def tearDown(self):
super().tearDown()

def _sample_api_field_to_user_data_map(self):
return [
{
"fieldName": "first_name",
"mapsTo": "first_name",
"source": "standard"
},
{
"fieldName": "nationality",
"mapsTo": "nationality",
"source": "custom"
}
]
return {
# API Field : User data
"first_name": "first_name",
"nationality": "nationality",
}

def test_custom_user_data_store(self):
self.user.get_user_data(self.domain).update({'nationality': 'Indian'})
result = get_user_data_for_api(self.user, self.config)
self.assertEqual(result, {'first_name': 'abc', 'nationality': 'Indian'})

def test_unsafe_custom_user_data_store(self):
self.config.api_field_to_user_data_map = {
# API field : Custom user data / CommCareUser property
"first_name": "password",
}
result = get_user_data_for_api(self.user, self.config)
self.assertEqual(result, {})

def test_custom_user_data_store_with_no_data(self):
result = get_user_data_for_api(self.user, self.config)
self.assertEqual(result, {'first_name': 'abc'})
Expand Down Expand Up @@ -196,7 +201,6 @@ def test_user_case_data_store_with_no_case(self):
def test_custom_case_data_store(self):
self.config.user_data_store = UserDataStore.OTHER_CASE_TYPE
self.config.other_case_type = 'other-case'
self.config.api_field_to_user_data_map[0]['source'] = 'custom'
self.config.save()
case = create_case(
self.domain,
Expand All @@ -221,7 +225,7 @@ def test_custom_case_data_store_with_no_data(self):

def test_incorrect_mapping_standard_field(self):
api_field_to_user_data_map = self._sample_api_field_to_user_data_map()
api_field_to_user_data_map[0]['mapsTo'] = 'wrong-standard_field'
api_field_to_user_data_map['first_name'] = 'wrong-standard_field'
self.config.api_field_to_user_data_map = api_field_to_user_data_map
self.config.save()

Expand Down
82 changes: 18 additions & 64 deletions corehq/apps/integration/kyc/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,53 +105,18 @@ def setUpClass(cls):
)
cls.addClassCleanup(conn_settings.delete)

cls.kyc_mapping = [
{
'fieldName': 'first_name',
'mapsTo': 'first_name',
'source': 'standard',
},
{
'fieldName': 'last_name',
'mapsTo': 'last_name',
'source': 'standard'
},
{
'fieldName': 'email',
'mapsTo': 'email',
'source': 'standard'
},
{
'fieldName': 'phone_number',
'mapsTo': 'phone_number',
'source': 'custom',
},
{
'fieldName': 'national_id_number',
'mapsTo': 'national_id_number',
'source': 'custom',
},
{
'fieldName': 'street_address',
'mapsTo': 'street_address',
'source': 'custom',
},
{
'fieldName': 'city',
'mapsTo': 'city',
'source': 'custom',
},
{
'fieldName': 'post_code',
'mapsTo': 'post_code',
'source': 'custom',
},
{
'fieldName': 'country',
'mapsTo': 'country',
'source': 'custom',
},
]
cls.kyc_mapping = {
# API field: User data
'first_name': 'first_name',
'last_name': 'last_name',
'email': 'email',
'phone_number': 'phone_number',
'national_id_number': 'national_id_number',
'street_address': 'street_address',
'city': 'city',
'post_code': 'post_code',
'country': 'country',
}
cls.kyc_config = KycConfig.objects.create(
domain=cls.domain,
user_data_store=UserDataStore.CUSTOM_USER_DATA,
Expand Down Expand Up @@ -244,6 +209,7 @@ def test_response_data_users(self):
'has_invalid_data': True,
'first_name': 'Jane',
'last_name': 'Doe',
'phone_number': None,
'email': '',
})
else:
Expand All @@ -265,23 +231,11 @@ def test_response_data_users(self):
def test_response_data_cases(self):
self.kyc_config.user_data_store = UserDataStore.OTHER_CASE_TYPE
self.kyc_config.other_case_type = 'other-case'
self.kyc_config.api_field_to_user_data_map[0:3] = [
{
'fieldName': 'first_name',
'mapsTo': 'first_name',
'source': 'custom'
},
{
'fieldName': 'last_name',
'mapsTo': 'last_name',
'source': 'custom'
},
{
'fieldName': 'email',
'mapsTo': 'email',
'source': 'custom'
}
]
self.kyc_config.api_field_to_user_data_map.update({
'first_name': 'first_name',
'last_name': 'last_name',
'email': 'email',
})
self.kyc_config.save()

response = self._make_request()
Expand Down

0 comments on commit 945da40

Please sign in to comment.