From e0cb88838ab3a0460dc92560c93b32a4824daa62 Mon Sep 17 00:00:00 2001 From: Richard Stromer Date: Tue, 31 Oct 2023 03:05:58 -0700 Subject: [PATCH] Add new backend for LinkedIn OpenID Connect (#833) * create linkedin openid connect backend * add docstring with link to official docs and deprecation notice for oauth2 * define oidc endpoint for linkedin * override token auth method to provide client id and secret in payload (not basic auth) * copy and override validate_claims to remove not support nonce validation * remove already disabled code section for nonce check * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add test case for linkedin openid (still breaking, inspired from google) * skip invalid nonce test as linkedin does not provide any nonce --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- social_core/backends/linkedin.py | 38 ++++++++++++++++++++- social_core/tests/backends/test_linkedin.py | 38 +++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/social_core/backends/linkedin.py b/social_core/backends/linkedin.py index 624dc21bc..07fbf0be5 100644 --- a/social_core/backends/linkedin.py +++ b/social_core/backends/linkedin.py @@ -2,11 +2,47 @@ LinkedIn OAuth1 and OAuth2 backend, docs at: https://python-social-auth.readthedocs.io/en/latest/backends/linkedin.html """ -from social_core.exceptions import AuthCanceled +import datetime +from calendar import timegm + +from social_core.backends.open_id_connect import OpenIdConnectAuth +from social_core.exceptions import AuthCanceled, AuthTokenError from .oauth import BaseOAuth2 +class LinkedinOpenIdConnect(OpenIdConnectAuth): + """ + Linkedin OpenID Connect backend. Oauth2 has been deprecated as of August 1, 2023. + https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2?context=linkedin/consumer/context + """ + + name = "linkedin-openidconnect" + # Settings from https://www.linkedin.com/oauth/.well-known/openid-configuration + OIDC_ENDPOINT = "https://www.linkedin.com/oauth" + + # https://developer.okta.com/docs/reference/api/oidc/#response-example-success-9 + # Override this value as it is not provided by Linkedin. + # else our request falls back to basic auth which is not supported. + TOKEN_ENDPOINT_AUTH_METHOD = "client_secret_post" + + def validate_claims(self, id_token): + """Copy of the regular validate_claims method without the nonce validation.""" + + utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple()) + + if "nbf" in id_token and utc_timestamp < id_token["nbf"]: + raise AuthTokenError(self, "Incorrect id_token: nbf") + + # Verify the token was issued in the last 10 minutes + iat_leeway = self.setting("ID_TOKEN_MAX_AGE", self.ID_TOKEN_MAX_AGE) + if utc_timestamp > id_token["iat"] + iat_leeway: + raise AuthTokenError(self, "Incorrect id_token: iat") + + # Skip the nonce validation for linkedin as it does not provide any nonce. + # https://stackoverflow.com/questions/76889585/issues-with-sign-in-with-linkedin-using-openid-connect + + class LinkedinOAuth2(BaseOAuth2): name = "linkedin-oauth2" AUTHORIZATION_URL = "https://www.linkedin.com/oauth/v2/authorization" diff --git a/social_core/tests/backends/test_linkedin.py b/social_core/tests/backends/test_linkedin.py index ff9f7d27c..b768d3172 100644 --- a/social_core/tests/backends/test_linkedin.py +++ b/social_core/tests/backends/test_linkedin.py @@ -1,6 +1,44 @@ import json from .oauth import OAuth2Test +from .test_open_id_connect import OpenIdConnectTestMixin + + +class LinkedinOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): + backend_path = "social_core.backends.linkedin.LinkedinOpenIdConnect" + user_data_url = "https://api.linkedin.com/v2/userinfo" + issuer = "https://www.linkedin.com" + openid_config_body = json.dumps( + { + "issuer": "https://www.linkedin.com", + "authorization_endpoint": "https://www.linkedin.com/oauth/v2/authorization", + "token_endpoint": "https://www.linkedin.com/oauth/v2/accessToken", + "userinfo_endpoint": "https://api.linkedin.com/v2/userinfo", + "jwks_uri": "https://www.linkedin.com/oauth/openid/jwks", + "response_types_supported": ["code"], + "subject_types_supported": ["pairwise"], + "id_token_signing_alg_values_supported": ["RS256"], + "scopes_supported": ["openid", "profile", "email"], + "claims_supported": [ + "iss", + "aud", + "iat", + "exp", + "sub", + "name", + "given_name", + "family_name", + "picture", + "email", + "email_verified", + "locale", + ], + } + ) + + def test_invalid_nonce(self): + """Skip the invalid nonce test as LinkedIn does not provide any nonce.""" + pass class BaseLinkedinTest: