diff --git a/social_core/backends/linkedin.py b/social_core/backends/linkedin.py index dcc49bb8c..d3c6ac76a 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( + {"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..0f3bcff04 100644 --- a/social_core/tests/backends/test_linkedin.py +++ b/social_core/tests/backends/test_linkedin.py @@ -41,23 +41,24 @@ 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"}, + }, } )