From 6f78a26591116843cb1257995e3564712d91ac0c Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Thu, 7 May 2020 15:10:42 -0400 Subject: [PATCH 01/17] refactored instagram backend --- social_core/backends/instagram.py | 80 ++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index cc138e576..9e7296437 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -11,43 +11,79 @@ class InstagramOAuth2(BaseOAuth2): name = 'instagram' + RESPONSE_TYPE = None + SCOPE_SEPARATOR = ',' AUTHORIZATION_URL = 'https://api.instagram.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://api.instagram.com/oauth/access_token' + USER_DATA_URL = 'https://graph.instagram.com/me/' + REFRESH_TOKEN_URL = 'https://graph.instagram.com/refresh_access_token' ACCESS_TOKEN_METHOD = 'POST' + def get_redirect_uri(self, state=None): + """Build redirect with redirect_state parameter.""" + # Facebook and Instagram redirect_uri can accept states now + # but the key must be 'state' or it'll cancel authentication + uri = self.redirect_uri + if self.REDIRECT_STATE and state: + uri = url_add_parameters(uri, {'state': state}) + return uri + + def auth_params(self, state=None): + params = super(InstagramOAuth2, self).auth_params(state) + params['return_scopes'] = 'true' + return params + def get_user_id(self, details, response): - # Sometimes Instagram returns 'user', sometimes 'data', but API docs - # says 'data' http://instagram.com/developer/endpoints/users/#get_users - user = response.get('user') or response.get('data') or {} - return user.get('id') + # https://developers.facebook.com/docs/instagram-basic-display-api/reference/me + user_id = response.get('id') or {} + return user_id def get_user_details(self, response): """Return user details from Instagram account""" - # Sometimes Instagram returns 'user', sometimes 'data', but API docs - # says 'data' http://instagram.com/developer/endpoints/users/#get_users - user = response.get('user') or response.get('data') or {} - username = user['username'] - email = user.get('email', '') + # https://developers.facebook.com/docs/instagram-basic-display-api/reference/me fullname, first_name, last_name = self.get_user_names( user.get('full_name', '') ) - return {'username': username, + return {'username': response.get('username', response.get('name')), + 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, - 'last_name': last_name, - 'email': email} + 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" - key, secret = self.get_key_and_secret() - params = {'access_token': access_token} - sig = self._generate_sig("/users/self", params, secret) - params['sig'] = sig - return self.get_json('https://api.instagram.com/v1/users/self', + params = self.setting('PROFILE_EXTRA_PARAMS', {}) + params['access_token'] = access_token + return self.get_json(self.USER_DATA_URL, params=params) - def _generate_sig(self, endpoint, params, secret): - sig = endpoint - for key in sorted(params.keys()): - sig += '|%s=%s' % (key, params[key]) - return hmac.new(secret.encode(), sig.encode(), sha256).hexdigest() + def process_error(self, data): + super(InstagramOAuth2, self).process_error(data) + if data.get('error_code'): + raise AuthCanceled(self, data.get('error_message') or + data.get('error_code')) + + @handle_http_errors + def auth_complete(self, *args, **kwargs): + """Completes login process, must return user instance""" + self.process_error(self.data) + if not self.data.get('code'): + raise AuthMissingParameter(self, 'code') + state = self.validate_state() + key, secret = self.get_key_and_secret() + response = self.request(ACCESS_TOKEN_URL, params={ + 'client_id': key, + 'client_secret': secret, + 'code': self.data['code'], + 'grant_type': 'authorization_code' + 'redirect_uri': self.get_redirect_uri(state), + }) + access_token = response['access_token'] + return self.do_auth(access_token, response, *args, **kwargs) + + + def refresh_token_params(self, token, *args, **kwargs): + return { + 'access_token': token, + 'grant_type': 'ig_refresh_token', + } From 3721c41ab75003e5627731a0cb03891eb803e4a1 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Thu, 7 May 2020 15:40:31 -0400 Subject: [PATCH 02/17] updated changelog to reflect instagram refactor change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d10567f..a16bcfac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Updated list of default user protected fields to include admin flags and password +- Instagram: Update to use the latest Graph API v3.2 ## [3.3.2](https://github.com/python-social-auth/social-core/releases/tag/3.3.2) - 2020-03-25 From 9f64952b693a8d5df7c5458e9ac6e55a90380c31 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Thu, 7 May 2020 16:27:00 -0400 Subject: [PATCH 03/17] removed unnecessary parameter from auth_params function --- social_core/backends/instagram.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 9e7296437..92e243317 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -28,11 +28,6 @@ def get_redirect_uri(self, state=None): uri = url_add_parameters(uri, {'state': state}) return uri - def auth_params(self, state=None): - params = super(InstagramOAuth2, self).auth_params(state) - params['return_scopes'] = 'true' - return params - def get_user_id(self, details, response): # https://developers.facebook.com/docs/instagram-basic-display-api/reference/me user_id = response.get('id') or {} From 4da31d4c8e99e8b2722d25dabbaaaa260da5d897 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 14:53:27 -0400 Subject: [PATCH 04/17] added missing comma --- social_core/backends/instagram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 92e243317..7b1d739d4 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -70,7 +70,7 @@ def auth_complete(self, *args, **kwargs): 'client_id': key, 'client_secret': secret, 'code': self.data['code'], - 'grant_type': 'authorization_code' + 'grant_type': 'authorization_code', 'redirect_uri': self.get_redirect_uri(state), }) access_token = response['access_token'] From 7984dcadc1a9fc37479029dcddea022092ed11e5 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 15:33:52 -0400 Subject: [PATCH 05/17] changed redirect_state default value --- social_core/backends/instagram.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 7b1d739d4..52a59daf2 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -12,6 +12,7 @@ class InstagramOAuth2(BaseOAuth2): name = 'instagram' RESPONSE_TYPE = None + REDIRECT_STATE = False SCOPE_SEPARATOR = ',' AUTHORIZATION_URL = 'https://api.instagram.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://api.instagram.com/oauth/access_token' @@ -19,15 +20,6 @@ class InstagramOAuth2(BaseOAuth2): REFRESH_TOKEN_URL = 'https://graph.instagram.com/refresh_access_token' ACCESS_TOKEN_METHOD = 'POST' - def get_redirect_uri(self, state=None): - """Build redirect with redirect_state parameter.""" - # Facebook and Instagram redirect_uri can accept states now - # but the key must be 'state' or it'll cancel authentication - uri = self.redirect_uri - if self.REDIRECT_STATE and state: - uri = url_add_parameters(uri, {'state': state}) - return uri - def get_user_id(self, details, response): # https://developers.facebook.com/docs/instagram-basic-display-api/reference/me user_id = response.get('id') or {} From 24e97af25a43ca9182d233000ef07e5301c2f255 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 15:42:54 -0400 Subject: [PATCH 06/17] added missing imports --- social_core/backends/instagram.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 52a59daf2..679accf27 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -3,10 +3,13 @@ https://python-social-auth.readthedocs.io/en/latest/backends/instagram.html """ import hmac +import json from hashlib import sha256 from .oauth import BaseOAuth2 +from ..utils import handle_http_errors +from ..exceptions import AuthCanceled, AuthMissingParameter class InstagramOAuth2(BaseOAuth2): From 47012825616d260de5952b35597ca1c70399eff1 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 16:01:31 -0400 Subject: [PATCH 07/17] added response type to auth params --- social_core/backends/instagram.py | 1 - 1 file changed, 1 deletion(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 679accf27..55110c38b 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -14,7 +14,6 @@ class InstagramOAuth2(BaseOAuth2): name = 'instagram' - RESPONSE_TYPE = None REDIRECT_STATE = False SCOPE_SEPARATOR = ',' AUTHORIZATION_URL = 'https://api.instagram.com/oauth/authorize' From ed8d26c3d1aa447633641e447e1f35ae7291e2b6 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 16:29:08 -0400 Subject: [PATCH 08/17] fixed access token url error --- social_core/backends/instagram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 55110c38b..2a635087d 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -60,7 +60,7 @@ def auth_complete(self, *args, **kwargs): raise AuthMissingParameter(self, 'code') state = self.validate_state() key, secret = self.get_key_and_secret() - response = self.request(ACCESS_TOKEN_URL, params={ + response = self.request(self.ACCESS_TOKEN_URL, params={ 'client_id': key, 'client_secret': secret, 'code': self.data['code'], From 9d914c3b456b712e98d2135e068fed7817b10162 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 16:54:44 -0400 Subject: [PATCH 09/17] fixed access_token url method --- social_core/backends/instagram.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 2a635087d..b4afb7d2f 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -60,13 +60,17 @@ def auth_complete(self, *args, **kwargs): raise AuthMissingParameter(self, 'code') state = self.validate_state() key, secret = self.get_key_and_secret() - response = self.request(self.ACCESS_TOKEN_URL, params={ - 'client_id': key, - 'client_secret': secret, - 'code': self.data['code'], - 'grant_type': 'authorization_code', - 'redirect_uri': self.get_redirect_uri(state), - }) + response = self.request( + self.ACCESS_TOKEN_URL, + params={ + 'client_id': key, + 'client_secret': secret, + 'code': self.data['code'], + 'grant_type': 'authorization_code', + 'redirect_uri': self.get_redirect_uri(state), + }, + method=self.ACCESS_TOKEN_METHOD + ) access_token = response['access_token'] return self.do_auth(access_token, response, *args, **kwargs) From 5255c792d46e495947963ef150edceee72ed4b86 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 17:46:03 -0400 Subject: [PATCH 10/17] api.instagram uses urlencoded parameters --- social_core/backends/instagram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index b4afb7d2f..d2b89dd63 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -62,7 +62,7 @@ def auth_complete(self, *args, **kwargs): key, secret = self.get_key_and_secret() response = self.request( self.ACCESS_TOKEN_URL, - params={ + data={ 'client_id': key, 'client_secret': secret, 'code': self.data['code'], From 2533f1d992291ad837ef8901fc6c376019897ade Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 18:50:06 -0400 Subject: [PATCH 11/17] response to access token not subscritable --- social_core/backends/instagram.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index d2b89dd63..2cb805a33 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -8,7 +8,7 @@ from hashlib import sha256 from .oauth import BaseOAuth2 -from ..utils import handle_http_errors +from ..utils import handle_http_errors, parse_qs from ..exceptions import AuthCanceled, AuthMissingParameter @@ -71,6 +71,13 @@ def auth_complete(self, *args, **kwargs): }, method=self.ACCESS_TOKEN_METHOD ) + # API v2.3 returns a JSON, according to the documents linked at issue + # #592, but it seems that this needs to be enabled(?), otherwise the + # usual querystring type response is returned. + try: + response = response.json() + except ValueError: + response = parse_qs(response.text) access_token = response['access_token'] return self.do_auth(access_token, response, *args, **kwargs) From 3b5708b752951b4709f7df25b01400632607120f Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 19:55:52 -0400 Subject: [PATCH 12/17] self.strategy.authenticate expects backend argument --- social_core/backends/instagram.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 2cb805a33..1c8d0f92a 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -52,6 +52,17 @@ def process_error(self, data): raise AuthCanceled(self, data.get('error_message') or data.get('error_code')) + @handle_http_errors + def do_auth(self, access_token, *args, **kwargs): + """Finish the auth process once the access_token was retrieved""" + data = self.user_data(access_token, *args, **kwargs) + response = kwargs.get('response') or {} + response.update(data or {}) + if 'access_token' not in response: + response['access_token'] = access_token + kwargs.update({'response': response}) + return self.strategy.authenticate(backend=self, *args, **kwargs) + @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" From e53a9c217868b9faa48ff82a2e5350f2512829ef Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 21:12:29 -0400 Subject: [PATCH 13/17] fixed self.strategy.authenticate argument placement --- social_core/backends/facebook.py | 4 ++-- social_core/backends/instagram.py | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/social_core/backends/facebook.py b/social_core/backends/facebook.py index 475761c87..f146c17cc 100644 --- a/social_core/backends/facebook.py +++ b/social_core/backends/facebook.py @@ -149,8 +149,8 @@ def do_auth(self, access_token, response=None, *args, **kwargs): if self.data.get('denied_scopes'): data['denied_scopes'] = self.data['denied_scopes'].split(',') - kwargs.update({'backend': self, 'response': data}) - return self.strategy.authenticate(*args, **kwargs) + kwargs.update({'response': data}) + return self.strategy.authenticate(self,*args, **kwargs) def revoke_token_url(self, token, uid): version = self.setting('API_VERSION', API_VERSION) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 1c8d0f92a..15d0df528 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -22,10 +22,6 @@ class InstagramOAuth2(BaseOAuth2): REFRESH_TOKEN_URL = 'https://graph.instagram.com/refresh_access_token' ACCESS_TOKEN_METHOD = 'POST' - def get_user_id(self, details, response): - # https://developers.facebook.com/docs/instagram-basic-display-api/reference/me - user_id = response.get('id') or {} - return user_id def get_user_details(self, response): """Return user details from Instagram account""" @@ -60,8 +56,8 @@ def do_auth(self, access_token, *args, **kwargs): response.update(data or {}) if 'access_token' not in response: response['access_token'] = access_token - kwargs.update({'response': response}) - return self.strategy.authenticate(backend=self, *args, **kwargs) + kwargs.update({'backend': self, 'response': data}) + return self.strategy.authenticate(*args, **kwargs) @handle_http_errors def auth_complete(self, *args, **kwargs): From bf02eaba74be8dd4cc1f346536e2cb6231a4acd3 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 21:27:59 -0400 Subject: [PATCH 14/17] fixed self.strategy.authenticate args change --- social_core/backends/instagram.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 15d0df528..d91739e8f 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -56,8 +56,9 @@ def do_auth(self, access_token, *args, **kwargs): response.update(data or {}) if 'access_token' not in response: response['access_token'] = access_token - kwargs.update({'backend': self, 'response': data}) - return self.strategy.authenticate(*args, **kwargs) + kwargs.update({'response': data}) + backend=self + return self.strategy.authenticate(backend, *args, **kwargs) @handle_http_errors def auth_complete(self, *args, **kwargs): From 522d515a96011ee92fdc8ac09d8c7c3e948cde09 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 21:52:45 -0400 Subject: [PATCH 15/17] fixed self.strategy.authenticate self args --- social_core/backends/instagram.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index d91739e8f..811c904f1 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -57,8 +57,7 @@ def do_auth(self, access_token, *args, **kwargs): if 'access_token' not in response: response['access_token'] = access_token kwargs.update({'response': data}) - backend=self - return self.strategy.authenticate(backend, *args, **kwargs) + return self.strategy.authenticate(self, *args, **kwargs) @handle_http_errors def auth_complete(self, *args, **kwargs): From 88bef53734e80e826f2af1506923399badaa53c2 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 22:30:34 -0400 Subject: [PATCH 16/17] fixed self.strategy.authenticate self args --- social_core/backends/instagram.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 811c904f1..44891cbeb 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -48,17 +48,6 @@ def process_error(self, data): raise AuthCanceled(self, data.get('error_message') or data.get('error_code')) - @handle_http_errors - def do_auth(self, access_token, *args, **kwargs): - """Finish the auth process once the access_token was retrieved""" - data = self.user_data(access_token, *args, **kwargs) - response = kwargs.get('response') or {} - response.update(data or {}) - if 'access_token' not in response: - response['access_token'] = access_token - kwargs.update({'response': data}) - return self.strategy.authenticate(self, *args, **kwargs) - @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" @@ -86,7 +75,7 @@ def auth_complete(self, *args, **kwargs): except ValueError: response = parse_qs(response.text) access_token = response['access_token'] - return self.do_auth(access_token, response, *args, **kwargs) + return self.do_auth(access_token, response=response, *args, **kwargs) def refresh_token_params(self, token, *args, **kwargs): From c8c0a6bc78385e7f8fa322b9eb0f75af028ffca9 Mon Sep 17 00:00:00 2001 From: Nana Adjedu Date: Fri, 8 May 2020 22:39:22 -0400 Subject: [PATCH 17/17] fixed user_details --- social_core/backends/instagram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/instagram.py b/social_core/backends/instagram.py index 44891cbeb..bfa1c804e 100644 --- a/social_core/backends/instagram.py +++ b/social_core/backends/instagram.py @@ -27,7 +27,7 @@ def get_user_details(self, response): """Return user details from Instagram account""" # https://developers.facebook.com/docs/instagram-basic-display-api/reference/me fullname, first_name, last_name = self.get_user_names( - user.get('full_name', '') + response.get('full_name', '') ) return {'username': response.get('username', response.get('name')), 'email': response.get('email', ''),