diff --git a/common/djangoapps/third_party_auth/models.py b/common/djangoapps/third_party_auth/models.py index fcabea6c89f8..79ba5746bf36 100644 --- a/common/djangoapps/third_party_auth/models.py +++ b/common/djangoapps/third_party_auth/models.py @@ -12,7 +12,7 @@ import json import logging from social.backends.base import BaseAuth -from social.backends.oauth import BaseOAuth2 +from social.backends.oauth import OAuthAuth from social.backends.saml import SAMLAuth, SAMLIdentityProvider from social.exceptions import SocialAuthBaseException from social.utils import module_member @@ -30,7 +30,7 @@ def _load_backend_classes(base_class=BaseAuth): if issubclass(auth_class, base_class): yield auth_class _PSA_BACKENDS = {backend_class.name: backend_class for backend_class in _load_backend_classes()} -_PSA_OAUTH2_BACKENDS = [backend_class.name for backend_class in _load_backend_classes(BaseOAuth2)] +_PSA_OAUTH2_BACKENDS = [backend_class.name for backend_class in _load_backend_classes(OAuthAuth)] _PSA_SAML_BACKENDS = [backend_class.name for backend_class in _load_backend_classes(SAMLAuth)] @@ -166,6 +166,7 @@ def get_authentication_backend(self): class OAuth2ProviderConfig(ProviderConfig): """ Configuration Entry for an OAuth2 based provider. + Also works for OAuth1 providers. """ prefix = 'oa2' KEY_FIELDS = ('backend_name', ) # Backend name is unique @@ -191,7 +192,7 @@ class OAuth2ProviderConfig(ProviderConfig): other_settings = models.TextField(blank=True, help_text="Optional JSON object with advanced settings, if any.") class Meta(object): # pylint: disable=missing-docstring - verbose_name = "Provider Configuration (OAuth2)" + verbose_name = "Provider Configuration (OAuth)" verbose_name_plural = verbose_name def clean(self): diff --git a/common/djangoapps/third_party_auth/strategy.py b/common/djangoapps/third_party_auth/strategy.py index 1d5134c6bd86..eeff362ff16d 100644 --- a/common/djangoapps/third_party_auth/strategy.py +++ b/common/djangoapps/third_party_auth/strategy.py @@ -3,7 +3,7 @@ ConfigurationModels rather than django.settings """ from .models import OAuth2ProviderConfig -from social.backends.oauth import BaseOAuth2 +from social.backends.oauth import OAuthAuth from social.strategies.django_strategy import DjangoStrategy @@ -17,11 +17,11 @@ def setting(self, name, default=None, backend=None): Load the setting from a ConfigurationModel if possible, or fall back to the normal Django settings lookup. - BaseOAuth2 subclasses will call this method for every setting they want to look up. + OAuthAuth subclasses will call this method for every setting they want to look up. SAMLAuthBackend subclasses will call this method only after first checking if the setting 'name' is configured via SAMLProviderConfig. """ - if isinstance(backend, BaseOAuth2): + if isinstance(backend, OAuthAuth): provider_config = OAuth2ProviderConfig.current(backend.name) if not provider_config.enabled: raise Exception("Can't fetch setting of a disabled backend/provider.") diff --git a/common/djangoapps/third_party_auth/tests/specs/test_twitter.py b/common/djangoapps/third_party_auth/tests/specs/test_twitter.py new file mode 100644 index 000000000000..50377d549ed7 --- /dev/null +++ b/common/djangoapps/third_party_auth/tests/specs/test_twitter.py @@ -0,0 +1,45 @@ +""" +Separate integration test for Twitter which is an OAuth1 provider. +""" + +from mock import patch +from third_party_auth.tests.specs import base + + +class TwitterIntegrationTest(base.Oauth2IntegrationTest): + """Integration tests for Twitter backend.""" + + def setUp(self): + super(TwitterIntegrationTest, self).setUp() + self.provider = self.configure_twitter_provider( + enabled=True, + key='twitter_oauth1_key', + secret='twitter_oauth1_secret', + ) + + # To test an OAuth1 provider, we need to patch an additional method: + patcher = patch( + 'social.backends.twitter.TwitterOAuth.unauthorized_token', + create=True, + return_value="unauth_token" + ) + patcher.start() + self.addCleanup(patcher.stop) + + TOKEN_RESPONSE_DATA = { + 'access_token': 'access_token_value', + 'token_type': 'bearer', + } + USER_RESPONSE_DATA = { + 'id': 10101010, + 'name': 'Bob Loblaw', + 'description': 'A Twitter User', + 'screen_name': 'bobloblaw', + 'location': 'Twitterverse', + 'followers_count': 77, + 'verified': False, + } + + def get_username(self): + response_data = self.get_response_data() + return response_data.get('screen_name') diff --git a/common/djangoapps/third_party_auth/tests/testutil.py b/common/djangoapps/third_party_auth/tests/testutil.py index 323e57142b0e..12022d2bf75f 100644 --- a/common/djangoapps/third_party_auth/tests/testutil.py +++ b/common/djangoapps/third_party_auth/tests/testutil.py @@ -82,6 +82,16 @@ def configure_linkedin_provider(cls, **kwargs): kwargs.setdefault("secret", "test") return cls.configure_oauth_provider(**kwargs) + @classmethod + def configure_twitter_provider(cls, **kwargs): + """ Update the settings for the Twitter third party auth provider/backend """ + kwargs.setdefault("name", "Twitter") + kwargs.setdefault("backend_name", "twitter") + kwargs.setdefault("icon_class", "fa-twitter") + kwargs.setdefault("key", "test") + kwargs.setdefault("secret", "test") + return cls.configure_oauth_provider(**kwargs) + class TestCase(ThirdPartyAuthTestMixin, django.test.TestCase): """Base class for auth test cases.""" diff --git a/lms/envs/test.py b/lms/envs/test.py index e4315af287d2..06455e4d8922 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -247,6 +247,7 @@ 'social.backends.google.GoogleOAuth2', 'social.backends.linkedin.LinkedinOAuth2', 'social.backends.facebook.FacebookOAuth2', + 'social.backends.twitter.TwitterOAuth', 'third_party_auth.dummy.DummyBackend', 'third_party_auth.saml.SAMLAuthBackend', ) + AUTHENTICATION_BACKENDS