From aade9d7e42e2476eebe96f0e709be52e9b761ea0 Mon Sep 17 00:00:00 2001 From: yuekui Date: Tue, 6 May 2025 13:07:18 -0700 Subject: [PATCH 1/4] Revert LinkedIn OAuth2 --- social_core/backends/linkedin.py | 47 ++++++++++++++++----- social_core/tests/backends/test_linkedin.py | 25 ++++++----- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/social_core/backends/linkedin.py b/social_core/backends/linkedin.py index dcc49bb8c..7033d3786 100644 --- a/social_core/backends/linkedin.py +++ b/social_core/backends/linkedin.py @@ -48,13 +48,13 @@ class LinkedinOAuth2(BaseOAuth2): name = "linkedin-oauth2" AUTHORIZATION_URL = "https://www.linkedin.com/oauth/v2/authorization" ACCESS_TOKEN_URL = "https://www.linkedin.com/oauth/v2/accessToken" - USER_DETAILS_URL = "https://api.linkedin.com/v2/userinfo?projection=({projection})" + USER_DETAILS_URL = "https://api.linkedin.com/v2/me?projection=({projection})" USER_EMAILS_URL = ( "https://api.linkedin.com/v2/emailAddress" "?q=members&projection=(elements*(handle~))" ) REDIRECT_STATE = False - DEFAULT_SCOPE = ["email", "profile", "openid"] + DEFAULT_SCOPE = ["r_liteprofile"] EXTRA_DATA = [ ("id", "id"), ("expires_in", "expires"), @@ -65,7 +65,14 @@ class LinkedinOAuth2(BaseOAuth2): ] def user_details_url(self): - return self.USER_DETAILS_URL + # use set() since LinkedIn fails when values are duplicated + fields_selectors = list( + set(["id", "firstName", "lastName"] + self.setting("FIELD_SELECTORS", [])) + ) + # user sort to ease the tests URL mocking + fields_selectors.sort() + fields_selectors = ",".join(fields_selectors) + return self.USER_DETAILS_URL.format(projection=fields_selectors) def user_emails_url(self): return self.USER_EMAILS_URL @@ -75,10 +82,10 @@ def user_data(self, access_token, *args, **kwargs): self.user_details_url(), headers=self.user_data_headers(access_token) ) - if "email" in set(self.setting("FIELD_SELECTORS", [])): + if "emailAddress" in set(self.setting("FIELD_SELECTORS", [])): emails = self.email_data(access_token, *args, **kwargs) if emails: - response["email"] = emails[0] + response["emailAddress"] = emails[0] return response @@ -88,20 +95,38 @@ def email_data(self, access_token, *args, **kwargs): ) email_addresses = [] for element in response.get("elements", []): - email_address = element.get("handle~", {}).get("email") + email_address = element.get("handle~", {}).get("emailAddress") email_addresses.append(email_address) return list(filter(None, email_addresses)) def get_user_details(self, response): """Return user details from Linkedin account""" - response = self.user_data(access_token=response["access_token"]) + + def get_localized_name(name): + """ + FirstName & Last Name object + { + 'localized': { + 'en_US': 'Smith' + }, + 'preferredLocale': { + 'country': 'US', + 'language': 'en' + } + } + :return the localizedName from the lastName object + """ + locale = "{}_{}".format( + name["preferredLocale"]["language"], name["preferredLocale"]["country"] + ) + return name["localized"].get(locale, "") + fullname, first_name, last_name = self.get_user_names( - first_name=response["given_name"], - last_name=response["family_name"], + first_name=get_localized_name(response["firstName"]), + last_name=get_localized_name(response["lastName"]), ) - email = response.get("email", "") + email = response.get("emailAddress", "") return { - "id": response.get("sub", ""), "username": first_name + last_name, "fullname": fullname, "first_name": first_name, diff --git a/social_core/tests/backends/test_linkedin.py b/social_core/tests/backends/test_linkedin.py index 6a90d4894..a4898f71d 100644 --- a/social_core/tests/backends/test_linkedin.py +++ b/social_core/tests/backends/test_linkedin.py @@ -41,23 +41,26 @@ def test_invalid_nonce(self): class BaseLinkedinTest: - user_data_url = "https://api.linkedin.com/v2/userinfo" + user_data_url = ( + "https://api.linkedin.com/v2/me" "?projection=(firstName,id,lastName)" + ) expected_username = "FooBar" access_token_body = json.dumps({"access_token": "foobar", "token_type": "bearer"}) # Reference: - # https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self - # -serve/sign-in-with-linkedin-v2#response-body-schema + # https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self + # -serve/sign-in-with-linkedin?context=linkedin/consumer/context#api-request user_data_body = json.dumps( { - "sub": "782bbtaQ", - "name": "FooBar", - "given_name": "Foo", - "family_name": "Bar", - "picture": "https://media.licdn-ei.com/dms/image/C5F03AQHqK8v7tB1HCQ/profile-displayphoto-shrink_100_100/0/", - "locale": "en-US", - "email": "doe@email.com", - "email_verified": True, + "id": "1010101010", + "firstName": { + "localized": {"en_US": "Foo"}, + "preferredLocale": {"country": "US", "language": "en"}, + }, + "lastName": { + "localized": {"en_US": "Bar"}, + "preferredLocale": {"country": "US", "language": "en"}, + }, } ) From 81dbfe7d3aa6b0a03d19cdbc3b2402372fa5e130 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 20:24:57 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- social_core/tests/backends/test_linkedin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/social_core/tests/backends/test_linkedin.py b/social_core/tests/backends/test_linkedin.py index a4898f71d..0f3bcff04 100644 --- a/social_core/tests/backends/test_linkedin.py +++ b/social_core/tests/backends/test_linkedin.py @@ -41,9 +41,7 @@ def test_invalid_nonce(self): class BaseLinkedinTest: - user_data_url = ( - "https://api.linkedin.com/v2/me" "?projection=(firstName,id,lastName)" - ) + user_data_url = "https://api.linkedin.com/v2/me?projection=(firstName,id,lastName)" expected_username = "FooBar" access_token_body = json.dumps({"access_token": "foobar", "token_type": "bearer"}) From d316c17a9e84b8e60426148d440788da2bcb0eda Mon Sep 17 00:00:00 2001 From: yuekui Date: Tue, 6 May 2025 13:32:07 -0700 Subject: [PATCH 3/4] fix: RUF005 Consider iterable unpacking instead of concatenation --- social_core/backends/linkedin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/linkedin.py b/social_core/backends/linkedin.py index 7033d3786..3b4e34a46 100644 --- a/social_core/backends/linkedin.py +++ b/social_core/backends/linkedin.py @@ -67,7 +67,7 @@ class LinkedinOAuth2(BaseOAuth2): def user_details_url(self): # use set() since LinkedIn fails when values are duplicated fields_selectors = list( - set(["id", "firstName", "lastName"] + self.setting("FIELD_SELECTORS", [])) + set(["id", "firstName", "lastName", *self.setting("FIELD_SELECTORS", [])]) ) # user sort to ease the tests URL mocking fields_selectors.sort() From 03c7bc95cd53e2600a65f7c9111bbfc7faabc528 Mon Sep 17 00:00:00 2001 From: yuekui Date: Tue, 6 May 2025 13:33:36 -0700 Subject: [PATCH 4/4] fix: C405 Unnecessary list literal (rewrite as a set literal) --- social_core/backends/linkedin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/linkedin.py b/social_core/backends/linkedin.py index 3b4e34a46..d3c6ac76a 100644 --- a/social_core/backends/linkedin.py +++ b/social_core/backends/linkedin.py @@ -67,7 +67,7 @@ class LinkedinOAuth2(BaseOAuth2): def user_details_url(self): # use set() since LinkedIn fails when values are duplicated fields_selectors = list( - set(["id", "firstName", "lastName", *self.setting("FIELD_SELECTORS", [])]) + {"id", "firstName", "lastName", *self.setting("FIELD_SELECTORS", [])} ) # user sort to ease the tests URL mocking fields_selectors.sort()