[Python-modules-commits] [python-social-auth] 05/32: Added an OAuth2 backend for Bitbucket

Wolfgang Borgert debacle at moszumanska.debian.org
Sat Dec 24 15:13:45 UTC 2016


This is an automated email from the git hooks/post-receive script.

debacle pushed a commit to tag v0.2.12
in repository python-social-auth.

commit 2853cfb2ff11cd3d149ba563ad63fc3a948df254
Author: Mark Adams <mark at markadams.me>
Date:   Sun Jun 21 15:06:45 2015 -0500

    Added an OAuth2 backend for Bitbucket
---
 docs/backends/bitbucket.rst             |  37 ++++++----
 social/backends/bitbucket.py            |  74 +++++++++++++++----
 social/backends/oauth.py                |   8 +++
 social/tests/backends/test_bitbucket.py | 121 ++++++++++++++++++++++++--------
 4 files changed, 184 insertions(+), 56 deletions(-)

diff --git a/docs/backends/bitbucket.rst b/docs/backends/bitbucket.rst
index a66478b..00809a3 100644
--- a/docs/backends/bitbucket.rst
+++ b/docs/backends/bitbucket.rst
@@ -1,26 +1,37 @@
 Bitbucket
 =========
 
-Bitbucket works similar to Twitter OAuth.
+Bitbucket supports both OAuth2 and OAuth1 logins.
 
-- Register a new application by emailing ``support at bitbucket.org`` with an
-  application name and a bit of a description,
+1. Register a new OAuth Consumer by following the instructions in the
+   Bitbucket documentation: `OAuth on Bitbucket`_
+
+   Note: For OAuth2, your consumer MUST have the "account" scope otherwise
+   the user profile information (username, name, etc.) won't be accessible.
+
+2. Configure the appropriate settings for OAuth2 or OAuth1 (see below).
+
+OAuth2
+------
 
 - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings::
 
-      SOCIAL_AUTH_BITBUCKET_KEY = ''
-      SOCIAL_AUTH_BITBUCKET_SECRET = ''
+    SOCIAL_AUTH_BITBUCKET_OAUTH2_KEY = '<your-consumer-key>'
+    SOCIAL_AUTH_BITBUCKET_OAUTH2_SECRET = '<your-consumer-secret>'
 
+- If you would like to restrict access to only users with verified e-mail
+  addresses, set ``SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY = True``
 
+OAuth1
+------
 
-Settings
---------
+- OAuth1 works similarly to OAuth2, but you must fill in the following settings
+  instead::
 
-Sometimes Bitbucket users don't have a verified email address, making it
-impossible to get the basic user information to continue the auth process.
-It's possible to avoid these users with this setting::
+    SOCIAL_AUTH_BITBUCKET_KEY = '<your-consumer-key>'
+    SOCIAL_AUTH_BITBUCKET_SECRET = '<your-consumer-secret>'
 
-    SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY = True
+- If you would like to restrict access to only users with verified e-mail
+  addresses, set ``SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY = True``
 
-By default the setting is set to ``False`` since it's possible for a project to
-gather this information by other methods.
+.. _OAuth on Bitbucket: https://confluence.atlassian.com/display/BITBUCKET/OAuth+on+Bitbucket
diff --git a/social/backends/bitbucket.py b/social/backends/bitbucket.py
index c057465..fd63880 100644
--- a/social/backends/bitbucket.py
+++ b/social/backends/bitbucket.py
@@ -1,18 +1,12 @@
 """
-Bitbucket OAuth1 backend, docs at:
+Bitbucket OAuth2 and OAuth1 backends, docs at:
     http://psa.matiasaguirre.net/docs/backends/bitbucket.html
 """
 from social.exceptions import AuthForbidden
-from social.backends.oauth import BaseOAuth1
+from social.backends.oauth import BaseOAuth1, BaseOAuth2
 
 
-class BitbucketOAuth(BaseOAuth1):
-    """Bitbucket OAuth authentication backend"""
-    name = 'bitbucket'
-    AUTHORIZATION_URL = 'https://bitbucket.org/api/1.0/oauth/authenticate'
-    REQUEST_TOKEN_URL = 'https://bitbucket.org/api/1.0/oauth/request_token'
-    ACCESS_TOKEN_URL = 'https://bitbucket.org/api/1.0/oauth/access_token'
-
+class BitbucketOAuthBase(object):
     # Bitbucket usernames can change. The account ID should always be the UUID
     # See: https://confluence.atlassian.com/display/BITBUCKET/Use+the+Bitbucket+REST+APIs
     ID_KEY = 'uuid'
@@ -27,10 +21,9 @@ class BitbucketOAuth(BaseOAuth1):
                 'first_name': first_name,
                 'last_name': last_name}
 
-    def user_data(self, access_token):
+    def user_data(self, access_token, *args, **kwargs):
         """Return user data provided"""
-        emails = self.get_json('https://api.bitbucket.org/2.0/user/emails',
-                               auth=self.oauth_auth(access_token))
+        emails = self._get_emails(access_token)
 
         email = None
 
