From a21f0548bf2914c41880a1f417c58a4dc7e089bc Mon Sep 17 00:00:00 2001 From: strikki Date: Wed, 27 Jan 2016 18:20:02 +0300 Subject: [PATCH] Added optional 'include_email' query param for Twitter backend. --- docs/backends/twitter.rst | 5 + social/backends/twitter.py | 9 +- social/tests/backends/test_twitter.py | 132 ++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 3 deletions(-) diff --git a/docs/backends/twitter.rst b/docs/backends/twitter.rst index e40c41383..6fcedd811 100644 --- a/docs/backends/twitter.rst +++ b/docs/backends/twitter.rst @@ -20,6 +20,10 @@ To enable Twitter these two keys are needed. Further documentation at Client type instead of the Browser. Almost any dummy value will work if you plan some test. +- You can request user's Email address by setting (consult `Twitter verify credentials`_):: + + SOCIAL_AUTH_TWITTER_INCLUDE_EMAIL = True + Twitter usually fails with a 401 error when trying to call the request-token URL, this is usually caused by server datetime errors (check miscellaneous section). Installing ``ntp`` and syncing the server date with some pool does @@ -27,3 +31,4 @@ the trick. .. _Twitter development resources: http://dev.twitter.com/pages/auth .. _Twitter App Creation: http://twitter.com/apps/new +.. _Twitter verify credentials: https://dev.twitter.com/rest/reference/get/account/verify_credentials \ No newline at end of file diff --git a/social/backends/twitter.py b/social/backends/twitter.py index c41f15ab1..20d06fb7d 100644 --- a/social/backends/twitter.py +++ b/social/backends/twitter.py @@ -27,14 +27,17 @@ def get_user_details(self, response): """Return user details from Twitter account""" fullname, first_name, last_name = self.get_user_names(response['name']) return {'username': response['screen_name'], - 'email': '', # not supplied + 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" + url = 'https://api.twitter.com/1.1/account/verify_credentials.json' + if self.setting('INCLUDE_EMAIL', False): + url += '?include_email=true' return self.get_json( - 'https://api.twitter.com/1.1/account/verify_credentials.json', - auth=self.oauth_auth(access_token) + url, + auth=self.oauth_auth(access_token) ) diff --git a/social/tests/backends/test_twitter.py b/social/tests/backends/test_twitter.py index 430023776..d4eaef8e4 100644 --- a/social/tests/backends/test_twitter.py +++ b/social/tests/backends/test_twitter.py @@ -129,3 +129,135 @@ def test_login(self): def test_partial_pipeline(self): self.do_partial_pipeline() + + +class TwitterOAuth1IncludeEmailTest(OAuth1Test): + backend_path = 'social.backends.twitter.TwitterOAuth' + user_data_url = 'https://api.twitter.com/1.1/account/' \ + 'verify_credentials.json?include_email=true' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer' + }) + request_token_body = urlencode({ + 'oauth_token_secret': 'foobar-secret', + 'oauth_token': 'foobar', + 'oauth_callback_confirmed': 'true' + }) + user_data_body = json.dumps({ + 'follow_request_sent': False, + 'profile_use_background_image': True, + 'id': 10101010, + 'description': 'Foo bar baz qux', + 'verified': False, + 'entities': { + 'description': { + 'urls': [] + } + }, + 'profile_image_url_https': 'https://twimg0-a.akamaihd.net/' + 'profile_images/532018826/' + 'n587119531_1939735_9305_normal.jpg', + 'profile_sidebar_fill_color': '252429', + 'profile_text_color': '666666', + 'followers_count': 77, + 'profile_sidebar_border_color': '181A1E', + 'location': 'Fooland', + 'default_profile_image': False, + 'listed_count': 4, + 'status': { + 'favorited': False, + 'contributors': None, + 'retweeted_status': { + 'favorited': False, + 'contributors': None, + 'truncated': False, + 'source': 'web', + 'text': '"Foo foo foo foo', + 'created_at': 'Fri Dec 21 18:12:00 +0000 2012', + 'retweeted': True, + 'in_reply_to_status_id': None, + 'coordinates': None, + 'id': 101010101010101010, + 'entities': { + 'user_mentions': [], + 'hashtags': [], + 'urls': [] + }, + 'in_reply_to_status_id_str': None, + 'place': None, + 'id_str': '101010101010101010', + 'in_reply_to_screen_name': None, + 'retweet_count': 8, + 'geo': None, + 'in_reply_to_user_id_str': None, + 'in_reply_to_user_id': None + }, + 'truncated': False, + 'source': 'web', + 'text': 'RT @foo: "Foo foo foo foo', + 'created_at': 'Fri Dec 21 18:24:10 +0000 2012', + 'retweeted': True, + 'in_reply_to_status_id': None, + 'coordinates': None, + 'id': 101010101010101010, + 'entities': { + 'user_mentions': [{ + 'indices': [3, 10], + 'id': 10101010, + 'screen_name': 'foo', + 'id_str': '10101010', + 'name': 'Foo' + }], + 'hashtags': [], + 'urls': [] + }, + 'in_reply_to_status_id_str': None, + 'place': None, + 'id_str': '101010101010101010', + 'in_reply_to_screen_name': None, + 'retweet_count': 8, + 'geo': None, + 'in_reply_to_user_id_str': None, + 'in_reply_to_user_id': None + }, + 'utc_offset': -10800, + 'statuses_count': 191, + 'profile_background_color': '1A1B1F', + 'friends_count': 151, + 'profile_background_image_url_https': 'https://twimg0-a.akamaihd.net/' + 'images/themes/theme9/bg.gif', + 'profile_link_color': '2FC2EF', + 'profile_image_url': 'http://a0.twimg.com/profile_images/532018826/' + 'n587119531_1939735_9305_normal.jpg', + 'is_translator': False, + 'geo_enabled': False, + 'id_str': '74313638', + 'profile_background_image_url': 'http://a0.twimg.com/images/themes/' + 'theme9/bg.gif', + 'screen_name': 'foobar', + 'lang': 'en', + 'profile_background_tile': False, + 'favourites_count': 2, + 'name': 'Foo', + 'notifications': False, + 'url': None, + 'created_at': 'Tue Sep 15 00:26:17 +0000 2009', + 'contributors_enabled': False, + 'time_zone': 'Buenos Aires', + 'protected': False, + 'default_profile': False, + 'following': False, + 'email': 'foo@bar.bas' + }) + + def test_login(self): + self.strategy.set_settings({ + 'SOCIAL_AUTH_TWITTER_INCLUDE_EMAIL': True + }) + user = self.do_login() + self.assertEquals(user.email, 'foo@bar.bas') + + def test_partial_pipeline(self): + self.do_partial_pipeline()