feat: look up remote_id by remote_id_field_name (#37228)
This commit is contained in:
@@ -20,4 +20,7 @@ class UserMappingSerializer(serializers.Serializer): # pylint: disable=abstract
|
||||
|
||||
def get_remote_id(self, social_user):
|
||||
""" Gets remote id from social user based on provider """
|
||||
remote_id_field_name = self.context.get('remote_id_field_name', None)
|
||||
if remote_id_field_name:
|
||||
return self.provider.get_remote_id_from_field_name(social_user, remote_id_field_name)
|
||||
return self.provider.get_remote_id_from_social_auth(social_user)
|
||||
|
||||
@@ -38,8 +38,10 @@ LINKED_USERS = (ALICE_USERNAME, STAFF_USERNAME, ADMIN_USERNAME)
|
||||
PASSWORD = "edx"
|
||||
|
||||
|
||||
def get_mapping_data_by_usernames(usernames):
|
||||
def get_mapping_data_by_usernames(usernames, remote_id_field_name=False):
|
||||
""" Generate mapping data used in response """
|
||||
if remote_id_field_name:
|
||||
return [{'username': username, 'remote_id': 'external_' + username} for username in usernames]
|
||||
return [{'username': username, 'remote_id': 'remote_' + username} for username in usernames]
|
||||
|
||||
|
||||
@@ -76,11 +78,13 @@ class TpaAPITestCase(ThirdPartyAuthTestMixin, APITestCase):
|
||||
provider=google.backend_name,
|
||||
uid=f'{username}@gmail.com',
|
||||
)
|
||||
UserSocialAuth.objects.create(
|
||||
usa = UserSocialAuth.objects.create(
|
||||
user=user,
|
||||
provider=testshib.backend_name,
|
||||
uid=f'{testshib.slug}:remote_{username}',
|
||||
)
|
||||
usa.set_extra_data({'external_user_id': f'external_{username}'})
|
||||
usa.refresh_from_db()
|
||||
# Create another user not linked to any providers:
|
||||
UserFactory.create(username=CARL_USERNAME, email=f'{CARL_USERNAME}@example.com', password=PASSWORD)
|
||||
|
||||
@@ -304,12 +308,20 @@ class UserMappingViewAPITests(TpaAPITestCase):
|
||||
@ddt.data(
|
||||
({'username': [ALICE_USERNAME, STAFF_USERNAME]}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME])),
|
||||
({'username': [ALICE_USERNAME, STAFF_USERNAME], 'remote_id_field_name': 'external_user_id'}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME], remote_id_field_name=True)),
|
||||
({'remote_id': ['remote_' + ALICE_USERNAME, 'remote_' + STAFF_USERNAME, 'remote_' + CARL_USERNAME]}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME])),
|
||||
({'remote_id': ['remote_' + ALICE_USERNAME, 'remote_' + STAFF_USERNAME, 'remote_' + CARL_USERNAME],
|
||||
'remote_id_field_name': 'external_user_id'}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME], remote_id_field_name=True)),
|
||||
({'username': [ALICE_USERNAME, CARL_USERNAME, STAFF_USERNAME]}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME])),
|
||||
({'username': [ALICE_USERNAME], 'remote_id': ['remote_' + STAFF_USERNAME]}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME])),
|
||||
({'username': [ALICE_USERNAME], 'remote_id': ['remote_' + STAFF_USERNAME],
|
||||
'remote_id_field_name': 'external_user_id'}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME], remote_id_field_name=True)),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_user_mappings_with_query_params_comma_separated(self, query_params, expect_code, expect_data):
|
||||
@@ -321,6 +333,8 @@ class UserMappingViewAPITests(TpaAPITestCase):
|
||||
for attr in ['username', 'remote_id']:
|
||||
if attr in query_params:
|
||||
params.append('{}={}'.format(attr, ','.join(query_params[attr])))
|
||||
if 'remote_id_field_name' in query_params:
|
||||
params.append('remote_id_field_name={}'.format(query_params['remote_id_field_name']))
|
||||
url = "{}?{}".format(base_url, '&'.join(params))
|
||||
response = self.client.get(url, HTTP_X_EDX_API_KEY=VALID_API_KEY)
|
||||
self._verify_response(response, expect_code, expect_data)
|
||||
@@ -328,12 +342,20 @@ class UserMappingViewAPITests(TpaAPITestCase):
|
||||
@ddt.data(
|
||||
({'username': [ALICE_USERNAME, STAFF_USERNAME]}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME])),
|
||||
({'username': [ALICE_USERNAME, STAFF_USERNAME], 'remote_id_field_name': 'external_user_id'}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME], remote_id_field_name=True)),
|
||||
({'remote_id': ['remote_' + ALICE_USERNAME, 'remote_' + STAFF_USERNAME, 'remote_' + CARL_USERNAME]}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME])),
|
||||
({'remote_id': ['remote_' + ALICE_USERNAME, 'remote_' + STAFF_USERNAME, 'remote_' + CARL_USERNAME],
|
||||
'remote_id_field_name': 'external_user_id'}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME], remote_id_field_name=True)),
|
||||
({'username': [ALICE_USERNAME, CARL_USERNAME, STAFF_USERNAME]}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME])),
|
||||
({'username': [ALICE_USERNAME], 'remote_id': ['remote_' + STAFF_USERNAME]}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME])),
|
||||
({'username': [ALICE_USERNAME], 'remote_id': ['remote_' + STAFF_USERNAME],
|
||||
'remote_id_field_name': 'external_user_id'}, 200,
|
||||
get_mapping_data_by_usernames([ALICE_USERNAME, STAFF_USERNAME], remote_id_field_name=True)),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_user_mappings_with_query_params_multi_value_key(self, query_params, expect_code, expect_data):
|
||||
@@ -345,6 +367,8 @@ class UserMappingViewAPITests(TpaAPITestCase):
|
||||
for attr in ['username', 'remote_id']:
|
||||
if attr in query_params:
|
||||
params.setlist(attr, query_params[attr])
|
||||
if 'remote_id_field_name' in query_params:
|
||||
params['remote_id_field_name'] = query_params['remote_id_field_name']
|
||||
url = f"{base_url}?{params.urlencode()}"
|
||||
response = self.client.get(url, HTTP_X_EDX_API_KEY=VALID_API_KEY)
|
||||
self._verify_response(response, expect_code, expect_data)
|
||||
|
||||
@@ -323,6 +323,9 @@ class UserMappingView(ListAPIView):
|
||||
|
||||
GET /api/third_party_auth/v0/providers/{provider_id}/users?username={username1},{username2}
|
||||
|
||||
GET /api/third_party_auth/v0/providers/{provider_id}/users?username={username1}&
|
||||
remote_id_field_name={external_id_field_name}
|
||||
|
||||
GET /api/third_party_auth/v0/providers/{provider_id}/users?username={username1}&usernames={username2}
|
||||
|
||||
GET /api/third_party_auth/v0/providers/{provider_id}/users?remote_id={remote_id1},{remote_id2}
|
||||
@@ -346,6 +349,9 @@ class UserMappingView(ListAPIView):
|
||||
* usernames: Optional. List of comma separated edX usernames to filter the result set.
|
||||
e.g. ?usernames=bob123,jane456
|
||||
|
||||
* remote_id_field_name: Optional. The field name to use for the remote id lookup.
|
||||
Useful when learners are coming from external LMS. e.g. ?remote_id_field_name=ext_userid_sf
|
||||
|
||||
* page, page_size: Optional. Used for paging the result set, especially when getting
|
||||
an unfiltered list.
|
||||
|
||||
@@ -415,6 +421,7 @@ class UserMappingView(ListAPIView):
|
||||
remove idp_slug from the remote_id if there is any
|
||||
"""
|
||||
context = super().get_serializer_context()
|
||||
context['remote_id_field_name'] = self.request.query_params.get('remote_id_field_name', None)
|
||||
context['provider'] = self.provider
|
||||
|
||||
return context
|
||||
|
||||
@@ -810,6 +810,17 @@ class SAMLProviderConfig(ProviderConfig):
|
||||
prefix = self.slug + ":"
|
||||
return self.backend_name == social_auth.provider and social_auth.uid.startswith(prefix)
|
||||
|
||||
def get_remote_id_from_field_name(self, social_auth, field_name):
|
||||
""" Given a UserSocialAuth object, return the user remote ID against the field name provided. """
|
||||
if not self.match_social_auth(social_auth):
|
||||
raise ValueError(
|
||||
f"UserSocialAuth record does not match given provider {self.provider_id}"
|
||||
)
|
||||
field_value = social_auth.extra_data.get(field_name, None)
|
||||
if field_value and isinstance(field_value, list):
|
||||
return field_value[0]
|
||||
return field_value
|
||||
|
||||
def get_remote_id_from_social_auth(self, social_auth):
|
||||
""" Given a UserSocialAuth object, return the remote ID used by this provider. """
|
||||
assert self.match_social_auth(social_auth)
|
||||
|
||||
Reference in New Issue
Block a user