@@ -44,10 +37,63 @@ class BitbucketOAuth(BaseOAuth1):
                 self, 'Bitbucket account has no verified email'
             )
 
-        user = self.get_json('https://api.bitbucket.org/2.0/user',
-                             auth=self.oauth_auth(access_token))
+        user = self._get_user(access_token)
 
         if email:
             user['email'] = email
 
         return user
+
+    def _get_user(self, access_token=None):
+        raise NotImplementedError
+
+    def _get_emails(self, access_token=None):
+        raise NotImplementedError
+
+
+class BitbucketOAuth2(BitbucketOAuthBase, BaseOAuth2):
+    name = 'bitbucket-oauth2'
+    SCOPE_SEPARATOR = ' '
+    AUTHORIZATION_URL = 'https://bitbucket.org/site/oauth2/authorize'
+    ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token'
+    ACCESS_TOKEN_METHOD = 'POST'
+    REDIRECT_STATE = False
+    EXTRA_DATA = [
+        ('scopes', 'scopes'),
+        ('expires_in', 'expires'),
+        ('token_type', 'token_type'),
+        ('refresh_token', 'refresh_token')
+    ]
+
+    def auth_complete_credentials(self):
+        return self.get_key_and_secret()
+
+    def _get_user(self, access_token=None):
+        return self.get_json('https://api.bitbucket.org/2.0/user',
+                             params={'access_token': access_token})
+
+    def _get_emails(self, access_token=None):
+        return self.get_json('https://api.bitbucket.org/2.0/user/emails',
+                             params={'access_token': access_token})
+
+    def refresh_token(self, *args, **kwargs):
+        raise NotImplementedError('Refresh tokens for Bitbucket have not been implemented')
+
+
+class BitbucketOAuth(BitbucketOAuthBase, BaseOAuth1):
+    """Bitbucket OAuth authentication backend"""
+    name = 'bitbucket'
+    AUTHORIZATION_URL = 'https://bitbucket.org/api/1.0/oauth/authenticate'
+    REQUEST_TOKEN_URL = 'https://bitbucket.org/api/1.0/oauth/request_token'
+    ACCESS_TOKEN_URL = 'https://bitbucket.org/api/1.0/oauth/access_token'
+
+    def oauth_auth(self, *args, **kwargs):
+        return super(BitbucketOAuth, self).oauth_auth(*args, **kwargs)
+
+    def _get_user(self, access_token=None):
+        return self.get_json('https://api.bitbucket.org/2.0/user',
+                             auth=self.oauth_auth(access_token))
+
+    def _get_emails(self, access_token=None):
+        return self.get_json('https://api.bitbucket.org/2.0/user/emails',
+                             auth=self.oauth_auth(access_token))
diff --git a/social/backends/oauth.py b/social/backends/oauth.py
index d154606..0396495 100644
--- a/social/backends/oauth.py
+++ b/social/backends/oauth.py
@@ -350,6 +350,9 @@ class BaseOAuth2(OAuthAuth):
             'redirect_uri': self.get_redirect_uri(state)
         }
 
+    def auth_complete_credentials(self):
+        return None
+
     def auth_headers(self):
         return {'Content-Type': 'application/x-www-form-urlencoded',
                 'Accept': 'application/json'}
@@ -371,12 +374,15 @@ class BaseOAuth2(OAuthAuth):
         """Completes loging process, must return user instance"""
         state = self.validate_state()
         self.process_error(self.data)
+
         response = self.request_access_token(
             self.access_token_url(),
             data=self.auth_complete_params(state),
             headers=self.auth_headers(),
+            auth=self.auth_complete_credentials(),
             method=self.ACCESS_TOKEN_METHOD
         )
+        print(dict(response))
         self.process_error(response)
         return self.do_auth(response['access_token'], response=response,
                             *args, **kwargs)
@@ -384,6 +390,8 @@ class BaseOAuth2(OAuthAuth):
     @handle_http_errors
     def do_auth(self, access_token, *args, **kwargs):
         """Finish the auth process once the access_token was retrieved"""
+        print(args)
+        print(kwargs)
         data = self.user_data(access_token, *args, **kwargs)
         response = kwargs.get('response') or {}
         response.update(data or {})
diff --git a/social/tests/backends/test_bitbucket.py b/social/tests/backends/test_bitbucket.py
index 0c0f912..400ea5f 100644
--- a/social/tests/backends/test_bitbucket.py
+++ b/social/tests/backends/test_bitbucket.py
@@ -4,22 +4,32 @@ from httpretty import HTTPretty
 
 from social.p3 import urlencode
 from social.exceptions import AuthForbidden
-from social.tests.backends.oauth import OAuth1Test
+from social.tests.backends.oauth import OAuth1Test, OAuth2Test
 
 
-class BitbucketOAuth1Test(OAuth1Test):
-    backend_path = 'social.backends.bitbucket.BitbucketOAuth'
+class BitbucketOAuthMixin(object):
     user_data_url = 'https://api.bitbucket.org/2.0/user'
     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'
+    bb_api_user_emails = 'https://api.bitbucket.org/2.0/user/emails'
+
+    user_data_body = json.dumps({
+        u'created_on': u'2012-03-29T18:07:38+00:00',
+        u'display_name': u'Foo Bar',
+        u'links': {
+            u'avatar': {u'href': u'https://bitbucket.org/account/foobar/avatar/32/'},
+            u'followers': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/followers'},
+            u'following': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/following'},
+            u'hooks': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/hooks'},
+            u'html': {u'href': u'https://bitbucket.org/foobar'},
+            u'repositories': {u'href': u'https://api.bitbucket.org/2.0/repositories/foobar'},
+            u'self': {u'href': u'https://api.bitbucket.org/2.0/users/foobar'}},
+        u'location': u'Fooville, Bar',
+        u'type': u'user',
+        u'username': u'foobar',
+        u'uuid': u'{397621dc-0f78-329f-8d6d-727396248e3f}',
+        u'website': u'http://foobar.com'
     })
+
     emails_body = json.dumps({
         u'page': 1,
         u'pagelen': 10,
@@ -41,33 +51,31 @@ class BitbucketOAuth1Test(OAuth1Test):
             }
         ]
     })
-    user_data_body = json.dumps({
-        u'created_on': u'2012-03-29T18:07:38+00:00',
-        u'display_name': u'Foo Bar',
-        u'links': {
-            u'avatar': {u'href': u'https://bitbucket.org/account/foobar/avatar/32/'},
-            u'followers': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/followers'},
-            u'following': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/following'},
-            u'hooks': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/hooks'},
-            u'html': {u'href': u'https://bitbucket.org/foobar'},
-            u'repositories': {u'href': u'https://api.bitbucket.org/2.0/repositories/foobar'},
-            u'self': {u'href': u'https://api.bitbucket.org/2.0/users/foobar'}},
-        u'location': u'Fooville, Bar',
-        u'type': u'user',
-        u'username': u'foobar',
-        u'uuid': u'{397621dc-0f78-329f-8d6d-727396248e3f}',
-        u'website': u'http://foobar.com'
+
+
+class BitbucketOAuth1Test(BitbucketOAuthMixin, OAuth1Test):
+    backend_path = 'social.backends.bitbucket.BitbucketOAuth'
+
+    request_token_body = urlencode({
+        'oauth_token_secret': 'foobar-secret',
+        'oauth_token': 'foobar',
+        'oauth_callback_confirmed': 'true'
+    })
+
+    access_token_body = json.dumps({
+        'access_token': 'foobar',
+        'token_type': 'bearer'
     })
 
     def test_login(self):
         HTTPretty.register_uri(HTTPretty.GET,
-                               'https://api.bitbucket.org/2.0/user/emails',
+                               self.bb_api_user_emails,
                                status=200, body=self.emails_body)
         self.do_login()
 
     def test_partial_pipeline(self):
         HTTPretty.register_uri(HTTPretty.GET,
-                               'https://api.bitbucket.org/2.0/user/emails',
+                               self.bb_api_user_emails,
                                status=200, body=self.emails_body)
         self.do_partial_pipeline()
 
@@ -101,3 +109,58 @@ class BitbucketOAuth1FailTest(BitbucketOAuth1Test):
         })
         with self.assertRaises(AuthForbidden):
             super(BitbucketOAuth1FailTest, self).test_partial_pipeline()
+
+
+class BitbucketOAuth2Test(BitbucketOAuthMixin, OAuth2Test):
+    backend_path = 'social.backends.bitbucket.BitbucketOAuth2'
+
+    access_token_body = json.dumps({
+        'access_token': 'foobar_access',
+        'scopes': 'foo_scope',
+        'expires_in': 3600,
+        'refresh_token': 'foobar_refresh',
+        'token_type': 'bearer'
+    })
+
+    def test_login(self):
+        HTTPretty.register_uri(HTTPretty.GET,
+                               self.bb_api_user_emails,
+                               status=200, body=self.emails_body)
+        self.do_login()
+
+    def test_partial_pipeline(self):
+        HTTPretty.register_uri(HTTPretty.GET,
+                               self.bb_api_user_emails,
+                               status=200, body=self.emails_body)
+        self.do_partial_pipeline()
+
+
+class BitbucketOAuth2FailTest(BitbucketOAuth2Test):
+    emails_body = json.dumps({
+        u'page': 1,
+        u'pagelen': 10,
+        u'size': 1,
+        u'values': [
+            {
+                u'email': u'foo at bar.com',
+                u'is_confirmed': False,
+                u'is_primary': True,
+                u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}},
+                u'type': u'email'
+            }
+        ]
+    })
+
+    def test_login(self):
+        self.strategy.set_settings({
+            'SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY': True
+        })
+        with self.assertRaises(AuthForbidden):
+            super(BitbucketOAuth2FailTest, self).test_login()
+
+    def test_partial_pipeline(self):
+        self.strategy.set_settings({
+            'SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY': True
+        })
+        with self.assertRaises(AuthForbidden):
+            super(BitbucketOAuth2FailTest, self).test_partial_pipeline()

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-social-auth.git



More information about the Python-modules-commits mailing list