diff --git a/README.rst b/README.rst index 13cf1ba7f..0cf190af7 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,7 @@ +**NOTE: THIS LIBRARY IS DEPRECATED IN FAVOR OF** `python-social-auth`_. **RIGHT NOW +THIS LIBRARY DEPENDS DIRECTLY ON** `python-social-auth`_ **AND SHOULD BE CONSIDERED +AS A MIGRATION STEP** + Django Social Auth ================== @@ -10,6 +14,7 @@ third parties. You can view this app's documentation on `Read the Docs`_ too. + .. contents:: Table of Contents @@ -214,3 +219,4 @@ Some bits were derived from others' work and copyrighted by: .. _Fedora OpenID: https://fedoraproject.org/wiki/OpenID .. _Exacttarget HubExchange: http://code.exacttarget.com/ .. _Appsfuel OAuth2: http://docs.appsfuel.com/api_reference#api_reference +.. _python-social-auth: https://github.com/omab/python-social-auth diff --git a/example/app/facebook.py b/example/app/facebook.py index eb2a71a31..60e2264ca 100644 --- a/example/app/facebook.py +++ b/example/app/facebook.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.contrib.auth import BACKEND_SESSION_KEY from django.contrib.auth.models import AnonymousUser from django.http import HttpResponse @@ -9,11 +10,15 @@ from social_auth.models import UserSocialAuth from social_auth.views import complete as social_complete -from social_auth.utils import setting -from social_auth.backends.facebook import load_signed_request, FacebookBackend +from social_auth.backends.facebook import FacebookBackend + def is_complete_authentication(request): - return request.user.is_authenticated() and FacebookBackend.__name__ in request.session.get(BACKEND_SESSION_KEY, '') + return request.user.is_authenticated() and \ + FacebookBackend.__name__ in request.session.get( + BACKEND_SESSION_KEY, '' + ) + def get_access_token(user): key = str(user.id) @@ -23,7 +28,9 @@ def get_access_token(user): if access_token is None: try: social_user = user.social_user if hasattr(user, 'social_user') \ - else UserSocialAuth.objects.get(user=user.id, provider=FacebookBackend.name) + else UserSocialAuth.objects.get( + user=user.id, provider=FacebookBackend.name + ) except UserSocialAuth.DoesNotExist: return None @@ -31,33 +38,37 @@ def get_access_token(user): access_token = social_user.extra_data.get('access_token') expires = social_user.extra_data.get('expires') - cache.set(key, access_token, int(expires) if expires is not None else 0) - + cache.set(key, access_token, int(expires) if expires is not None + else 0) return access_token + # Facebook decorator to setup environment def facebook_decorator(func): def wrapper(request, *args, **kwargs): user = request.user - # User must me logged via FB backend in order to ensure we talk about the same person + # User must me logged via FB backend in order to ensure we talk about + # the same person if not is_complete_authentication(request): try: user = social_complete(request, FacebookBackend.name) except ValueError: - pass # no matter if failed + pass # no matter if failed - # Not recommended way for FB, but still something we need to be aware of + # Not recommended way for FB, but still something we need to be aware + # of if isinstance(user, HttpResponse): kwargs.update({'auth_response': user}) - # Need to re-check the completion - else: + else: # Need to re-check the completion if is_complete_authentication(request): kwargs.update({'access_token': get_access_token(request.user)}) else: request.user = AnonymousUser() - signed_request = load_signed_request(request.REQUEST.get('signed_request', '')) + signed_request = FacebookBackend().load_signed_request( + request.REQUEST.get('signed_request', '') + ) if signed_request: kwargs.update({'signed_request': signed_request}) @@ -70,9 +81,10 @@ def wrapper(request, *args, **kwargs): @facebook_decorator def facebook_view(request, *args, **kwargs): # If there is a ready response just return it. Not recommended though. - auth_response = kwargs.get('auth_response') + auth_response = kwargs.get('auth_response') if auth_response: return auth_response - - return render_to_response('facebook.html', {'fb_app_id':setting('FACEBOOK_APP_ID'), - 'warning': request.method == 'GET'}, RequestContext(request)) \ No newline at end of file + return render_to_response('facebook.html', { + 'fb_app_id': getattr(settings, 'FACEBOOK_APP_ID', None), + 'warning': request.method == 'GET' + }, RequestContext(request)) diff --git a/example/app/pipeline.py b/example/app/pipeline.py index 2cb2288e2..0889575ad 100644 --- a/example/app/pipeline.py +++ b/example/app/pipeline.py @@ -1,27 +1,26 @@ from django.http import HttpResponseRedirect -def redirect_to_form(*args, **kwargs): - if not kwargs['request'].session.get('saved_username') and \ - kwargs.get('user') is None: +def redirect_to_form(strategy, user=None, *args, **kwargs): + if not strategy.session_get('saved_username') and user is None: return HttpResponseRedirect('/form/') -def username(request, *args, **kwargs): - if kwargs.get('user'): - username = kwargs['user'].username +def username(strategy, user=None, *args, **kwargs): + if user: + username = user.username else: - username = request.session.get('saved_username') + username = strategy.session_get('saved_username') return {'username': username} -def redirect_to_form2(*args, **kwargs): - if not kwargs['request'].session.get('saved_first_name'): +def redirect_to_form2(strategy, *args, **kwargs): + if strategy.session_get('saved_first_name'): return HttpResponseRedirect('/form2/') -def first_name(request, *args, **kwargs): - if 'saved_first_name' in request.session: +def first_name(strategy, *args, **kwargs): + if strategy.session_get('saved_first_name'): user = kwargs['user'] - user.first_name = request.session.get('saved_first_name') + user.first_name = strategy.session_get('saved_first_name') user.save() diff --git a/example/app/views.py b/example/app/views.py index da37c31fd..1be60a655 100644 --- a/example/app/views.py +++ b/example/app/views.py @@ -6,7 +6,6 @@ from django.contrib.messages.api import get_messages from social_auth import __version__ as version -from social_auth.utils import setting def home(request): @@ -44,9 +43,8 @@ def logout(request): def form(request): if request.method == 'POST' and request.POST.get('username'): - name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline') request.session['saved_username'] = request.POST['username'] - backend = request.session[name]['backend'] + backend = request.session['partial_pipeline']['backend'] return redirect('socialauth_complete', backend=backend) return render_to_response('form.html', {}, RequestContext(request)) @@ -54,8 +52,7 @@ def form(request): def form2(request): if request.method == 'POST' and request.POST.get('first_name'): request.session['saved_first_name'] = request.POST['first_name'] - name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline') - backend = request.session[name]['backend'] + backend = request.session['partial_pipeline']['backend'] return redirect('socialauth_complete', backend=backend) return render_to_response('form2.html', {}, RequestContext(request)) diff --git a/example/app/vkontakte.py b/example/app/vkontakte.py index 54a8bc0e6..be1b2505c 100644 --- a/example/app/vkontakte.py +++ b/example/app/vkontakte.py @@ -9,11 +9,15 @@ from social_auth.models import UserSocialAuth from social_auth.views import complete as social_complete -from social_auth.utils import setting -from social_auth.backends.contrib.vk import VKOAuth2Backend, vk_api +from social_auth.backends.contrib.vk import VKOAuth2Backend + def is_complete_authentication(request): - return request.user.is_authenticated() and VKOAuth2Backend.__name__ in request.session.get(BACKEND_SESSION_KEY, '') + return request.user.is_authenticated() and \ + VKOAuth2Backend.__name__ in request.session.get( + BACKEND_SESSION_KEY, '' + ) + def get_access_token(user): key = str(user.id) @@ -23,7 +27,10 @@ def get_access_token(user): if access_token is None: try: social_user = user.social_user if hasattr(user, 'social_user') \ - else UserSocialAuth.objects.get(user=user.id, provider=VKOAuth2Backend.name) + else UserSocialAuth.objects.get( + user=user.id, + provider=VKOAuth2Backend.name + ) except UserSocialAuth.DoesNotExist: return None @@ -31,27 +38,29 @@ def get_access_token(user): access_token = social_user.extra_data.get('access_token') expires = social_user.extra_data.get('expires') - cache.set(key, access_token, int(expires) if expires is not None else 0) - + cache.set(key, access_token, int(expires) if expires is not None + else 0) return access_token + # VK decorator to setup environment def vkontakte_decorator(func): def wrapper(request, *args, **kwargs): user = request.user - # User must me logged via VKontakte backend in order to ensure we talk about the same person + # User must me logged via VKontakte backend in order to ensure we talk + # about the same person if not is_complete_authentication(request): try: user = social_complete(request, VKOAuth2Backend.name) except (ValueError, AttributeError): - pass # no matter if failed + pass # no matter if failed - # Not recommended way for VK, but still something we need to be aware of + # Not recommended way for VK, but still something we need to be aware + # of if isinstance(user, HttpResponse): kwargs.update({'auth_response': user}) - # Need to re-check the completion - else: + else: # Need to re-check the completion if is_complete_authentication(request): kwargs.update({'access_token': get_access_token(request.user)}) else: @@ -61,18 +70,20 @@ def wrapper(request, *args, **kwargs): return wrapper + @vkontakte_decorator def vkontakte_view(request, *args, **kwargs): - # If there is a ready response just return it. Not recommended because pipeline redirects fail the normal workflow - # here. + # If there is a ready response just return it. Not recommended because + # pipeline redirects fail the normal workflow here. auth_response = kwargs.get('auth_response') if auth_response: for item in auth_response.items(): if item[0] == 'Location' and 'form' in item[1]: return auth_response - return render_to_response('vkontakte_app.html', - {'vk_app_id': settings.VKONTAKTE_APP_AUTH['id'] if hasattr(settings, 'VKONTAKTE_APP_AUTH') else None, - 'app_scope': ','.join(settings.VKONTAKTE_OAUTH2_EXTRA_SCOPE), - 'warning': not request.GET.get('user_id')}, - RequestContext(request)) + return render_to_response('vkontakte_app.html', { + 'vk_app_id': settings.VKONTAKTE_APP_AUTH['id'] + if hasattr(settings, 'VKONTAKTE_APP_AUTH') else None, + 'app_scope': ','.join(settings.VKONTAKTE_OAUTH2_EXTRA_SCOPE), + 'warning': not request.GET.get('user_id') + }, RequestContext(request)) diff --git a/example/example/settings.py b/example/example/settings.py index daf2d416f..0027e6513 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -1,11 +1,16 @@ +import sys from os.path import abspath, dirname, basename, join -try: - import social_auth -except ImportError: - import sys - sys.path.insert(0, '..') +#try: + #import social_auth + #social_auth # pyflakes +#except ImportError: + #import sys +# sys.path.insert(0, '..') + +sys.path.insert(0, '..') +sys.path.insert(0, '../../python-social-auth') DEBUG = True @@ -44,7 +49,7 @@ STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', + # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) SECRET_KEY = '_u6ym67ywnj0ugi2=6f-a_361i6o5elx91hftz$+klw)(*pqjw' @@ -52,7 +57,7 @@ TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', + # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( @@ -81,7 +86,8 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', - 'south', + # 'south', + 'social.apps.django_app.default', 'social_auth', 'app', ) @@ -111,6 +117,7 @@ } AUTHENTICATION_BACKENDS = ( + 'social_auth.backends.OpenIDBackend', 'social_auth.backends.twitter.TwitterBackend', 'social_auth.backends.facebook.FacebookBackend', 'social_auth.backends.google.GoogleOAuthBackend', @@ -118,37 +125,36 @@ 'social_auth.backends.google.GoogleBackend', 'social_auth.backends.yahoo.YahooBackend', 'social_auth.backends.stripe.StripeBackend', + 'social_auth.backends.steam.SteamBackend', + 'social_auth.backends.reddit.RedditBackend', + 'social_auth.backends.amazon.AmazonBackend', + 'social_auth.backends.browserid.BrowserIDBackend', 'social_auth.backends.contrib.linkedin.LinkedinBackend', 'social_auth.backends.contrib.skyrock.SkyrockBackend', 'social_auth.backends.contrib.flickr.FlickrBackend', 'social_auth.backends.contrib.instagram.InstagramBackend', 'social_auth.backends.contrib.github.GithubBackend', 'social_auth.backends.contrib.yandex.YandexBackend', + 'social_auth.backends.contrib.yandex.YandexOAuth2Backend', + 'social_auth.backends.contrib.yandex.YaruBackend', 'social_auth.backends.contrib.disqus.DisqusBackend', 'social_auth.backends.contrib.yahoo.YahooOAuthBackend', 'social_auth.backends.contrib.foursquare.FoursquareBackend', - 'social_auth.backends.OpenIDBackend', 'social_auth.backends.contrib.live.LiveBackend', 'social_auth.backends.contrib.livejournal.LiveJournalBackend', 'social_auth.backends.contrib.douban.DoubanBackend', - 'social_auth.backends.browserid.BrowserIDBackend', 'social_auth.backends.contrib.vk.VKOpenAPIBackend', - 'social_auth.backends.contrib.yandex.YandexOAuth2Backend', - 'social_auth.backends.contrib.yandex.YaruBackend', + 'social_auth.backends.contrib.vk.VKOAuth2Backend', 'social_auth.backends.contrib.odnoklassniki.OdnoklassnikiBackend', 'social_auth.backends.contrib.odnoklassniki.OdnoklassnikiAppBackend', - 'social_auth.backends.contrib.vk.VKOAuth2Backend', 'social_auth.backends.contrib.mailru.MailruBackend', 'social_auth.backends.contrib.dailymotion.DailymotionBackend', - 'social_auth.backends.contrib.shopify.ShopifyBackend', - 'social_auth.backends.contrib.exacttarget.ExactTargetBackend', + # 'social_auth.backends.contrib.shopify.ShopifyBackend', + # 'social_auth.backends.contrib.exacttarget.ExactTargetBackend', 'social_auth.backends.contrib.stocktwits.StocktwitsBackend', 'social_auth.backends.contrib.behance.BehanceBackend', 'social_auth.backends.contrib.readability.ReadabilityBackend', 'social_auth.backends.contrib.fedora.FedoraBackend', - 'social_auth.backends.steam.SteamBackend', - 'social_auth.backends.reddit.RedditBackend', - 'social_auth.backends.amazon.AmazonBackend', 'django.contrib.auth.backends.ModelBackend', ) diff --git a/requirements.txt b/requirements.txt index 518248cd7..301f6585f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,2 @@ django>=1.2.5 -oauth2>=1.5.167 -python_openid>=2.2 -selenium>=2.29.0 -mock==1.0.1 +python-social-auth>=0.1.10 diff --git a/setup.py b/setup.py index 808632c57..455a14b67 100644 --- a/setup.py +++ b/setup.py @@ -45,8 +45,7 @@ def long_description(): package_data={'social_auth': ['locale/*/LC_MESSAGES/*']}, long_description=long_description(), install_requires=['Django>=1.2.5', - 'oauth2>=1.5.167', - 'python-openid>=2.2'], + 'python-social-auth>=0.1.10'], classifiers=['Framework :: Django', 'Development Status :: 4 - Beta', 'Topic :: Internet', diff --git a/social_auth/__init__.py b/social_auth/__init__.py index 236b2475c..55bf36bac 100644 --- a/social_auth/__init__.py +++ b/social_auth/__init__.py @@ -2,5 +2,13 @@ Django-social-auth application, allows OpenId or OAuth user registration/authentication just adding a few configurations. """ -version = (0, 7, 25) +from django.conf import settings + + +version = (0, 8, 0) __version__ = '.'.join(map(str, version)) + + +MODELS = getattr(settings, 'SOCIAL_AUTH_MODELS', + 'social_auth.db.django_models') +IS_DJANGO_MODELS = MODELS == 'social_auth.db.django_models' diff --git a/social_auth/admin.py b/social_auth/admin.py index 81ca0d9cb..dfaa39e2c 100644 --- a/social_auth/admin.py +++ b/social_auth/admin.py @@ -1,44 +1,6 @@ -"""Admin settings""" -from social_auth.utils import setting +from social_auth import IS_DJANGO_MODELS -if setting('SOCIAL_AUTH_MODELS') in (None, 'social_auth.db.django_models'): - from django.contrib import admin - from social_auth.models import UserSocialAuth, Nonce, Association - - _User = UserSocialAuth.user_model() - - if hasattr(_User, 'USERNAME_FIELD'): - username_field = _User.USERNAME_FIELD - elif hasattr(_User, 'username'): - username_field = 'username' - else: - username_field = None - - fieldnames = ('first_name', 'last_name', 'email') + (username_field,) - all_names = _User._meta.get_all_field_names() - user_search_fields = ['user__' + name for name in fieldnames - if name in all_names] - - class UserSocialAuthOption(admin.ModelAdmin): - """Social Auth user options""" - list_display = ('id', 'user', 'provider', 'uid') - search_fields = user_search_fields - list_filter = ('provider',) - raw_id_fields = ('user',) - list_select_related = True - - class NonceOption(admin.ModelAdmin): - """Nonce options""" - list_display = ('id', 'server_url', 'timestamp', 'salt') - search_fields = ('server_url',) - - class AssociationOption(admin.ModelAdmin): - """Association options""" - list_display = ('id', 'server_url', 'assoc_type') - list_filter = ('assoc_type',) - search_fields = ('server_url',) - - admin.site.register(UserSocialAuth, UserSocialAuthOption) - admin.site.register(Nonce, NonceOption) - admin.site.register(Association, AssociationOption) +if IS_DJANGO_MODELS: + from social.apps.django_app.default import admin + admin # placate pyflakes diff --git a/social_auth/backends/__init__.py b/social_auth/backends/__init__.py index 8a4dc508c..74ff6a0b5 100644 --- a/social_auth/backends/__init__.py +++ b/social_auth/backends/__init__.py @@ -1,930 +1,11 @@ -""" -Base backends structures. - -This module defines base classes needed to define custom OpenID or OAuth -auth services from third parties. This customs must subclass an Auth and -and Backend class, check current implementation for examples. - -Also the modules *must* define a BACKENDS dictionary with the backend name -(which is used for URLs matching) and Auth class, otherwise it won't be -enabled. -""" -from urllib2 import Request, HTTPError -from urllib import urlencode - -from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE -from openid.consumer.discover import DiscoveryFailure -from openid.extensions import sreg, ax, pape - -from oauth2 import Consumer as OAuthConsumer, Token, Request as OAuthRequest - -from django.contrib.auth import authenticate -from django.utils import simplejson +from django.conf import settings from django.utils.importlib import import_module -from social_auth.models import UserSocialAuth -from social_auth.utils import setting, model_to_ctype, ctype_to_model, \ - clean_partial_pipeline, url_add_parameters, \ - get_random_string, constant_time_compare, \ - dsa_urlopen -from social_auth.store import DjangoOpenIDStore -from social_auth.exceptions import StopPipeline, AuthException, AuthFailed, \ - AuthCanceled, AuthUnknownError, \ - AuthTokenError, AuthMissingParameter, \ - AuthStateMissing, AuthStateForbidden, \ - NotAllowedToDisconnect -from social_auth.backends.utils import build_consumer_oauth_request - - -# OpenID configuration -OLD_AX_ATTRS = [ - ('http://schema.openid.net/contact/email', 'old_email'), - ('http://schema.openid.net/namePerson', 'old_fullname'), - ('http://schema.openid.net/namePerson/friendly', 'old_nickname') -] -AX_SCHEMA_ATTRS = [ - # Request both the full name and first/last components since some - # providers offer one but not the other. - ('http://axschema.org/contact/email', 'email'), - ('http://axschema.org/namePerson', 'fullname'), - ('http://axschema.org/namePerson/first', 'first_name'), - ('http://axschema.org/namePerson/last', 'last_name'), - ('http://axschema.org/namePerson/friendly', 'nickname'), -] -SREG_ATTR = [ - ('email', 'email'), - ('fullname', 'fullname'), - ('nickname', 'nickname') -] -OPENID_ID_FIELD = 'openid_identifier' -SESSION_NAME = 'openid' - -PIPELINE = setting('SOCIAL_AUTH_PIPELINE', ( - 'social_auth.backends.pipeline.social.social_auth_user', - # Removed by default since it can be a dangerouse behavior that - # could lead to accounts take over. - #'social_auth.backends.pipeline.associate.associate_by_email', - 'social_auth.backends.pipeline.user.get_username', - 'social_auth.backends.pipeline.user.create_user', - 'social_auth.backends.pipeline.social.associate_user', - 'social_auth.backends.pipeline.social.load_extra_data', - 'social_auth.backends.pipeline.user.update_user_details', - )) - - -class SocialAuthBackend(object): - """A django.contrib.auth backend that authenticates the user based on - a authentication provider response""" - name = '' # provider name, it's stored in database - supports_inactive_user = False - - def authenticate(self, *args, **kwargs): - """Authenticate user using social credentials - - Authentication is made if this is the correct backend, backend - verification is made by kwargs inspection for current backend - name presence. - """ - # Validate backend and arguments. Require that the Social Auth - # response be passed in as a keyword argument, to make sure we - # don't match the username/password calling conventions of - # authenticate. - if not (self.name and kwargs.get(self.name) and 'response' in kwargs): - return None - - response = kwargs.get('response') - pipeline = PIPELINE - kwargs = kwargs.copy() - kwargs['backend'] = self - - if 'pipeline_index' in kwargs: - pipeline = pipeline[kwargs['pipeline_index']:] - else: - kwargs['details'] = self.get_user_details(response) - kwargs['uid'] = self.get_user_id(kwargs['details'], response) - kwargs['is_new'] = False - - out = self.pipeline(pipeline, *args, **kwargs) - if not isinstance(out, dict): - return out - - social_user = out.get('social_user') - if social_user: - # define user.social_user attribute to track current social - # account - user = social_user.user - user.social_user = social_user - user.is_new = out.get('is_new') - return user - - def pipeline(self, pipeline, *args, **kwargs): - """Pipeline""" - out = kwargs.copy() - - if 'pipeline_index' in kwargs: - base_index = int(kwargs['pipeline_index']) - else: - base_index = 0 - - for idx, name in enumerate(pipeline): - out['pipeline_index'] = base_index + idx - mod_name, func_name = name.rsplit('.', 1) - mod = import_module(mod_name) - func = getattr(mod, func_name, None) - - try: - result = func(*args, **out) or {} - except StopPipeline: - # Clean partial pipeline on stop - if 'request' in kwargs: - clean_partial_pipeline(kwargs['request']) - break - - if isinstance(result, dict): - out.update(result) - else: - return result - - # clean the partial pipeline at the end of the process - if 'request' in kwargs: - clean_partial_pipeline(kwargs['request']) - return out - - def extra_data(self, user, uid, response, details): - """Return default blank user extra data""" - return {} - - def get_user_id(self, details, response): - """Must return a unique ID from values returned on details""" - raise NotImplementedError('Implement in subclass') - - def get_user_details(self, response): - """Must return user details in a know internal struct: - {'username': , - 'email': , - 'fullname': , - 'first_name': , - 'last_name': } - """ - raise NotImplementedError('Implement in subclass') - - @classmethod - def tokens(cls, instance): - """Return the tokens needed to authenticate the access to any API the - service might provide. The return value will be a dictionary with the - token type name as key and the token value. - - instance must be a UserSocialAuth instance. - """ - if instance.extra_data and 'access_token' in instance.extra_data: - return { - 'access_token': instance.extra_data['access_token'] - } - else: - return {} - - def get_user(self, user_id): - """ - Return user with given ID from the User model used by this backend. - This is called by django.contrib.auth.middleware. - """ - return UserSocialAuth.get_user(user_id) - - -class OAuthBackend(SocialAuthBackend): - """OAuth authentication backend base class. - - EXTRA_DATA defines a set of name that will be stored in - extra_data field. It must be a list of tuples with - name and alias. - - Also settings will be inspected to get more values names that should be - stored on extra_data field. Setting name is created from current backend - name (all uppercase) plus _EXTRA_DATA. - - access_token is always stored. - """ - EXTRA_DATA = None - ID_KEY = 'id' - - def get_user_id(self, details, response): - """OAuth providers return an unique user id in response""" - return response[self.ID_KEY] - - @classmethod - def extra_data(cls, user, uid, response, details=None): - """Return access_token and extra defined names to store in - extra_data field""" - data = {'access_token': response.get('access_token', '')} - name = cls.name.replace('-', '_').upper() - names = (cls.EXTRA_DATA or []) + setting(name + '_EXTRA_DATA', []) - for entry in names: - if len(entry) == 2: - (name, alias), discard = entry, False - elif len(entry) == 3: - name, alias, discard = entry - elif len(entry) == 1: - name = alias = entry - discard = False - else: # ??? - continue - - value = response.get(name) - if discard and not value: - continue - data[alias] = value - return data - - -class OpenIDBackend(SocialAuthBackend): - """Generic OpenID authentication backend""" - name = 'openid' - - def get_user_id(self, details, response): - """Return user unique id provided by service""" - return response.identity_url - - def values_from_response(self, response, sreg_names=None, ax_names=None): - """Return values from SimpleRegistration response or - AttributeExchange response if present. - - @sreg_names and @ax_names must be a list of name and aliases - for such name. The alias will be used as mapping key. - """ - values = {} - - # Use Simple Registration attributes if provided - if sreg_names: - resp = sreg.SRegResponse.fromSuccessResponse(response) - if resp: - values.update((alias, resp.get(name) or '') - for name, alias in sreg_names) - - # Use Attribute Exchange attributes if provided - if ax_names: - resp = ax.FetchResponse.fromSuccessResponse(response) - if resp: - for src, alias in ax_names: - name = alias.replace('old_', '') - values[name] = resp.getSingle(src, '') or values.get(name) - return values - - def get_user_details(self, response): - """Return user details from an OpenID request""" - values = {'username': '', 'email': '', 'fullname': '', - 'first_name': '', 'last_name': ''} - # update values using SimpleRegistration or AttributeExchange - # values - values.update(self.values_from_response(response, - SREG_ATTR, - OLD_AX_ATTRS + - AX_SCHEMA_ATTRS)) - - fullname = values.get('fullname') or '' - first_name = values.get('first_name') or '' - last_name = values.get('last_name') or '' - - if not fullname and first_name and last_name: - fullname = first_name + ' ' + last_name - elif fullname: - try: # Try to split name for django user storage - first_name, last_name = fullname.rsplit(' ', 1) - except ValueError: - last_name = fullname - - values.update({ - 'fullname': fullname, - 'first_name': first_name, - 'last_name': last_name, - 'username': values.get('username') or - (first_name.title() + last_name.title()) - }) - return values - - def extra_data(self, user, uid, response, details): - """Return defined extra data names to store in extra_data field. - Settings will be inspected to get more values names that should be - stored on extra_data field. Setting name is created from current - backend name (all uppercase) plus _SREG_EXTRA_DATA and - _AX_EXTRA_DATA because values can be returned by SimpleRegistration - or AttributeExchange schemas. - - Both list must be a value name and an alias mapping similar to - SREG_ATTR, OLD_AX_ATTRS or AX_SCHEMA_ATTRS - """ - name = self.name.replace('-', '_').upper() - sreg_names = setting(name + '_SREG_EXTRA_DATA') - ax_names = setting(name + '_AX_EXTRA_DATA') - data = self.values_from_response(response, sreg_names, ax_names) - return data - - -class BaseAuth(object): - """Base authentication class, new authenticators should subclass - and implement needed methods. - - AUTH_BACKEND Authorization backend related with this service - """ - AUTH_BACKEND = None - - def __init__(self, request, redirect): - self.request = request - # Use request because some auth providers use POST urls with needed - # GET parameters on it - self.data = request.REQUEST - self.redirect = redirect - - def auth_url(self): - """Must return redirect URL to auth provider""" - raise NotImplementedError('Implement in subclass') - - def auth_html(self): - """Must return login HTML content returned by provider""" - raise NotImplementedError('Implement in subclass') - - def auth_complete(self, *args, **kwargs): - """Completes loging process, must return user instance""" - raise NotImplementedError('Implement in subclass') - - def to_session_dict(self, next_idx, *args, **kwargs): - """Returns dict to store on session for partial pipeline.""" - return { - 'next': next_idx, - 'backend': self.AUTH_BACKEND.name, - 'args': tuple(map(model_to_ctype, args)), - 'kwargs': dict((key, model_to_ctype(val)) - for key, val in kwargs.iteritems()) - } - - def from_session_dict(self, session_data, *args, **kwargs): - """Takes session saved data to continue pipeline and merges with any - new extra argument needed. Returns tuple with next pipeline index - entry, arguments and keyword arguments to continue the process.""" - args = args[:] + tuple(map(ctype_to_model, session_data['args'])) - - kwargs = kwargs.copy() - saved_kwargs = dict((key, ctype_to_model(val)) - for key, val in session_data['kwargs'].iteritems()) - saved_kwargs.update((key, val) - for key, val in kwargs.iteritems()) - return (session_data['next'], args, saved_kwargs) - - def continue_pipeline(self, *args, **kwargs): - """Continue previous halted pipeline""" - kwargs.update({ - 'auth': self, - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - - def request_token_extra_arguments(self): - """Return extra arguments needed on request-token process, - setting is per backend and defined by: - _REQUEST_TOKEN_EXTRA_ARGUMENTS. - """ - backend_name = self.AUTH_BACKEND.name.upper().replace('-', '_') - return setting(backend_name + '_REQUEST_TOKEN_EXTRA_ARGUMENTS', {}) - - def auth_extra_arguments(self): - """Return extra arguments needed on auth process, setting is per - backend and defined by: - _AUTH_EXTRA_ARGUMENTS. - The defaults can be overriden by GET parameters. - """ - backend_name = self.AUTH_BACKEND.name.upper().replace('-', '_') - extra_arguments = setting(backend_name + '_AUTH_EXTRA_ARGUMENTS', {}) - for key, value in extra_arguments.iteritems(): - if key in self.data: - extra_arguments[key] = self.data[key] - elif value: - extra_arguments[key] = value - return extra_arguments - - @property - def uses_redirect(self): - """Return True if this provider uses redirect url method, - otherwise return false.""" - return True - - @classmethod - def enabled(cls): - """Return backend enabled status, all enabled by default""" - return True - - def disconnect(self, user, association_id=None): - """Deletes current backend from user if associated. - Override if extra operations are needed. - """ - name = self.AUTH_BACKEND.name - if UserSocialAuth.allowed_to_disconnect(user, name, association_id): - do_revoke = setting('SOCIAL_AUTH_REVOKE_TOKENS_ON_DISCONNECT') - filter_args = {} - - if association_id: - filter_args['id'] = association_id - else: - filter_args['provider'] = name - instances = UserSocialAuth.get_social_auth_for_user(user)\ - .filter(**filter_args) - - if do_revoke: - for instance in instances: - instance.revoke_token(drop_token=False) - instances.delete() - else: - raise NotAllowedToDisconnect() - - def build_absolute_uri(self, path=None): - """Build absolute URI for given path. Replace http:// schema with - https:// if SOCIAL_AUTH_REDIRECT_IS_HTTPS is defined. - """ - uri = self.request.build_absolute_uri(path) - if setting('SOCIAL_AUTH_REDIRECT_IS_HTTPS'): - uri = uri.replace('http://', 'https://') - return uri - +from social.backends.base import BaseAuth +from social.backends.open_id import OpenIdAuth as OpenIDBackend -class OpenIdAuth(BaseAuth): - """OpenId process handling""" - AUTH_BACKEND = OpenIDBackend - - def auth_url(self): - """Return auth URL returned by service""" - openid_request = self.setup_request(self.auth_extra_arguments()) - # Construct completion URL, including page we should redirect to - return_to = self.build_absolute_uri(self.redirect) - return openid_request.redirectURL(self.trust_root(), return_to) - - def auth_html(self): - """Return auth HTML returned by service""" - openid_request = self.setup_request(self.auth_extra_arguments()) - return_to = self.build_absolute_uri(self.redirect) - form_tag = {'id': 'openid_message'} - return openid_request.htmlMarkup(self.trust_root(), return_to, - form_tag_attrs=form_tag) - - def trust_root(self): - """Return trust-root option""" - return setting('OPENID_TRUST_ROOT') or self.build_absolute_uri('/') - - def continue_pipeline(self, *args, **kwargs): - """Continue previous halted pipeline""" - response = self.consumer().complete(dict(self.data.items()), - self.build_absolute_uri()) - kwargs.update({ - 'auth': self, - 'response': response, - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - - def auth_complete(self, *args, **kwargs): - """Complete auth process""" - response = self.consumer().complete(dict(self.data.items()), - self.build_absolute_uri()) - if not response: - raise AuthException(self, 'OpenID relying party endpoint') - elif response.status == SUCCESS: - kwargs.update({ - 'auth': self, - 'response': response, - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - elif response.status == FAILURE: - raise AuthFailed(self, response.message) - elif response.status == CANCEL: - raise AuthCanceled(self) - else: - raise AuthUnknownError(self, response.status) - - def setup_request(self, extra_params=None): - """Setup request""" - openid_request = self.openid_request(extra_params) - # Request some user details. Use attribute exchange if provider - # advertises support. - if openid_request.endpoint.supportsType(ax.AXMessage.ns_uri): - fetch_request = ax.FetchRequest() - # Mark all attributes as required, Google ignores optional ones - for attr, alias in (AX_SCHEMA_ATTRS + OLD_AX_ATTRS): - fetch_request.add(ax.AttrInfo(attr, alias=alias, - required=True)) - else: - fetch_request = sreg.SRegRequest(optional=dict(SREG_ATTR).keys()) - openid_request.addExtension(fetch_request) - - # Add PAPE Extension for if configured - preferred_policies = setting( - 'SOCIAL_AUTH_OPENID_PAPE_PREFERRED_AUTH_POLICIES') - preferred_level_types = setting( - 'SOCIAL_AUTH_OPENID_PAPE_PREFERRED_AUTH_LEVEL_TYPES') - max_age = setting('SOCIAL_AUTH_OPENID_PAPE_MAX_AUTH_AGE') - if max_age is not None: - try: - max_age = int(max_age) - except (ValueError, TypeError): - max_age = None - - if (max_age is not None or preferred_policies is not None - or preferred_level_types is not None): - pape_request = pape.Request( - preferred_auth_policies=preferred_policies, - max_auth_age=max_age, - preferred_auth_level_types=preferred_level_types - ) - openid_request.addExtension(pape_request) - - return openid_request - - def consumer(self): - """Create an OpenID Consumer object for the given Django request.""" - return Consumer(self.request.session.setdefault(SESSION_NAME, {}), - DjangoOpenIDStore()) - - @property - def uses_redirect(self): - """Return true if openid request will be handled with redirect or - HTML content will be returned. - """ - return self.openid_request(self.auth_extra_arguments())\ - .shouldSendRedirect() - - def openid_request(self, extra_params=None): - """Return openid request""" - if not hasattr(self, '_openid_request'): - try: - self._openid_request = self.consumer().begin( - url_add_parameters(self.openid_url(), extra_params) - ) - except DiscoveryFailure, err: - raise AuthException(self, 'OpenID discovery error: %s' % err) - return self._openid_request - - def openid_url(self): - """Return service provider URL. - This base class is generic accepting a POST parameter that specifies - provider URL.""" - if OPENID_ID_FIELD not in self.data: - raise AuthMissingParameter(self, OPENID_ID_FIELD) - return self.data[OPENID_ID_FIELD] - - -class BaseOAuth(BaseAuth): - """OAuth base class""" - SETTINGS_KEY_NAME = '' - SETTINGS_SECRET_NAME = '' - SCOPE_VAR_NAME = None - SCOPE_PARAMETER_NAME = 'scope' - DEFAULT_SCOPE = None - SCOPE_SEPARATOR = ' ' - - def __init__(self, request, redirect): - """Init method""" - super(BaseOAuth, self).__init__(request, redirect) - self.redirect_uri = self.build_absolute_uri(self.redirect) - - @classmethod - def get_key_and_secret(cls): - """Return tuple with Consumer Key and Consumer Secret for current - service provider. Must return (key, secret), order *must* be respected. - """ - return setting(cls.SETTINGS_KEY_NAME), \ - setting(cls.SETTINGS_SECRET_NAME) - - @classmethod - def enabled(cls): - """Return backend enabled status by checking basic settings""" - return setting(cls.SETTINGS_KEY_NAME) and \ - setting(cls.SETTINGS_SECRET_NAME) - - def get_scope(self): - """Return list with needed access scope""" - scope = self.DEFAULT_SCOPE or [] - if self.SCOPE_VAR_NAME: - scope = scope + setting(self.SCOPE_VAR_NAME, []) - return scope - - def get_scope_argument(self): - param = {} - scope = self.get_scope() - if scope: - param[self.SCOPE_PARAMETER_NAME] = self.SCOPE_SEPARATOR.join(scope) - return param - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service. Implement in subclass""" - return {} - - -class ConsumerBasedOAuth(BaseOAuth): - """Consumer based mechanism OAuth authentication, fill the needed - parameters to communicate properly with authentication service. - - AUTHORIZATION_URL Authorization service url - REQUEST_TOKEN_URL Request token URL - ACCESS_TOKEN_URL Access token URL - """ - AUTHORIZATION_URL = '' - REQUEST_TOKEN_URL = '' - ACCESS_TOKEN_URL = '' - - def auth_url(self): - """Return redirect url""" - token = self.unauthorized_token() - name = self.AUTH_BACKEND.name + 'unauthorized_token_name' - if not isinstance(self.request.session.get(name), list): - self.request.session[name] = [] - self.request.session[name].append(token.to_string()) - self.request.session.modified = True - return self.oauth_authorization_request(token).to_url() - - def auth_complete(self, *args, **kwargs): - """Return user, might be logged in""" - # Multiple unauthorized tokens are supported (see #521) - name = self.AUTH_BACKEND.name + 'unauthorized_token_name' - token = None - unauthed_tokens = self.request.session.get(name) or [] - if not unauthed_tokens: - raise AuthTokenError(self, 'Missing unauthorized token') - for unauthed_token in unauthed_tokens: - token = Token.from_string(unauthed_token) - if token.key == self.data.get('oauth_token', 'no-token'): - unauthed_tokens = list(set(unauthed_tokens) - - set([unauthed_token])) - self.request.session[name] = unauthed_tokens - self.request.session.modified = True - break - else: - raise AuthTokenError(self, 'Incorrect tokens') - - try: - access_token = self.access_token(token) - except HTTPError, e: - if e.code == 400: - raise AuthCanceled(self) - else: - raise - return self.do_auth(access_token, *args, **kwargs) - - def do_auth(self, access_token, *args, **kwargs): - """Finish the auth process once the access_token was retrieved""" - if isinstance(access_token, basestring): - access_token = Token.from_string(access_token) - - data = self.user_data(access_token) - if data is not None: - data['access_token'] = access_token.to_string() - - kwargs.update({ - 'auth': self, - 'response': data, - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - - def unauthorized_token(self): - """Return request for unauthorized token (first stage)""" - request = self.oauth_request( - token=None, - url=self.REQUEST_TOKEN_URL, - extra_params=self.request_token_extra_arguments() - ) - return Token.from_string(self.fetch_response(request)) - - def oauth_authorization_request(self, token): - """Generate OAuth request to authorize token.""" - params = self.auth_extra_arguments() or {} - params.update(self.get_scope_argument()) - return OAuthRequest.from_token_and_callback( - token=token, - callback=self.redirect_uri, - http_url=self.AUTHORIZATION_URL, - parameters=params - ) - - def oauth_request(self, token, url, extra_params=None): - """Generate OAuth request, setups callback url""" - return build_consumer_oauth_request(self, token, url, - self.redirect_uri, - self.data.get('oauth_verifier'), - extra_params) - - def fetch_response(self, request): - """Executes request and fetchs service response""" - response = dsa_urlopen(request.to_url()) - return '\n'.join(response.readlines()) - - def access_token(self, token): - """Return request for access token value""" - request = self.oauth_request(token, self.ACCESS_TOKEN_URL) - return Token.from_string(self.fetch_response(request)) - - @property - def consumer(self): - """Setups consumer""" - return OAuthConsumer(*self.get_key_and_secret()) - - -class BaseOAuth2(BaseOAuth): - """Base class for OAuth2 providers. - - OAuth2 draft details at: - http://tools.ietf.org/html/draft-ietf-oauth-v2-10 - - Attributes: - AUTHORIZATION_URL Authorization service url - ACCESS_TOKEN_URL Token URL - """ - AUTHORIZATION_URL = None - ACCESS_TOKEN_URL = None - REFRESH_TOKEN_URL = None - REVOKE_TOKEN_URL = None - REVOKE_TOKEN_METHOD = 'POST' - RESPONSE_TYPE = 'code' - REDIRECT_STATE = True - STATE_PARAMETER = True - - def state_token(self): - """Generate csrf token to include as state parameter.""" - return get_random_string(32) - - def get_redirect_uri(self, state=None): - """Build redirect_uri with redirect_state parameter.""" - uri = self.redirect_uri - if self.REDIRECT_STATE and state: - uri = url_add_parameters(uri, {'redirect_state': state}) - return uri - - def auth_params(self, state=None): - client_id, client_secret = self.get_key_and_secret() - params = { - 'client_id': client_id, - 'redirect_uri': self.get_redirect_uri(state) - } - if self.STATE_PARAMETER and state: - params['state'] = state - if self.RESPONSE_TYPE: - params['response_type'] = self.RESPONSE_TYPE - return params - - def auth_url(self): - """Return redirect url""" - if self.STATE_PARAMETER or self.REDIRECT_STATE: - # Store state in session for further request validation. The state - # value is passed as state parameter (as specified in OAuth2 spec), - # but also added to redirect_uri, that way we can still verify the - # request if the provider doesn't implement the state parameter. - # Reuse token if any. - name = self.AUTH_BACKEND.name + '_state' - state = self.request.session.get(name) or self.state_token() - self.request.session[self.AUTH_BACKEND.name + '_state'] = state - else: - state = None - - params = self.auth_params(state) - params.update(self.get_scope_argument()) - params.update(self.auth_extra_arguments()) - - if self.request.META.get('QUERY_STRING'): - query_string = '&' + self.request.META['QUERY_STRING'] - else: - query_string = '' - return self.AUTHORIZATION_URL + '?' + urlencode(params) + query_string - - def validate_state(self): - """Validate state value. Raises exception on error, returns state - value if valid.""" - if not self.STATE_PARAMETER and not self.REDIRECT_STATE: - return None - state = self.request.session.get(self.AUTH_BACKEND.name + '_state') - if state: - request_state = self.data.get('state') or \ - self.data.get('redirect_state') - if not request_state: - raise AuthMissingParameter(self, 'state') - elif not state: - raise AuthStateMissing(self, 'state') - elif not constant_time_compare(request_state, state): - raise AuthStateForbidden(self) - return state - - def process_error(self, data): - error = data.get('error_description') or data.get('error') - if error: - raise AuthFailed(self, error) - - def auth_complete_params(self, state=None): - client_id, client_secret = self.get_key_and_secret() - return { - 'grant_type': 'authorization_code', # request auth code - 'code': self.data.get('code', ''), # server response code - 'client_id': client_id, - 'client_secret': client_secret, - 'redirect_uri': self.get_redirect_uri(state) - } - - @classmethod - def auth_headers(cls): - return {'Content-Type': 'application/x-www-form-urlencoded', - 'Accept': 'application/json'} - - def auth_complete(self, *args, **kwargs): - """Completes loging process, must return user instance""" - self.process_error(self.data) - params = self.auth_complete_params(self.validate_state()) - request = Request(self.ACCESS_TOKEN_URL, data=urlencode(params), - headers=self.auth_headers()) - - try: - response = simplejson.loads(dsa_urlopen(request).read()) - except HTTPError, e: - if e.code == 400: - raise AuthCanceled(self) - else: - raise - except (ValueError, KeyError): - raise AuthUnknownError(self) - - self.process_error(response) - return self.do_auth(response['access_token'], response=response, - *args, **kwargs) - - @classmethod - def refresh_token_params(cls, token): - client_id, client_secret = cls.get_key_and_secret() - return { - 'refresh_token': token, - 'grant_type': 'refresh_token', - 'client_id': client_id, - 'client_secret': client_secret - } - - @classmethod - def process_refresh_token_response(cls, response): - return simplejson.loads(response) - - @classmethod - def refresh_token(cls, token): - request = Request( - cls.REFRESH_TOKEN_URL or cls.ACCESS_TOKEN_URL, - data=urlencode(cls.refresh_token_params(token)), - headers=cls.auth_headers() - ) - return cls.process_refresh_token_response(dsa_urlopen(request).read()) - - @classmethod - def revoke_token_params(cls, token, uid): - return None - - @classmethod - def revoke_token_headers(cls, token, uid): - return None - - @classmethod - def process_revoke_token_response(cls, response): - return response.code == 200 - - @classmethod - def revoke_token(cls, token, uid): - if not cls.REVOKE_TOKEN_URL: - return - url = cls.REVOKE_TOKEN_URL.format(token=token, uid=uid) - params = cls.revoke_token_params(token, uid) or {} - headers = cls.revoke_token_headers(token, uid) or {} - data = None - - if cls.REVOKE_TOKEN_METHOD == 'GET': - url = '{}?{}'.format(url, urlencode(params)) - else: - data = urlencode(params) - - request = Request(url, data=data, headers=headers) - if cls.REVOKE_TOKEN_URL.lower() not in ('get', 'post'): - # Patch get_method to return the needed method - request.get_method = lambda: cls.REVOKE_TOKEN_METHOD - response = dsa_urlopen(request) - return cls.process_revoke_token_response(response) - - 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 {}) - kwargs.update({ - 'auth': self, - 'response': response, - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - - -# Backend loading was previously performed via the -# SOCIAL_AUTH_IMPORT_BACKENDS setting - as it's no longer used, -# provide a deprecation warning. -if setting('SOCIAL_AUTH_IMPORT_BACKENDS'): - from warnings import warn - warn("SOCIAL_AUTH_IMPORT_SOURCES is deprecated") +OpenIDBackend # placate pyflakes # Cache for discovered backends. BACKENDSCACHE = {} @@ -950,16 +31,13 @@ def get_backends(force_load=False): below can retry a requested backend that may not yet be discovered. """ if not BACKENDSCACHE or force_load: - for auth_backend in setting('AUTHENTICATION_BACKENDS'): + for auth_backend in getattr(settings, 'AUTHENTICATION_BACKENDS', []): mod, cls_name = auth_backend.rsplit('.', 1) module = import_module(mod) backend = getattr(module, cls_name) - if issubclass(backend, SocialAuthBackend): - name = backend.name - backends = getattr(module, 'BACKENDS', {}) - if name in backends and backends[name].enabled(): - BACKENDSCACHE[name] = backends[name] + if issubclass(backend, BaseAuth): + BACKENDSCACHE[backend.name] = backend return BACKENDSCACHE @@ -981,8 +59,3 @@ def get_backend(name, *args, **kwargs): return BACKENDSCACHE[name](*args, **kwargs) except KeyError: return None - - -BACKENDS = { - 'openid': OpenIdAuth -} diff --git a/social_auth/backends/amazon.py b/social_auth/backends/amazon.py index cd2d1e932..a10093675 100644 --- a/social_auth/backends/amazon.py +++ b/social_auth/backends/amazon.py @@ -1,88 +1 @@ -import base64 -from urllib2 import Request, HTTPError -from urllib import urlencode - -from django.utils import simplejson - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import dsa_urlopen -from social_auth.exceptions import AuthTokenError - - -class AmazonBackend(OAuthBackend): - """Amazon OAuth2 authentication backend""" - name = 'amazon' - # Default extra data to store - EXTRA_DATA = [ - ('user_id', 'user_id'), - ('postal_code', 'postal_code') - ] - ID_KEY = 'user_id' - - def get_user_details(self, response): - """Return user details from amazon account""" - name = response.get('name') or '' - first_name = '' - last_name = '' - if name and ' ' in name: - first_name, last_name = response.get('name').split(' ', 1) - else: - first_name = name - return {'username': name, - 'email': response.get('email'), - 'fullname': name, - 'first_name': first_name, - 'last_name': last_name} - - -class AmazonAuth(BaseOAuth2): - """Amazon OAuth2 support""" - REDIRECT_STATE = False - AUTH_BACKEND = AmazonBackend - AUTHORIZATION_URL = 'http://www.amazon.com/ap/oa' - ACCESS_TOKEN_URL = 'https://api.amazon.com/auth/o2/token' - SETTINGS_KEY_NAME = 'AMAZON_APP_ID' - SETTINGS_SECRET_NAME = 'AMAZON_API_SECRET' - SCOPE_VAR_NAME = 'AMAZON_EXTENDED_PERMISSIONS' - DEFAULT_SCOPE = ['profile'] - - @classmethod - def refresh_token(cls, token, redirect_uri): - data = cls.refresh_token_params(token) - data['redirect_uri'] = redirect_uri - request = Request(cls.ACCESS_TOKEN_URL, - data=urlencode(data), - headers=cls.auth_headers()) - return cls.process_refresh_token_response(dsa_urlopen(request).read()) - - def user_data(self, access_token, *args, **kwargs): - """Grab user profile information from amazon.""" - url = 'https://www.amazon.com/ap/user/profile?access_token=%s' % \ - access_token - try: - response = simplejson.load(dsa_urlopen(Request(url))) - except ValueError: - return None - except HTTPError: - raise AuthTokenError(self) - else: - if 'Profile' in response: - response = { - 'user_id': response['Profile']['CustomerId'], - 'name': response['Profile']['Name'], - 'email': response['Profile']['PrimaryEmail'] - } - return response - - @classmethod - def auth_headers(cls): - return { - 'Authorization': 'Basic %s' % base64.urlsafe_b64encode( - '%s:%s' % cls.get_key_and_secret() - ) - } - - -BACKENDS = { - 'amazon': AmazonAuth -} +from social.backends.amazon import AmazonOAuth2 as AmazonBackend diff --git a/social_auth/backends/aol.py b/social_auth/backends/aol.py index 117e85a25..887af9a39 100644 --- a/social_auth/backends/aol.py +++ b/social_auth/backends/aol.py @@ -1,28 +1 @@ -""" -AOL OpenID support - -No extra configurations are needed to make this work. -""" -from social_auth.backends import OpenIdAuth, OpenIDBackend - - -AOL_OPENID_URL = 'http://openid.aol.com' - -# Backends -class AolBackend(OpenIDBackend): - """Aol OpenID authentication backend""" - name = 'aol' - -# Auth classes -class AolAuth(OpenIdAuth): - """Aol OpenID authentication""" - AUTH_BACKEND = AolBackend - - def openid_url(self): - """Return AOL OpenID service url""" - return AOL_OPENID_URL - -# Backend definition -BACKENDS = { - 'aol': AolAuth, -} +from social.backends.aol import AOLOpenId as AolBackend diff --git a/social_auth/backends/browserid.py b/social_auth/backends/browserid.py index db98fe1a8..219903a97 100644 --- a/social_auth/backends/browserid.py +++ b/social_auth/backends/browserid.py @@ -1,85 +1 @@ -""" -BrowserID support -""" -from urllib import urlencode - -from django.contrib.auth import authenticate -from django.utils import simplejson - -from social_auth.backends import SocialAuthBackend, BaseAuth -from social_auth.utils import log, dsa_urlopen -from social_auth.exceptions import AuthFailed, AuthMissingParameter - - -# BrowserID verification server -BROWSER_ID_SERVER = 'https://verifier.login.persona.org/verify' - - -class BrowserIDBackend(SocialAuthBackend): - """BrowserID authentication backend""" - name = 'browserid' - - def get_user_id(self, details, response): - """Use BrowserID email as ID""" - return details['email'] - - def get_user_details(self, response): - """Return user details, BrowserID only provides Email.""" - # {'status': 'okay', - # 'audience': 'localhost:8000', - # 'expires': 1328983575529, - # 'email': 'name@server.com', - # 'issuer': 'login.persona.org'} - email = response['email'] - return {'username': email.split('@', 1)[0], - 'email': email, - 'fullname': '', - 'first_name': '', - 'last_name': ''} - - def extra_data(self, user, uid, response, details): - """Return users extra data""" - return { - 'audience': response['audience'], - 'issuer': response['issuer'] - } - - -# Auth classes -class BrowserIDAuth(BaseAuth): - """BrowserID authentication""" - AUTH_BACKEND = BrowserIDBackend - - def auth_complete(self, *args, **kwargs): - """Completes loging process, must return user instance""" - if not 'assertion' in self.data: - raise AuthMissingParameter(self, 'assertion') - - data = urlencode({ - 'assertion': self.data['assertion'], - 'audience': self.request.get_host() - }) - - try: - response = simplejson.load(dsa_urlopen(BROWSER_ID_SERVER, - data=data)) - except ValueError: - log('error', 'Could not load user data from BrowserID.', - exc_info=True) - else: - if response.get('status') == 'failure': - log('debug', 'Authentication failed.') - raise AuthFailed(self) - - kwargs.update({ - 'auth': self, - 'response': response, - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - - -# Backend definition -BACKENDS = { - 'browserid': BrowserIDAuth -} +from social.backends.persona import PersonaAuth as BrowserIDBackend diff --git a/social_auth/backends/contrib/angel.py b/social_auth/backends/contrib/angel.py index 503aa7732..02ff7c632 100644 --- a/social_auth/backends/contrib/angel.py +++ b/social_auth/backends/contrib/angel.py @@ -1,70 +1 @@ -""" -settings.py should include the following: - - ANGEL_CLIENT_ID = '...' - ANGEL_CLIENT_SECRET = '...' - -Optional scope to include 'email' and/or 'messages' separated by space: - - ANGEL_AUTH_EXTRA_ARGUMENTS = {'scope': 'email messages'} - -More information on scope can be found at https://angel.co/api/oauth/faq -""" -from urllib import urlencode - -from django.utils import simplejson - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import dsa_urlopen - - -ANGEL_SERVER = 'angel.co' -ANGEL_AUTHORIZATION_URL = 'https://angel.co/api/oauth/authorize/' -ANGEL_ACCESS_TOKEN_URL = 'https://angel.co/api/oauth/token/' -ANGEL_CHECK_AUTH = 'https://api.angel.co/1/me/' - - -class AngelBackend(OAuthBackend): - name = 'angel' - - def get_user_id(self, details, response): - return response['id'] - - def get_user_details(self, response): - """Return user details from Angel account""" - username = response['angellist_url'].split('/')[-1] - first_name = response['name'].split(' ')[0] - last_name = response['name'].split(' ')[-1] - email = response['email'] - return { - 'username': username, - 'first_name': first_name, - 'last_name': last_name, - 'email': email, - } - - -class AngelAuth(BaseOAuth2): - """Angel OAuth mechanism""" - AUTHORIZATION_URL = ANGEL_AUTHORIZATION_URL - ACCESS_TOKEN_URL = ANGEL_ACCESS_TOKEN_URL - AUTH_BACKEND = AngelBackend - SETTINGS_KEY_NAME = 'ANGEL_CLIENT_ID' - SETTINGS_SECRET_NAME = 'ANGEL_CLIENT_SECRET' - REDIRECT_STATE = False - STATE_PARAMETER = False - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - params = {'access_token': access_token} - url = ANGEL_CHECK_AUTH + '?' + urlencode(params) - try: - return simplejson.load(dsa_urlopen(url)) - except ValueError: - return None - - -# Backend definition -BACKENDS = { - 'angel': AngelAuth, -} +from social.backends.angel import AngelOAuth2 as AngelAuth diff --git a/social_auth/backends/contrib/appsfuel.py b/social_auth/backends/contrib/appsfuel.py index 66a328d18..55b75d469 100644 --- a/social_auth/backends/contrib/appsfuel.py +++ b/social_auth/backends/contrib/appsfuel.py @@ -1,66 +1,2 @@ -""" -This module is originally written: django-social-auth-appsfuel==1.0.0 -You could refer to https://github.com/AppsFuel/django-social-auth-appsfuel for issues - -settings.py should include the following: - - APPSFUEL_CLIENT_ID = '...' - APPSFUEL_CLIENT_SECRET = '...' - -""" -import json -from urllib import urlencode -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import dsa_urlopen - - -class AppsfuelBackend(OAuthBackend): - name = 'appsfuel' - - def get_user_id(self, details, response): - return response['user_id'] - - def get_user_details(self, response): - """Return user details from Appsfuel account""" - fullname = response.get('display_name', '') - email = response.get('email', '') - username = email.split('@')[0] if email else '' - return { - 'username': username, - 'first_name': fullname, - 'email': email - } - - -class AppsfuelAuth(BaseOAuth2): - """Appsfuel OAuth mechanism""" - AUTH_BACKEND = AppsfuelBackend - AUTHORIZATION_URL = 'http://app.appsfuel.com/content/permission' - ACCESS_TOKEN_URL = 'https://api.appsfuel.com/v1/live/oauth/token' - USER_URL = 'https://api.appsfuel.com/v1/live/user' - SETTINGS_KEY_NAME = 'APPSFUEL_CLIENT_ID' - SETTINGS_SECRET_NAME = 'APPSFUEL_CLIENT_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - params = {'access_token': access_token} - url = self.USER_URL + '?' + urlencode(params) - return json.load(dsa_urlopen(url)) - - -class AppsfuelSandboxBackend(AppsfuelBackend): - name = 'appsfuel-sandbox' - - -class AppsfuelSandboxAuth(AppsfuelAuth): - AUTH_BACKEND = AppsfuelSandboxBackend - AUTHORIZATION_URL = 'https://api.appsfuel.com/v1/sandbox/choose' - ACCESS_TOKEN_URL = 'https://api.appsfuel.com/v1/sandbox/oauth/token' - USER_URL = 'https://api.appsfuel.com/v1/sandbox/user' - - -# Backend definitions -BACKENDS = { - 'appsfuel': AppsfuelAuth, - 'appsfuel-sandbox': AppsfuelSandboxAuth, -} +from social.backends.appsfuel import AppsfuelOAuth2 as AppsfuelAuth, \ + AppsfuelOAuth2Sandbox as AppsfuelSandboxAuth diff --git a/social_auth/backends/contrib/behance.py b/social_auth/backends/contrib/behance.py index 1af91c588..e530fc39e 100644 --- a/social_auth/backends/contrib/behance.py +++ b/social_auth/backends/contrib/behance.py @@ -1,68 +1 @@ -""" -Behance OAuth2 support. - -This contribution adds support for the Behance OAuth service. The settings -BEHANCE_CLIENT_ID and BEHANCE_CLIENT_SECRET must be defined with the values -given by Behance application registration process. - -Extended permissions are supported by defining BEHANCE_EXTENDED_PERMISSIONS -setting, it must be a list of values to request. - -By default username and access_token are stored in extra_data field. -""" -from social_auth.backends import BaseOAuth2, OAuthBackend - - -# Behance configuration -BEHANCE_AUTHORIZATION_URL = 'https://www.behance.net/v2/oauth/authenticate' -BEHANCE_ACCESS_TOKEN_URL = 'https://www.behance.net/v2/oauth/token' - - -class BehanceBackend(OAuthBackend): - """Behance OAuth authentication backend""" - name = 'behance' - # Default extra data to store (in addition to access_token) - EXTRA_DATA = [ - ('username', 'username'), - ] - - def get_user_id(self, details, response): - return response['user']['id'] - - def get_user_details(self, response): - """Return user details from Behance account""" - user = response['user'] - return { - 'username': user['username'], - 'last_name': user['last_name'], - 'first_name': user['first_name'], - 'fullname': user['display_name'], - 'email': '', - } - - def extra_data(self, user, uid, response, details): - # Pull up the embedded user attributes so they can be found as extra - # data. See the example token response for possible attributes: - # http://www.behance.net/dev/authentication#step-by-step - all_data = dict((name, value) for name, value in response.iteritems()) - all_data.update(response['user']) - return super(BehanceBackend, self).extra_data(user, uid, all_data, - details) - - -class BehanceAuth(BaseOAuth2): - """Behance OAuth2 mechanism""" - AUTHORIZATION_URL = BEHANCE_AUTHORIZATION_URL - ACCESS_TOKEN_URL = BEHANCE_ACCESS_TOKEN_URL - AUTH_BACKEND = BehanceBackend - SETTINGS_KEY_NAME = 'BEHANCE_CLIENT_ID' - SETTINGS_SECRET_NAME = 'BEHANCE_CLIENT_SECRET' - SCOPE_SEPARATOR = '|' - ### Look at http://www.behance.net/dev/authentication#scopes - SCOPE_VAR_NAME = 'BEHANCE_EXTENDED_PERMISSIONS' - - -# Backend definition -BACKENDS = { - 'behance': BehanceAuth -} +from social.backends.behance import BehanceOAuth2 as BehanceBackend diff --git a/social_auth/backends/contrib/belgiumeid.py b/social_auth/backends/contrib/belgiumeid.py index 9d9a8ffa9..17a48c6e4 100644 --- a/social_auth/backends/contrib/belgiumeid.py +++ b/social_auth/backends/contrib/belgiumeid.py @@ -1,22 +1 @@ -from social_auth.backends import OpenIDBackend, OpenIdAuth - -E_ID_OPENID_URL = 'https://www.e-contract.be/eid-idp/endpoints/openid/auth' - - -class EIDBackend(OpenIDBackend): - """e-ID OpenID authentication backend""" - name = 'eID' - - -class EIDAuth(OpenIdAuth): - """Belgium e-ID OpenID authentication""" - AUTH_BACKEND = EIDBackend - - def openid_url(self): - """Return Belgium e-ID OpenID service url""" - return E_ID_OPENID_URL - -# Backend definition -BACKENDS = { - 'eID': EIDAuth, -} +from social.backends.belgiumeid import BelgiumEIDOpenId as EIDAuth diff --git a/social_auth/backends/contrib/bitbucket.py b/social_auth/backends/contrib/bitbucket.py index c6f0bfead..794708bec 100644 --- a/social_auth/backends/contrib/bitbucket.py +++ b/social_auth/backends/contrib/bitbucket.py @@ -1,107 +1 @@ -""" -Bitbucket OAuth support. - -This adds support for Bitbucket OAuth service. An application must -be registered first on Bitbucket and the settings BITBUCKET_CONSUMER_KEY -and BITBUCKET_CONSUMER_SECRET must be defined with the corresponding -values. - -By default username, email, token expiration time, first name and last name are -stored in extra_data field, check OAuthBackend class for details on how to -extend it. -""" -from django.utils import simplejson -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend -from social_auth.utils import dsa_urlopen - -# Bitbucket configuration -BITBUCKET_SERVER = 'bitbucket.org/api/1.0' -BITBUCKET_REQUEST_TOKEN_URL = 'https://%s/oauth/request_token' % \ - BITBUCKET_SERVER -BITBUCKET_ACCESS_TOKEN_URL = 'https://%s/oauth/access_token' % BITBUCKET_SERVER -BITBUCKET_AUTHORIZATION_URL = 'https://%s/oauth/authenticate' % \ - BITBUCKET_SERVER -BITBUCKET_EMAIL_DATA_URL = 'https://%s/emails/' % BITBUCKET_SERVER -BITBUCKET_USER_DATA_URL = 'https://%s/users/' % BITBUCKET_SERVER - - -class BitbucketBackend(OAuthBackend): - """Bitbucket OAuth authentication backend""" - name = 'bitbucket' - EXTRA_DATA = [ - ('username', 'username'), - ('expires', 'expires'), - ('email', 'email'), - ('first_name', 'first_name'), - ('last_name', 'last_name') - ] - - def get_user_details(self, response): - """Return user details from Bitbucket account""" - return {'username': response.get('username'), - 'email': response.get('email'), - 'fullname': ' '.join((response.get('first_name'), - response.get('last_name'))), - 'first_name': response.get('first_name'), - 'last_name': response.get('last_name')} - - def get_user_id(self, details, response): - """Return the user id, Bitbucket only provides username as a unique - identifier""" - return response['username'] - - @classmethod - def tokens(cls, instance): - """Return the tokens needed to authenticate the access to any API the - service might provide. Bitbucket uses a pair of OAuthToken consisting - on a oauth_token and oauth_token_secret. - - instance must be a UserSocialAuth instance. - """ - token = super(BitbucketBackend, cls).tokens(instance) - if token and 'access_token' in token: - token = dict(tok.split('=') - for tok in token['access_token'].split('&')) - return token - - -class BitbucketAuth(ConsumerBasedOAuth): - """Bitbucket OAuth authentication mechanism""" - AUTHORIZATION_URL = BITBUCKET_AUTHORIZATION_URL - REQUEST_TOKEN_URL = BITBUCKET_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = BITBUCKET_ACCESS_TOKEN_URL - AUTH_BACKEND = BitbucketBackend - SETTINGS_KEY_NAME = 'BITBUCKET_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'BITBUCKET_CONSUMER_SECRET' - - def user_data(self, access_token): - """Return user data provided""" - # Bitbucket has a bit of an indirect route to obtain user data from an - # authenticated query: First obtain the user's email via an - # authenticated GET - url = BITBUCKET_EMAIL_DATA_URL - request = self.oauth_request(access_token, url) - response = self.fetch_response(request) - try: - # Then retrieve the user's primary email address or the top email - email_addresses = simplejson.loads(response) - for email_address in reversed(email_addresses): - if email_address['active']: - email = email_address['email'] - if email_address['primary']: - break - # Then return the user data using a normal GET with the - # BITBUCKET_USER_DATA_URL and the user's email - response = dsa_urlopen(BITBUCKET_USER_DATA_URL + email) - user_details = simplejson.load(response)['user'] - user_details['email'] = email - return user_details - except ValueError: - return None - return None - - -# Backend definition -BACKENDS = { - 'bitbucket': BitbucketAuth, -} +from social.backends.bitbucket import BitbucketOAuth as BitbucketAuth diff --git a/social_auth/backends/contrib/dailymotion.py b/social_auth/backends/contrib/dailymotion.py index d8b9989a4..4a8679b86 100644 --- a/social_auth/backends/contrib/dailymotion.py +++ b/social_auth/backends/contrib/dailymotion.py @@ -1,81 +1 @@ -""" -Dailymotion OAuth2 support. - -This adds support for Dailymotion OAuth service. An application must -be registered first on dailymotion and the settings DAILYMOTION_CONSUMER_KEY -and DAILYMOTION_CONSUMER_SECRET must be defined with the corresponding -values. - -User screen name is used to generate username. - -By default account id is stored in extra_data field, check OAuthBackend -class for details on how to extend it. -""" -from urllib2 import HTTPError - -from django.utils import simplejson - -from social_auth.utils import dsa_urlopen -from social_auth.backends import BaseOAuth2 -from social_auth.backends import SocialAuthBackend -from social_auth.exceptions import AuthCanceled - - -# Dailymotion configuration -DAILYMOTION_SERVER = 'api.dailymotion.com' -DAILYMOTION_REQUEST_TOKEN_URL = 'https://%s/oauth/token' % DAILYMOTION_SERVER -DAILYMOTION_ACCESS_TOKEN_URL = 'https://%s/oauth/token' % DAILYMOTION_SERVER -# Note: oauth/authorize forces the user to authorize every time. -# oauth/authenticate uses their previous selection, barring revocation. -DAILYMOTION_AUTHORIZATION_URL = 'https://%s/oauth/authorize' % \ - DAILYMOTION_SERVER -DAILYMOTION_CHECK_AUTH = 'https://%s/me/?access_token=' % DAILYMOTION_SERVER - - -class DailymotionBackend(SocialAuthBackend): - """Dailymotion OAuth authentication backend""" - name = 'dailymotion' - EXTRA_DATA = [('id', 'id')] - - def get_user_id(self, details, response): - """Use dailymotion username as unique id""" - return details['username'] - - def get_user_details(self, response): - return {'username': response['screenname']} - - -class DailymotionAuth(BaseOAuth2): - """Dailymotion OAuth2 authentication mechanism""" - - AUTHORIZATION_URL = DAILYMOTION_AUTHORIZATION_URL - REQUEST_TOKEN_URL = DAILYMOTION_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = DAILYMOTION_ACCESS_TOKEN_URL - AUTH_BACKEND = DailymotionBackend - SETTINGS_KEY_NAME = 'DAILYMOTION_OAUTH2_KEY' - SETTINGS_SECRET_NAME = 'DAILYMOTION_OAUTH2_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - try: - data = dsa_urlopen(DAILYMOTION_CHECK_AUTH + access_token).read() - return simplejson.loads(data) - except (ValueError, HTTPError): - return None - - def auth_complete(self, *args, **kwargs): - """Completes login process, must return user instance""" - if 'denied' in self.data: - raise AuthCanceled(self) - else: - return super(DailymotionAuth, self).auth_complete(*args, **kwargs) - - def oauth_request(self, token, url, extra_params=None): - extra_params = extra_params or {} - return extra_params - - -# Backend definition -BACKENDS = { - 'dailymotion': DailymotionAuth, -} +from social.backends.dailymotion import DailymotionOAuth2 as DailymotionBackend diff --git a/social_auth/backends/contrib/disqus.py b/social_auth/backends/contrib/disqus.py index 186eda470..30c6fe3a9 100644 --- a/social_auth/backends/contrib/disqus.py +++ b/social_auth/backends/contrib/disqus.py @@ -1,73 +1 @@ -from django.utils import simplejson -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import dsa_urlopen, backend_setting -from urllib import urlencode - - -DISQUS_SERVER = 'disqus.com' -DISQUS_AUTHORIZATION_URL = 'https://disqus.com/api/oauth/2.0/authorize/' -DISQUS_ACCESS_TOKEN_URL = 'https://disqus.com/api/oauth/2.0/access_token/' -DISQUS_CHECK_AUTH = 'https://disqus.com/api/3.0/users/details.json' - - -class DisqusBackend(OAuthBackend): - name = 'disqus' - - EXTRA_DATA = [ - ('avatar', 'avatar'), - ('connections', 'connections'), - ('user_id', 'user_id'), - ('email', 'email'), - ('email_hash', 'emailHash'), - ('expires', 'expires'), - ('location', 'location'), - ('meta', 'response'), - ('name', 'name'), - ('username', 'username'), - ] - - def get_user_id(self, details, response): - return response['response']['id'] - - def get_user_details(self, response): - """Return user details from Disqus account""" - rr = response.get('response', {}) - - return { - 'username': rr.get('username', ''), - 'user_id': response.get('user_id', ''), - 'email': rr.get('email', ''), - 'name': rr.get('name', ''), - } - - def extra_data(self, user, uid, response, details): - meta_response = dict(response, **response.get('response', {})) - return super(DisqusBackend, self).extra_data(user, uid, meta_response, - details) - - -class DisqusAuth(BaseOAuth2): - """Disqus OAuth mechanism""" - AUTHORIZATION_URL = DISQUS_AUTHORIZATION_URL - ACCESS_TOKEN_URL = DISQUS_ACCESS_TOKEN_URL - AUTH_BACKEND = DisqusBackend - SETTINGS_KEY_NAME = 'DISQUS_CLIENT_ID' - SETTINGS_SECRET_NAME = 'DISQUS_CLIENT_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - params = { - 'access_token': access_token, - 'api_secret': backend_setting(self, self.SETTINGS_SECRET_NAME), - } - url = DISQUS_CHECK_AUTH + '?' + urlencode(params) - try: - return simplejson.load(dsa_urlopen(url)) - except ValueError: - return None - - -# Backend definition -BACKENDS = { - 'disqus': DisqusAuth, -} +from social.backends.disqus import DisqusOAuth2 as DisqusBackend diff --git a/social_auth/backends/contrib/douban.py b/social_auth/backends/contrib/douban.py index 414dc8ca7..34d609d74 100644 --- a/social_auth/backends/contrib/douban.py +++ b/social_auth/backends/contrib/douban.py @@ -1,122 +1,2 @@ -""" -Douban OAuth support. - -This adds support for Douban OAuth service. An application must -be registered first on douban.com and the settings DOUBAN_CONSUMER_KEY -and DOUBAN_CONSUMER_SECRET must be defined with they corresponding -values. - -By default account id is stored in extra_data field, check OAuthBackend -class for details on how to extend it. -""" -from urllib2 import Request - -from django.utils import simplejson - -from social_auth.utils import dsa_urlopen -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend, BaseOAuth2 -from social_auth.exceptions import AuthCanceled - - -DOUBAN_SERVER = 'www.douban.com' -DOUBAN_REQUEST_TOKEN_URL = 'http://%s/service/auth/request_token' % \ - DOUBAN_SERVER -DOUBAN_ACCESS_TOKEN_URL = 'http://%s/service/auth/access_token' % \ - DOUBAN_SERVER - -DOUBAN_AUTHORIZATION_URL = 'http://%s/service/auth/authorize' % \ - DOUBAN_SERVER - - -class DoubanBackend(OAuthBackend): - """Douban OAuth authentication backend""" - name = 'douban' - EXTRA_DATA = [('id', 'id')] - - def get_user_id(self, details, response): - return response['db:uid']['$t'] - - def get_user_details(self, response): - """Return user details from Douban""" - return {'username': response["db:uid"]["$t"], - 'email': ''} - - -class DoubanAuth(ConsumerBasedOAuth): - """Douban OAuth authentication mechanism""" - AUTHORIZATION_URL = DOUBAN_AUTHORIZATION_URL - REQUEST_TOKEN_URL = DOUBAN_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = DOUBAN_ACCESS_TOKEN_URL - AUTH_BACKEND = DoubanBackend - SETTINGS_KEY_NAME = 'DOUBAN_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'DOUBAN_CONSUMER_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - url = 'http://api.douban.com/people/%40me?&alt=json' - request = self.oauth_request(access_token, url) - json = self.fetch_response(request) - - try: - return simplejson.loads(json) - except ValueError: - return None - - def auth_complete(self, *args, **kwargs): - """Completes login process, must return user instance""" - if 'denied' in self.data: - raise AuthCanceled(self) - else: - return super(DoubanAuth, self).auth_complete(*args, **kwargs) - - -class DoubanBackend2(OAuthBackend): - """Douban OAuth authentication backend""" - name = 'douban2' - EXTRA_DATA = [('id', 'id'), - ('uid', 'username'), - ('refresh_token', 'refresh_token'), - ] - - def get_user_id(self, details, response): - return response['id'] - - def get_user_details(self, response): - """Return user details from Douban""" - return {'username': response.get('uid', ''), - 'fullname': response.get('name', ''), - 'email': ''} - - -class DoubanAuth2(BaseOAuth2): - """Douban OAuth authentication mechanism""" - AUTHORIZATION_URL = 'https://%s/service/auth2/auth' % DOUBAN_SERVER - ACCESS_TOKEN_URL = 'https://%s/service/auth2/token' % DOUBAN_SERVER - AUTH_BACKEND = DoubanBackend2 - SETTINGS_KEY_NAME = 'DOUBAN2_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'DOUBAN2_CONSUMER_SECRET' - REDIRECT_STATE = False - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - url = 'https://api.douban.com/v2/user/~me' - headers = {'Authorization': 'Bearer %s' % access_token} - request = Request(url, headers=headers) - try: - return simplejson.loads(dsa_urlopen(request).read()) - except (ValueError, KeyError, IOError): - return None - - def auth_complete(self, *args, **kwargs): - """Completes login process, must return user instance""" - if 'denied' in self.data: - raise AuthCanceled(self) - else: - return super(DoubanAuth2, self).auth_complete(*args, **kwargs) - - -# Backend definition -BACKENDS = { - 'douban': DoubanAuth, - 'douban2': DoubanAuth2, # OAuth2.0 -} +from social.backends.douban import DoubanOAuth as DoubanBackend, \ + DoubanOAuth2 as Douban2Backend diff --git a/social_auth/backends/contrib/dropbox.py b/social_auth/backends/contrib/dropbox.py index 154f5f74e..e3e34475f 100644 --- a/social_auth/backends/contrib/dropbox.py +++ b/social_auth/backends/contrib/dropbox.py @@ -1,74 +1 @@ -""" -Dropbox OAuth support. - -This contribution adds support for Dropbox OAuth service. The settings -DROPBOX_APP_ID and DROPBOX_API_SECRET must be defined with the values -given by Dropbox application registration process. - -By default account id and token expiration time are stored in extra_data -field, check OAuthBackend class for details on how to extend it. -""" -from django.utils import simplejson - -from social_auth.utils import setting -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend - - -# Dropbox configuration -DROPBOX_SERVER = 'dropbox.com' -DROPBOX_API = 'api.%s' % DROPBOX_SERVER -DROPBOX_REQUEST_TOKEN_URL = 'https://%s/1/oauth/request_token' % DROPBOX_API -DROPBOX_AUTHORIZATION_URL = 'https://www.%s/1/oauth/authorize' % DROPBOX_SERVER -DROPBOX_ACCESS_TOKEN_URL = 'https://%s/1/oauth/access_token' % DROPBOX_API - - -class DropboxBackend(OAuthBackend): - """Dropbox OAuth authentication backend""" - name = 'dropbox' - # Default extra data to store - EXTRA_DATA = [ - ('id', 'id'), - ('expires', 'expires') - ] - - def get_user_details(self, response): - """Return user details from Dropbox account""" - return {'username': str(response.get('uid')), - 'email': response.get('email'), - 'first_name': response.get('display_name')} - - def get_user_id(self, details, response): - """OAuth providers return an unique user id in response""" - # Dropbox uses a uid parameter instead of id like most others... - return response['uid'] - - -class DropboxAuth(ConsumerBasedOAuth): - """Dropbox OAuth authentication mechanism""" - AUTHORIZATION_URL = DROPBOX_AUTHORIZATION_URL - REQUEST_TOKEN_URL = DROPBOX_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = DROPBOX_ACCESS_TOKEN_URL - AUTH_BACKEND = DropboxBackend - SETTINGS_KEY_NAME = 'DROPBOX_APP_ID' - SETTINGS_SECRET_NAME = 'DROPBOX_API_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - url = 'https://' + DROPBOX_API + '/1/account/info' - request = self.oauth_request(access_token, url) - response = self.fetch_response(request) - try: - return simplejson.loads(response) - except ValueError: - return None - - @classmethod - def enabled(cls): - """Return backend enabled status by checking basic settings""" - return setting('DROPBOX_APP_ID') and setting('DROPBOX_API_SECRET') - - -# Backend definition -BACKENDS = { - 'dropbox': DropboxAuth, -} +from social.backends.dropbox import DropboxOAuth as DropboxAuth diff --git a/social_auth/backends/contrib/evernote.py b/social_auth/backends/contrib/evernote.py index 05a1ff314..8fecea288 100644 --- a/social_auth/backends/contrib/evernote.py +++ b/social_auth/backends/contrib/evernote.py @@ -1,119 +1,7 @@ -""" -EverNote OAuth support +from django.conf import settings -No extra configurations are needed to make this work. -""" -from urllib2 import HTTPError -try: - from urlparse import parse_qs - parse_qs # placate pyflakes -except ImportError: - # fall back for Python 2.5 - from cgi import parse_qs -from oauth2 import Token -from social_auth.utils import setting -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend -from social_auth.exceptions import AuthCanceled - - -if setting('EVERNOTE_DEBUG', False): - EVERNOTE_SERVER = 'sandbox.evernote.com' +if getattr(settings, 'EVERNOTE_DEBUG', False): + from social.backends.evernote import EvernoteSandboxOAuth as EvernoteAuth else: - EVERNOTE_SERVER = 'www.evernote.com' - -EVERNOTE_REQUEST_TOKEN_URL = 'https://%s/oauth' % EVERNOTE_SERVER -EVERNOTE_ACCESS_TOKEN_URL = 'https://%s/oauth' % EVERNOTE_SERVER -EVERNOTE_AUTHORIZATION_URL = 'https://%s/OAuth.action' % EVERNOTE_SERVER - - -class EvernoteBackend(OAuthBackend): - """ - Evernote OAuth authentication backend. - - Possible Values: - {'edam_expires': ['1367525289541'], - 'edam_noteStoreUrl': [ - 'https://sandbox.evernote.com/shard/s1/notestore' - ], - 'edam_shard': ['s1'], - 'edam_userId': ['123841'], - 'edam_webApiUrlPrefix': ['https://sandbox.evernote.com/shard/s1/'], - 'oauth_token': [ - 'S=s1:U=1e3c1:E=13e66dbee45:C=1370f2ac245:P=185:A=my_user:' \ - 'H=411443c5e8b20f8718ed382a19d4ae38' - ]} - """ - name = 'evernote' - - EXTRA_DATA = [ - ('access_token', 'access_token'), - ('oauth_token', 'oauth_token'), - ('edam_noteStoreUrl', 'store_url'), - ('edam_expires', 'expires') - ] - - @classmethod - def extra_data(cls, user, uid, response, details=None): - data = super(EvernoteBackend, cls).extra_data(user, uid, response, details) - # Evernote returns expiration timestamp in miliseconds, so it needs to - # be normalized. - if 'expires' in data: - data['expires'] = unicode(int(data['expires']) / 1000) - return data - - def get_user_details(self, response): - """Return user details from Evernote account""" - return { - 'username': response['edam_userId'], - 'email': '', - } - - def get_user_id(self, details, response): - return response['edam_userId'] - - -class EvernoteAuth(ConsumerBasedOAuth): - """Evernote OAuth authentication mechanism""" - AUTHORIZATION_URL = EVERNOTE_AUTHORIZATION_URL - REQUEST_TOKEN_URL = EVERNOTE_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = EVERNOTE_ACCESS_TOKEN_URL - AUTH_BACKEND = EvernoteBackend - SETTINGS_KEY_NAME = 'EVERNOTE_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'EVERNOTE_CONSUMER_SECRET' - - def access_token(self, token): - """Return request for access token value""" - request = self.oauth_request(token, self.ACCESS_TOKEN_URL) - - try: - response = self.fetch_response(request) - except HTTPError, e: - # Evernote returns a 401 error when AuthCanceled - if e.code == 401: - raise AuthCanceled(self) - else: - raise - - params = parse_qs(response) - - # evernote sents a empty secret token, this way it doesn't fires up the - # exception - response = response.replace('oauth_token_secret=', - 'oauth_token_secret=None') - token = Token.from_string(response) - - token.user_info = params - return token - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - # drop lists - return dict([(key, val[0]) for key, val in - access_token.user_info.items()]) - - -# Backend definition -BACKENDS = { - 'evernote': EvernoteAuth, -} + from social.backends.evernote import EvernoteOAuth as EvernoteAuth diff --git a/social_auth/backends/contrib/exacttarget.py b/social_auth/backends/contrib/exacttarget.py index 51a8b1ca4..a5359f711 100644 --- a/social_auth/backends/contrib/exacttarget.py +++ b/social_auth/backends/contrib/exacttarget.py @@ -1,124 +1 @@ -""" -ExactTarget OAuth support. -Support Authentication from IMH using JWT token and pre-shared key. -Requires package pyjwt -""" -import imp -from datetime import timedelta, datetime -from django.contrib.auth import authenticate - -from social_auth.utils import setting -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.exceptions import AuthFailed, AuthCanceled - - -class ExactTargetBackend(OAuthBackend): - """ExactTarget HubExchange authentication backend""" - name = 'exacttarget' - # Default extra data to store - EXTRA_DATA = [] - - def get_user_details(self, response): - """Use the email address of the user, suffixed by _et""" - if response.get("token"): - token = response['token'] - user = token.get('request', {}).get('user') - if user: - if 'email' in user: - user['username'] = "%s_et" % user['email'] - return user - - def extra_data(self, user, uid, response, details): - """ - Load extra details from the JWT token - """ - data = { - 'email': details.get('email'), - 'id': details.get('id'), - # OAuth token, for use with legacy SOAP API calls: - # http://bit.ly/13pRHfo - 'internalOauthToken': details.get('internalOauthToken'), - # Token for use with the Application ClientID for the FUEL API - 'oauthToken': details.get('oauthToken'), - # If the token has expired, use the FUEL API to get a new token see - # http://bit.ly/10v1K5l and http://bit.ly/11IbI6F - set legacy=1 - 'refreshToken': details.get('refreshToken'), - } - - # The expiresIn value determines how long the tokens are valid for. - # Take a bit off, then convert to an int timestamp - expiresSeconds = details.get('expiresIn', 0) - 30 - expires = datetime.utcnow() + timedelta(seconds=expiresSeconds) - data['expires'] = (expires - datetime(1970, 1, 1)).total_seconds() - - if response.get("token"): - token = response['token'] - org = token.get('request', {}).get('organization') - if org: - data['stack'] = org.get('stackKey') - data['enterpriseId'] = org.get('enterpriseId') - return data - - def get_user_id(self, details, response): - """Create a user ID from the ET user ID""" - return "exacttarget_%s" % details.get('id') - - def uses_redirect(self): - return False - - -class ExactTargetAuth(BaseOAuth2): - """ExactTarget authentication mechanism""" - AUTH_BACKEND = ExactTargetBackend - SETTINGS_KEY_NAME = 'EXACTTARGET_UNUSED' - # Set this to your application signature (from code.exacttarget.com) - SETTINGS_SECRET_NAME = 'EXACTTARGET_APP_SIGNATURE' - - def __init__(self, request, redirect): - super(ExactTargetAuth, self).__init__(request, redirect) - fp, pathname, description = imp.find_module('jwt') - self.jwt = imp.load_module('jwt', fp, pathname, description) - - def auth_url(self): - return None - - def auth_complete(self, *args, **kwargs): - """Completes login process, must return user instance""" - - if self.data.get('error'): - error = self.data.get('error_description') or self.data['error'] - raise AuthFailed(self, error) - - token = kwargs.get('request').POST.get('jwt', {}) - - if not token: - raise AuthFailed(self, 'Authentication Failed') - return self.do_auth(token, *args, **kwargs) - - def do_auth(self, jwt_token, *args, **kwargs): - dummy, client_secret = self.get_key_and_secret() - - # Decode the jwt token, using the Application Signature from settings - try: - decoded = self.jwt.decode(jwt_token, client_secret) - except self.jwt.DecodeError: - raise AuthCanceled(self) # Wrong signature, fail authentication - - kwargs.update({ - 'auth': self, - 'response': { - 'token': decoded, - }, - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - - @classmethod - def enabled(cls): - """Return backend enabled status by checking basic settings""" - return setting('EXACTTARGET_APP_SIGNATURE') - -# Backend definition -BACKENDS = { - 'exacttarget': ExactTargetAuth, -} +from social.backends.exacttarget import ExactTargetOAuth2 as ExactTargetBackend diff --git a/social_auth/backends/contrib/fedora.py b/social_auth/backends/contrib/fedora.py index 3c948b8d1..658921c3a 100644 --- a/social_auth/backends/contrib/fedora.py +++ b/social_auth/backends/contrib/fedora.py @@ -1,29 +1 @@ -""" -Fedora OpenID support - -No extra configurations are needed to make this work. -""" -from social_auth.backends import OpenIDBackend, OpenIdAuth - - -FEDORA_OPENID_URL = 'https://id.fedoraproject.org' - - -class FedoraBackend(OpenIDBackend): - """Fedora OpenID authentication backend""" - name = 'fedora' - - -class FedoraAuth(OpenIdAuth): - """Fedora OpenID authentication""" - AUTH_BACKEND = FedoraBackend - - def openid_url(self): - """Return Fedora OpenID service url""" - return FEDORA_OPENID_URL - - -# Backend definition -BACKENDS = { - 'fedora': FedoraAuth, -} +from social.backends.fedora import FedoraOpenId as FedoraBackend diff --git a/social_auth/backends/contrib/fitbit.py b/social_auth/backends/contrib/fitbit.py index 8aec5143a..3529bf789 100644 --- a/social_auth/backends/contrib/fitbit.py +++ b/social_auth/backends/contrib/fitbit.py @@ -1,89 +1 @@ -""" -Fitbit OAuth support. - -This contribution adds support for Fitbit OAuth service. The settings -FITBIT_CONSUMER_KEY and FITBIT_CONSUMER_SECRET must be defined with the values -given by Fitbit application registration process. - -By default account id, username and token expiration time are stored in -extra_data field, check OAuthBackend class for details on how to extend it. -""" -try: - from urlparse import parse_qs - parse_qs # placate pyflakes -except ImportError: - # fall back for Python 2.5 - from cgi import parse_qs - -from oauth2 import Token - -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend - - -# Fitbit configuration -FITBIT_SERVER = 'https://api.fitbit.com' -FITBIT_REQUEST_TOKEN_URL = '%s/oauth/request_token' % FITBIT_SERVER -FITBIT_AUTHORIZATION_URL = '%s/oauth/authorize' % FITBIT_SERVER -FITBIT_ACCESS_TOKEN_URL = '%s/oauth/access_token' % FITBIT_SERVER -FITBIT_USERINFO = 'http://api.fitbit.com/1/user/-/profile.json' - - -class FitbitBackend(OAuthBackend): - """Fitbit OAuth authentication backend""" - name = 'fitbit' - # Default extra data to store - EXTRA_DATA = [('id', 'id'), - ('username', 'username'), - ('expires', 'expires')] - - def get_user_id(self, details, response): - """ - Fitbit doesn't provide user data, it must be requested to its API: - https://wiki.fitbit.com/display/API/API-Get-User-Info - """ - return response['id'] - - def get_user_details(self, response): - """Return user details from Fitbit account""" - return {'username': response.get('id'), - 'email': '', - 'first_name': response.get('fullname')} - - -class FitbitAuth(ConsumerBasedOAuth): - """Fitbit OAuth authentication mechanism""" - AUTHORIZATION_URL = FITBIT_AUTHORIZATION_URL - REQUEST_TOKEN_URL = FITBIT_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = FITBIT_ACCESS_TOKEN_URL - AUTH_BACKEND = FitbitBackend - SETTINGS_KEY_NAME = 'FITBIT_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'FITBIT_CONSUMER_SECRET' - - def access_token(self, token): - """Return request for access token value""" - # Fitbit is a bit different - it passes user information along with - # the access token, so temporarily store it to vie the user_data - # method easy access later in the flow! - request = self.oauth_request(token, self.ACCESS_TOKEN_URL) - response = self.fetch_response(request) - token = Token.from_string(response) - params = parse_qs(response) - - token.encoded_user_id = params.get('encoded_user_id', [None])[0] - token.fullname = params.get('fullname', [None])[0] - token.username = params.get('username', [None])[0] - return token - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - return { - 'id': access_token.encoded_user_id, - 'username': access_token.username, - 'fullname': access_token.fullname, - } - - -# Backend definition -BACKENDS = { - 'fitbit': FitbitAuth, -} +from social.backends.fitbit import FitbitOAuth as FitbitAuth diff --git a/social_auth/backends/contrib/flickr.py b/social_auth/backends/contrib/flickr.py index 39dbb7984..f319c1360 100644 --- a/social_auth/backends/contrib/flickr.py +++ b/social_auth/backends/contrib/flickr.py @@ -1,92 +1 @@ -""" -Flickr OAuth support. - -This contribution adds support for Flickr OAuth service. The settings -FLICKR_APP_ID and FLICKR_API_SECRET must be defined with the values -given by Flickr application registration process. - -By default account id, username and token expiration time are stored in -extra_data field, check OAuthBackend class for details on how to extend it. -""" -try: - from urlparse import parse_qs - parse_qs # placate pyflakes -except ImportError: - # fall back for Python 2.5 - from cgi import parse_qs - -from oauth2 import Token - -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend - - -# Flickr configuration -FLICKR_SERVER = 'http://www.flickr.com/services' -FLICKR_REQUEST_TOKEN_URL = '%s/oauth/request_token' % FLICKR_SERVER -FLICKR_AUTHORIZATION_URL = '%s/oauth/authorize' % FLICKR_SERVER -FLICKR_ACCESS_TOKEN_URL = '%s/oauth/access_token' % FLICKR_SERVER - - -class FlickrBackend(OAuthBackend): - """Flickr OAuth authentication backend""" - name = 'flickr' - # Default extra data to store - EXTRA_DATA = [ - ('id', 'id'), - ('username', 'username'), - ('expires', 'expires') - ] - - def get_user_details(self, response): - """Return user details from Flickr account""" - return {'username': response.get('id'), - 'email': '', - 'first_name': response.get('fullname')} - - -class FlickrAuth(ConsumerBasedOAuth): - """Flickr OAuth authentication mechanism""" - AUTHORIZATION_URL = FLICKR_AUTHORIZATION_URL - REQUEST_TOKEN_URL = FLICKR_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = FLICKR_ACCESS_TOKEN_URL - AUTH_BACKEND = FlickrBackend - SETTINGS_KEY_NAME = 'FLICKR_APP_ID' - SETTINGS_SECRET_NAME = 'FLICKR_API_SECRET' - - def access_token(self, token): - """Return request for access token value""" - # Flickr is a bit different - it passes user information along with - # the access token, so temporarily store it to view the user_data - # method easy access later in the flow! - request = self.oauth_request(token, self.ACCESS_TOKEN_URL) - response = self.fetch_response(request) - token = Token.from_string(response) - params = parse_qs(response) - - token.user_nsid = params['user_nsid'][0] if 'user_nsid' in params \ - else None - token.fullname = params['fullname'][0] if 'fullname' in params \ - else None - token.username = params['username'][0] if 'username' in params \ - else None - return token - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - return { - 'id': access_token.user_nsid, - 'username': access_token.username, - 'fullname': access_token.fullname, - } - - def auth_extra_arguments(self): - params = super(FlickrAuth, self).auth_extra_arguments() or {} - if not 'perms' in params: - params['perms'] = 'read' - return params - - -# Backend definition -BACKENDS = { - 'flickr': FlickrAuth, -} +from social.backends.flickr import FlickrOAuth as FlickrBackend diff --git a/social_auth/backends/contrib/foursquare.py b/social_auth/backends/contrib/foursquare.py index 073760436..730865efc 100644 --- a/social_auth/backends/contrib/foursquare.py +++ b/social_auth/backends/contrib/foursquare.py @@ -1,53 +1 @@ -from urllib import urlencode - -from django.utils import simplejson - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import dsa_urlopen - - -FOURSQUARE_SERVER = 'foursquare.com' -FOURSQUARE_AUTHORIZATION_URL = 'https://foursquare.com/oauth2/authenticate' -FOURSQUARE_ACCESS_TOKEN_URL = 'https://foursquare.com/oauth2/access_token' -FOURSQUARE_CHECK_AUTH = 'https://api.foursquare.com/v2/users/self' - - -class FoursquareBackend(OAuthBackend): - name = 'foursquare' - - def get_user_id(self, details, response): - return response['response']['user']['id'] - - def get_user_details(self, response): - """Return user details from Foursquare account""" - firstName = response['response']['user']['firstName'] - lastName = response['response']['user'].get('lastName', '') - email = response['response']['user']['contact']['email'] - return {'username': firstName + ' ' + lastName, - 'first_name': firstName, - 'last_name': lastName, - 'email': email} - - -class FoursquareAuth(BaseOAuth2): - """Foursquare OAuth mechanism""" - AUTHORIZATION_URL = FOURSQUARE_AUTHORIZATION_URL - ACCESS_TOKEN_URL = FOURSQUARE_ACCESS_TOKEN_URL - AUTH_BACKEND = FoursquareBackend - SETTINGS_KEY_NAME = 'FOURSQUARE_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'FOURSQUARE_CONSUMER_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - params = {'oauth_token': access_token} - url = FOURSQUARE_CHECK_AUTH + '?' + urlencode(params) - try: - return simplejson.load(dsa_urlopen(url)) - except ValueError: - return None - - -# Backend definition -BACKENDS = { - 'foursquare': FoursquareAuth, -} +from social.backends.foursquare import FoursquareOAuth2 as FoursquareBackend diff --git a/social_auth/backends/contrib/gae.py b/social_auth/backends/contrib/gae.py index 2b0a6d047..1ddd17fa7 100644 --- a/social_auth/backends/contrib/gae.py +++ b/social_auth/backends/contrib/gae.py @@ -1,67 +1 @@ -""" -Google App Engine support using User API - -This backend is for use of django-social-auth on top -of Google's App Engine PaaS. - -This backend directly uses Google's User API that -is available on the App Engine platform. -""" -from __future__ import absolute_import - -from google.appengine.api import users - -from django.contrib.auth import authenticate -from django.core.urlresolvers import reverse - -from social_auth.backends import SocialAuthBackend, BaseAuth -from social_auth.exceptions import AuthException - - -class GAEBackend(SocialAuthBackend): - """GoogleAppengine authentication backend""" - name = 'google-appengine' - - def get_user_id(self, details, response): - """Return current user id.""" - user = users.get_current_user() - if user: - return user.user_id() - - def get_user_details(self, response): - """Return user basic information (id and email only).""" - user = users.get_current_user() - return {'username': user.user_id(), - 'email': user.email(), - 'fullname': '', - 'first_name': '', - 'last_name': ''} - - -# Auth classes -class GAEAuth(BaseAuth): - """GoogleAppengine authentication""" - AUTH_BACKEND = GAEBackend - - def auth_url(self): - """Build and return complete URL.""" - return users.create_login_url(reverse('socialauth_complete', - args=(self.AUTH_BACKEND.name,))) - - def auth_complete(self, *args, **kwargs): - """Completes login process, must return user instance.""" - if not users.get_current_user(): - raise AuthException('Authentication error') - - # Setting these two are necessary for BaseAuth.authenticate to work - kwargs.update({ - 'response': '', - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - - -# Backend definition -BACKENDS = { - 'google-appengine': GAEAuth, -} +from social.backends.gae import GoogleAppEngineAuth as GAEAuth diff --git a/social_auth/backends/contrib/github.py b/social_auth/backends/contrib/github.py index 4fdfe8c61..71ff7218e 100644 --- a/social_auth/backends/contrib/github.py +++ b/social_auth/backends/contrib/github.py @@ -1,114 +1,8 @@ -""" -GitHub OAuth support. - -This contribution adds support for GitHub OAuth service. The settings -GITHUB_APP_ID and GITHUB_API_SECRET must be defined with the values -given by GitHub application registration process. - -GITHUB_ORGANIZATION is an optional setting that will allow you to constrain -authentication to a given GitHub organization. - -Extended permissions are supported by defining GITHUB_EXTENDED_PERMISSIONS -setting, it must be a list of values to request. - -By default account id and token expiration time are stored in extra_data -field, check OAuthBackend class for details on how to extend it. -""" -from urllib import urlencode -from urllib2 import HTTPError - -from django.utils import simplejson from django.conf import settings -from social_auth.utils import dsa_urlopen -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.exceptions import AuthFailed - - -# GitHub configuration -GITHUB_AUTHORIZATION_URL = 'https://github.com/login/oauth/authorize' -GITHUB_ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token' -GITHUB_USER_DATA_URL = 'https://api.github.com/user' - -# GitHub organization configuration -GITHUB_ORGANIZATION_MEMBER_OF_URL = \ - 'https://api.github.com/orgs/{org}/members/{username}' - -GITHUB_SERVER = 'github.com' - - -class GithubBackend(OAuthBackend): - """Github OAuth authentication backend""" - name = 'github' - # Default extra data to store - EXTRA_DATA = [ - ('id', 'id'), - ('expires', 'expires') - ] - - def get_user_details(self, response): - """Return user details from Github account""" - name = response.get('name') or '' - details = {'username': response.get('login'), - 'email': response.get('email') or ''} - try: - # GitHub doesn't separate first and last names. Let's try. - first_name, last_name = name.split(' ', 1) - except ValueError: - details['first_name'] = name - else: - details['first_name'] = first_name - details['last_name'] = last_name - return details - - -class GithubAuth(BaseOAuth2): - """Github OAuth2 mechanism""" - AUTHORIZATION_URL = GITHUB_AUTHORIZATION_URL - ACCESS_TOKEN_URL = GITHUB_ACCESS_TOKEN_URL - AUTH_BACKEND = GithubBackend - SETTINGS_KEY_NAME = 'GITHUB_APP_ID' - SETTINGS_SECRET_NAME = 'GITHUB_API_SECRET' - SCOPE_SEPARATOR = ',' - # Look at http://developer.github.com/v3/oauth/ - SCOPE_VAR_NAME = 'GITHUB_EXTENDED_PERMISSIONS' - - GITHUB_ORGANIZATION = getattr(settings, 'GITHUB_ORGANIZATION', None) - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - url = GITHUB_USER_DATA_URL + '?' + urlencode({ - 'access_token': access_token - }) - - try: - data = simplejson.load(dsa_urlopen(url)) - except ValueError: - data = None - - # if we have a github organization defined, test that the current users - # is a member of that organization. - if data and self.GITHUB_ORGANIZATION: - member_url = GITHUB_ORGANIZATION_MEMBER_OF_URL.format( - org=self.GITHUB_ORGANIZATION, - username=data.get('login') - ) + '?' + urlencode({ - 'access_token': access_token - }) - - try: - response = dsa_urlopen(member_url) - except HTTPError: - data = None - else: - # if the user is a member of the organization, response code - # will be 204, see http://bit.ly/ZS6vFl - if response.code != 204: - raise AuthFailed('User doesn\'t belong to the ' - 'organization') - return data -# Backend definition -BACKENDS = { - 'github': GithubAuth, -} +if getattr(settings, 'GITHUB_ORGANIZATION', None): + from social.backends.github import \ + GithubOrganizationOAuth2 as GithubBackend +else: + from social.backends.github import GithubOAuth2 as GithubBackend diff --git a/social_auth/backends/contrib/instagram.py b/social_auth/backends/contrib/instagram.py index 0d052ed4c..8c8e26d65 100644 --- a/social_auth/backends/contrib/instagram.py +++ b/social_auth/backends/contrib/instagram.py @@ -1,66 +1 @@ -from urllib import urlencode - -from django.utils import simplejson - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import dsa_urlopen - - -INSTAGRAM_SERVER = 'instagram.com' -INSTAGRAM_AUTHORIZATION_URL = 'https://instagram.com/oauth/authorize' -INSTAGRAM_ACCESS_TOKEN_URL = 'https://instagram.com/oauth/access_token' -INSTAGRAM_CHECK_AUTH = 'https://api.instagram.com/v1/users/self' - - -class InstagramBackend(OAuthBackend): - name = 'instagram' - - @classmethod - def extra_data(cls, user, uid, response, details=None): - """Return access_token and extra defined names to store in - extra_data field""" - data = super(InstagramBackend, cls).extra_data(user, uid, response, - details) - try: - data['username'] = response['user']['username'] - except KeyError: - pass - return data - - def get_user_id(self, details, response): - return response['user']['id'] - - def get_user_details(self, response): - """Return user details from Instagram account""" - username = response['user']['username'] - fullname = response['user'].get('full_name', '') - email = response['user'].get('email', '') - return { - 'username': username, - 'first_name': fullname, - 'email': email - } - - -class InstagramAuth(BaseOAuth2): - """Instagram OAuth mechanism""" - AUTHORIZATION_URL = INSTAGRAM_AUTHORIZATION_URL - ACCESS_TOKEN_URL = INSTAGRAM_ACCESS_TOKEN_URL - AUTH_BACKEND = InstagramBackend - SETTINGS_KEY_NAME = 'INSTAGRAM_CLIENT_ID' - SETTINGS_SECRET_NAME = 'INSTAGRAM_CLIENT_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - params = {'access_token': access_token} - url = INSTAGRAM_CHECK_AUTH + '?' + urlencode(params) - try: - return simplejson.load(dsa_urlopen(url)) - except ValueError: - return None - - -# Backend definition -BACKENDS = { - 'instagram': InstagramAuth, -} +from social.backends.instagram import InstagramOAuth2 as InstagramBackend diff --git a/social_auth/backends/contrib/jawbone.py b/social_auth/backends/contrib/jawbone.py index 0dab84df3..5db514e39 100644 --- a/social_auth/backends/contrib/jawbone.py +++ b/social_auth/backends/contrib/jawbone.py @@ -1,76 +1 @@ -from urllib2 import Request, urlopen - -from django.utils import simplejson - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.exceptions import AuthCanceled, AuthUnknownError - - -# Jawbone configuration -JAWBONE_SERVER = 'https://jawbone.com/' -JAWBONE_AUTHORIZATION_URL = '%s/auth/oauth2/auth' % JAWBONE_SERVER -JAWBONE_ACCESS_TOKEN_URL = '%s/auth/oauth2/token' % JAWBONE_SERVER -JAWBONE_CHECK_AUTH = '%s/nudge/api/users/@me' % JAWBONE_SERVER - - -class JawboneBackend(OAuthBackend): - name = 'jawbone' - - def get_user_id(self, details, response): - return response['data']['xid'] - - def get_user_details(self, response): - """Return user details from Jawbone account""" - firstName = response['data'].get('first', '') - lastName = response['data'].get('last', '') - dob = response['data'].get('dob', '') - gender = response['data'].get('gender', '') - height = response['data'].get('height', '') - weight = response['data'].get('weight', '') - - return {'username': firstName + ' ' + lastName, - 'first_name': firstName, - 'last_name': lastName, - 'dob': dob, - 'gender': gender, - 'height': height, - 'weight': weight} - - -class JawboneAuth(BaseOAuth2): - """Jawbone OAuth mechanism""" - AUTHORIZATION_URL = JAWBONE_AUTHORIZATION_URL - ACCESS_TOKEN_URL = JAWBONE_ACCESS_TOKEN_URL - SERVER_URL = JAWBONE_SERVER - AUTH_BACKEND = JawboneBackend - SETTINGS_KEY_NAME = 'JAWBONE_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'JAWBONE_CONSUMER_SECRET' - SCOPE_SEPARATOR = ' ' - # Look at http://developer.github.com/v3/oauth/ - SCOPE_VAR_NAME = 'JAWBONE_EXTENDED_PERMISSIONS' - REDIRECT_STATE = False - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - url = JAWBONE_CHECK_AUTH - headers = {'Authorization': 'Bearer ' + access_token} - request = Request(url, headers=headers) - try: - return simplejson.load(urlopen(request)) - except ValueError: - return None - - def process_error(self, data): - error = self.request.GET.get('error', '') - if error: - if error == 'access_denied': - raise AuthCanceled(self) - else: - raise AuthUnknownError(self, 'Jawbone error was %s' % error) - return super(JawboneAuth, self).process_error(data) - - -# Backend definition -BACKENDS = { - 'jawbone': JawboneAuth, -} +from social.backends.jawbone import JawboneOAuth2 as JawboneAuth diff --git a/social_auth/backends/contrib/linkedin.py b/social_auth/backends/contrib/linkedin.py index f8724d26b..177a193b8 100644 --- a/social_auth/backends/contrib/linkedin.py +++ b/social_auth/backends/contrib/linkedin.py @@ -1,194 +1,2 @@ -from django.utils.translation import get_language -""" -Linkedin OAuth support - -No extra configurations are needed to make this work. -""" -from xml.etree import ElementTree -from xml.parsers.expat import ExpatError - -from urllib import urlencode -from urllib2 import Request -from oauth2 import Token - -from django.utils import simplejson - -from social_auth.utils import setting, dsa_urlopen -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend, BaseOAuth2 -from social_auth.exceptions import AuthCanceled, AuthUnknownError - - -LINKEDIN_SERVER = 'linkedin.com' -LINKEDIN_REQUEST_TOKEN_URL = 'https://api.%s/uas/oauth/requestToken' % \ - LINKEDIN_SERVER -LINKEDIN_ACCESS_TOKEN_URL = 'https://api.%s/uas/oauth/accessToken' % \ - LINKEDIN_SERVER -LINKEDIN_AUTHORIZATION_URL = 'https://www.%s/uas/oauth/authenticate' % \ - LINKEDIN_SERVER -LINKEDIN_CHECK_AUTH = 'https://api.%s/v1/people/~' % LINKEDIN_SERVER -# Check doc at http://developer.linkedin.com/docs/DOC-1014 about how to use -# fields selectors to retrieve extra user data -LINKEDIN_FIELD_SELECTORS = ['id', 'first-name', 'last-name'] - - -def add_language_header(request): - language = setting('LINKEDIN_FORCE_PROFILE_LANGUAGE', False) - if language is True: - request.add_header('Accept-Language', get_language()) - elif language: - request.add_header('Accept-Language', language) - - -class LinkedinBackend(OAuthBackend): - """Linkedin OAuth authentication backend""" - name = 'linkedin' - EXTRA_DATA = [('id', 'id'), - ('first-name', 'first_name'), - ('last-name', 'last_name')] - - def get_user_details(self, response): - """Return user details from Linkedin account""" - first_name, last_name = response['first-name'], response['last-name'] - email = response.get('email-address', '') - return {'username': first_name + last_name, - 'fullname': first_name + ' ' + last_name, - 'first_name': first_name, - 'last_name': last_name, - 'email': email} - - @classmethod - def tokens(cls, instance): - """ Return list of OAuth v1 tokens from Linkedin """ - token = super(LinkedinBackend, cls).tokens(instance) - if token and 'access_token' in token: - token = dict(tok.split('=') - for tok in token['access_token'].split('&')) - return token - - -class LinkedinOAuth2Backend(OAuthBackend): - """Linkedin OAuth2 authentication backend""" - name = 'linkedin-oauth2' - - EXTRA_DATA = [('id', 'id'), - ('firstName', 'first_name'), - ('lastName', 'last_name')] - - def get_user_details(self, response): - first_name, last_name = response['firstName'], response['lastName'] - return {'username': first_name + last_name, - 'fullname': first_name + ' ' + last_name, - 'first_name': first_name, - 'last_name': last_name, - 'email': response.get('emailAddress', '')} - - -class LinkedinAuth(ConsumerBasedOAuth): - """Linkedin OAuth authentication mechanism""" - AUTHORIZATION_URL = LINKEDIN_AUTHORIZATION_URL - REQUEST_TOKEN_URL = LINKEDIN_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = LINKEDIN_ACCESS_TOKEN_URL - AUTH_BACKEND = LinkedinBackend - SETTINGS_KEY_NAME = 'LINKEDIN_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'LINKEDIN_CONSUMER_SECRET' - SCOPE_VAR_NAME = 'LINKEDIN_SCOPE' - SCOPE_SEPARATOR = '+' - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - fields_selectors = LINKEDIN_FIELD_SELECTORS + \ - setting('LINKEDIN_EXTRA_FIELD_SELECTORS', []) - # use set() over fields_selectors since LinkedIn fails when values are - # duplicated - url = LINKEDIN_CHECK_AUTH + ':(%s)' % ','.join(set(fields_selectors)) - request = self.oauth_request(access_token, url) - add_language_header(request) - raw_xml = self.fetch_response(request) - try: - return to_dict(ElementTree.fromstring(raw_xml)) - except (ExpatError, KeyError, IndexError): - return None - - def auth_complete(self, *args, **kwargs): - """Complete auth process. Check LinkedIn error response.""" - oauth_problem = self.request.GET.get('oauth_problem') - if oauth_problem: - if oauth_problem == 'user_refused': - raise AuthCanceled(self, '') - else: - raise AuthUnknownError(self, 'LinkedIn error was %s' % - oauth_problem) - return super(LinkedinAuth, self).auth_complete(*args, **kwargs) - - def get_scope(self): - """Return list with needed access scope""" - scope = [] - if self.SCOPE_VAR_NAME: - scope = setting(self.SCOPE_VAR_NAME, []) - else: - scope = [] - return scope - - def unauthorized_token(self): - """Makes first request to oauth. Returns an unauthorized Token.""" - request_token_url = self.REQUEST_TOKEN_URL - scope = self.get_scope() - if scope: - qs = 'scope=' + self.SCOPE_SEPARATOR.join(scope) - request_token_url = request_token_url + '?' + qs - - request = self.oauth_request( - token=None, - url=request_token_url, - extra_params=self.request_token_extra_arguments() - ) - response = self.fetch_response(request) - return Token.from_string(response) - - -class LinkedinOAuth2(BaseOAuth2): - AUTH_BACKEND = LinkedinOAuth2Backend - AUTHORIZATION_URL = 'https://www.linkedin.com/uas/oauth2/authorization' - ACCESS_TOKEN_URL = 'https://www.linkedin.com/uas/oauth2/accessToken' - SETTINGS_KEY_NAME = 'LINKEDIN_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'LINKEDIN_CONSUMER_SECRET' - SCOPE_VAR_NAME = 'LINKEDIN_SCOPE' - REDIRECT_STATE = False - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - fields_selectors = LINKEDIN_FIELD_SELECTORS + \ - setting('LINKEDIN_EXTRA_FIELD_SELECTORS', []) - url = LINKEDIN_CHECK_AUTH + ':(%s)' % ','.join(set(fields_selectors)) - data = {'oauth2_access_token': access_token, 'format': 'json'} - request = Request(url + '?' + urlencode(data)) - add_language_header(request) - try: - return simplejson.loads(dsa_urlopen(request).read()) - except (ExpatError, KeyError, IndexError): - return None - - -def to_dict(xml): - """Convert XML structure to dict recursively, repeated keys entries - are returned as in list containers.""" - children = xml.getchildren() - if not children: - return xml.text - else: - out = {} - for node in xml.getchildren(): - if node.tag in out: - if not isinstance(out[node.tag], list): - out[node.tag] = [out[node.tag]] - out[node.tag].append(to_dict(node)) - else: - out[node.tag] = to_dict(node) - return out - - -# Backend definition -BACKENDS = { - 'linkedin': LinkedinAuth, - 'linkedin-oauth2': LinkedinOAuth2, -} +from social.backends.linkedin import LinkedinOAuth as LinkedinBackend, \ + LinkedinOAuth2 as LinkedinOAuth2Backend diff --git a/social_auth/backends/contrib/live.py b/social_auth/backends/contrib/live.py index f028d4f87..555f27845 100644 --- a/social_auth/backends/contrib/live.py +++ b/social_auth/backends/contrib/live.py @@ -1,88 +1 @@ -""" -MSN Live Connect oAuth 2.0 - -Settings: -LIVE_CLIENT_ID -LIVE_CLIENT_SECRET -LIVE_EXTENDED_PERMISSIONS (defaults are: wl.basic, wl.emails) - -References: -* oAuth http://msdn.microsoft.com/en-us/library/live/hh243649.aspx -* Scopes http://msdn.microsoft.com/en-us/library/live/hh243646.aspx -* REST http://msdn.microsoft.com/en-us/library/live/hh243648.aspx - -Throws: -AuthUnknownError - if user data retrieval fails -""" -from urllib import urlencode - -from django.utils import simplejson - -from social_auth.utils import dsa_urlopen -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.exceptions import AuthUnknownError - - -# Live Connect configuration -LIVE_AUTHORIZATION_URL = 'https://login.live.com/oauth20_authorize.srf' -LIVE_ACCESS_TOKEN_URL = 'https://login.live.com/oauth20_token.srf' -LIVE_USER_DATA_URL = 'https://apis.live.net/v5.0/me' -LIVE_SERVER = 'live.com' -LIVE_DEFAULT_PERMISSIONS = ['wl.basic', 'wl.emails'] - - -class LiveBackend(OAuthBackend): - name = 'live' - - EXTRA_DATA = [ - ('id', 'id'), - ('access_token', 'access_token'), - ('reset_token', 'reset_token'), - ('expires', 'expires'), - ('email', 'email'), - ('first_name', 'first_name'), - ('last_name', 'last_name'), - ] - - def get_user_id(self, details, response): - return response['id'] - - def get_user_details(self, response): - """Return user details from Live Connect account""" - try: - email = response['emails']['account'] - except KeyError: - email = '' - - return {'username': response.get('name'), - 'email': email, - 'first_name': response.get('first_name'), - 'last_name': response.get('last_name')} - - -class LiveAuth(BaseOAuth2): - AUTHORIZATION_URL = LIVE_AUTHORIZATION_URL - ACCESS_TOKEN_URL = LIVE_ACCESS_TOKEN_URL - AUTH_BACKEND = LiveBackend - SETTINGS_KEY_NAME = 'LIVE_CLIENT_ID' - SETTINGS_SECRET_NAME = 'LIVE_CLIENT_SECRET' - SCOPE_SEPARATOR = ',' - SCOPE_VAR_NAME = 'LIVE_EXTENDED_PERMISSIONS' - DEFAULT_SCOPE = LIVE_DEFAULT_PERMISSIONS - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - url = LIVE_USER_DATA_URL + '?' + urlencode({ - 'access_token': access_token - }) - try: - return simplejson.load(dsa_urlopen(url)) - except (ValueError, IOError): - raise AuthUnknownError('Error during profile retrieval, ' - 'please, try again later') - - -# Backend definition -BACKENDS = { - 'live': LiveAuth, -} +from social.backends.live import LiveOAuth2 as LiveBackend diff --git a/social_auth/backends/contrib/livejournal.py b/social_auth/backends/contrib/livejournal.py index c1bbf2932..87b4cef61 100644 --- a/social_auth/backends/contrib/livejournal.py +++ b/social_auth/backends/contrib/livejournal.py @@ -1,49 +1 @@ -""" -LiveJournal OpenID support. - -This contribution adds support for LiveJournal OpenID service in the form -username.livejournal.com. Username is retrieved from the identity url. -""" -import urlparse - -from social_auth.backends import OpenIDBackend, OpenIdAuth -from social_auth.exceptions import AuthMissingParameter - - -# LiveJournal conf -LIVEJOURNAL_URL = 'http://%s.livejournal.com' -LIVEJOURNAL_USER_FIELD = 'openid_lj_user' - - -class LiveJournalBackend(OpenIDBackend): - """LiveJournal OpenID authentication backend""" - name = 'livejournal' - - def get_user_details(self, response): - """Generate username from identity url""" - values = super(LiveJournalBackend, self).get_user_details(response) - values['username'] = values.get('username') or \ - urlparse.urlsplit(response.identity_url)\ - .netloc.split('.', 1)[0] - return values - - -class LiveJournalAuth(OpenIdAuth): - """LiveJournal OpenID authentication""" - AUTH_BACKEND = LiveJournalBackend - - def uses_redirect(self): - """LiveJournal uses redirect""" - return True - - def openid_url(self): - """Returns LiveJournal authentication URL""" - if not self.data.get(LIVEJOURNAL_USER_FIELD): - raise AuthMissingParameter(self, LIVEJOURNAL_USER_FIELD) - return LIVEJOURNAL_URL % self.data[LIVEJOURNAL_USER_FIELD] - - -# Backend definition -BACKENDS = { - 'livejournal': LiveJournalAuth, -} +from social.backends.livejournal import LiveJournalOpenId as LiveJournalBackend diff --git a/social_auth/backends/contrib/mailru.py b/social_auth/backends/contrib/mailru.py index 3634c007c..73a9b95e8 100644 --- a/social_auth/backends/contrib/mailru.py +++ b/social_auth/backends/contrib/mailru.py @@ -1,103 +1 @@ -""" -Mail.ru OAuth2 support - -Take a look to http://api.mail.ru/docs/guides/oauth/ - -You need to register OAuth site here: -http://api.mail.ru/sites/my/add - -Then update your settings values using registration information - -""" - -from django.conf import settings -from django.utils import simplejson - -from urllib import urlencode, unquote -from urllib2 import Request, HTTPError -from hashlib import md5 - -from social_auth.backends import OAuthBackend, BaseOAuth2 -from social_auth.exceptions import AuthCanceled -from social_auth.utils import setting, log, dsa_urlopen - -MAILRU_API_URL = 'http://www.appsmail.ru/platform/api' -MAILRU_OAUTH2_SCOPE = [''] - - -class MailruBackend(OAuthBackend): - """Mail.ru authentication backend""" - name = 'mailru-oauth2' - EXTRA_DATA = [('refresh_token', 'refresh_token'), - ('expires_in', 'expires')] - - def get_user_id(self, details, response): - """Return user unique id provided by Mail.ru""" - return response['uid'] - - def get_user_details(self, response): - """Return user details from Mail.ru request""" - values = { - 'username': unquote(response['nick']), - 'email': unquote(response['email']), - 'first_name': unquote(response['first_name']), - 'last_name': unquote(response['last_name']) - } - - if values['first_name'] and values['last_name']: - values['fullname'] = '%s %s' % (values['first_name'], - values['last_name']) - return values - - -class MailruOAuth2(BaseOAuth2): - """Mail.ru OAuth2 support""" - AUTH_BACKEND = MailruBackend - AUTHORIZATION_URL = 'https://connect.mail.ru/oauth/authorize' - ACCESS_TOKEN_URL = 'https://connect.mail.ru/oauth/token' - SETTINGS_KEY_NAME = 'MAILRU_OAUTH2_CLIENT_KEY' - SETTINGS_SECRET_NAME = 'MAILRU_OAUTH2_CLIENT_SECRET' - - def get_scope(self): - return setting('MAILRU_OAUTH2_EXTRA_SCOPE', []) - - def auth_complete(self, *args, **kwargs): - try: - return super(MailruOAuth2, self).auth_complete(*args, **kwargs) - except HTTPError: # Mail.ru returns HTTPError 400 if cancelled - raise AuthCanceled(self) - - def user_data(self, access_token, *args, **kwargs): - """Return user data from Mail.ru REST API""" - data = {'method': 'users.getInfo', 'session_key': access_token} - return mailru_api(data)[0] - - -def mailru_sig(data): - """ Calculates signature of request data """ - param_list = sorted(list(item + '=' + data[item] for item in data)) - return md5(''.join(param_list) + - settings.MAILRU_OAUTH2_CLIENT_SECRET).hexdigest() - - -def mailru_api(data): - """ Calls Mail.ru REST API method - http://api.mail.ru/docs/guides/restapi/ - """ - data.update({'app_id': settings.MAILRU_OAUTH2_CLIENT_KEY, 'secure': '1'}) - data['sig'] = mailru_sig(data) - - params = urlencode(data) - request = Request(MAILRU_API_URL, params) - try: - return simplejson.loads(dsa_urlopen(request).read()) - except (TypeError, KeyError, IOError, ValueError, IndexError): - log('error', 'Could not load data from Mail.ru.', - exc_info=True, extra=dict(data=params)) - return None - - -# Backend definition -BACKENDS = { - 'mailru-oauth2': MailruOAuth2 -} +from social.backends.mailru import MailruOAuth2 as MailruBackend diff --git a/social_auth/backends/contrib/mendeley.py b/social_auth/backends/contrib/mendeley.py index 498b666fc..2ec0e46f8 100644 --- a/social_auth/backends/contrib/mendeley.py +++ b/social_auth/backends/contrib/mendeley.py @@ -1,62 +1 @@ -""" -Mendeley OAuth support - -No extra configurations are needed to make this work. -""" -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend - -from django.utils import simplejson - -MENDELEY_SERVER = 'mendeley.com' -MENDELEY_REQUEST_TOKEN_URL = 'http://api.%s/oauth/request_token/' % \ - MENDELEY_SERVER -MENDELEY_ACCESS_TOKEN_URL = 'http://api.%s/oauth/access_token/' % \ - MENDELEY_SERVER -MENDELEY_AUTHORIZATION_URL = 'http://api.%s/oauth/authorize/' % \ - MENDELEY_SERVER -MENDELEY_CHECK_AUTH = 'http://api.%s/oapi/profiles/info/' % MENDELEY_SERVER - -MENDELEY_FIELD_SELECTORS = ['profile_id', 'name', 'bio'] - - -class MendeleyBackend(OAuthBackend): - name = 'mendeley' - EXTRA_DATA = [('profile_id', 'profile_id'), - ('name', 'name'), - ('bio', 'bio')] - - def get_user_id(self, details, response): - return response['main']['profile_id'] - - def get_user_details(self, response): - """Return user details from Mendeley account""" - profile_id = response['main']['profile_id'] - name = response['main']['name'] - bio = response['main']['bio'] - return {'profile_id': profile_id, - 'name': name, - 'bio': bio} - - -class MendeleyAuth(ConsumerBasedOAuth): - """Mendeley OAuth authentication mechanism""" - AUTHORIZATION_URL = MENDELEY_AUTHORIZATION_URL - REQUEST_TOKEN_URL = MENDELEY_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = MENDELEY_ACCESS_TOKEN_URL - AUTH_BACKEND = MendeleyBackend - SETTINGS_KEY_NAME = 'MENDELEY_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'MENDELEY_CONSUMER_SECRET' - SCOPE_VAR_NAME = 'MENDELEY_SCOPE' - SCOPE_SEPARATOR = '+' - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - url = MENDELEY_CHECK_AUTH + 'me/' - request = self.oauth_request(access_token, url) - data = simplejson.loads(self.fetch_response(request)) - data.update(data['main']) - return data - -BACKENDS = { - 'mendeley': MendeleyAuth, -} +from social.backends.mendeley import MendeleyOAuth as MendeleyAuth diff --git a/social_auth/backends/contrib/mixcloud.py b/social_auth/backends/contrib/mixcloud.py index 9acc3572f..a7d18967e 100644 --- a/social_auth/backends/contrib/mixcloud.py +++ b/social_auth/backends/contrib/mixcloud.py @@ -1,52 +1 @@ -""" -Mixcloud OAuth2 support -""" -from urllib import urlencode -from urllib2 import Request - -from django.utils import simplejson - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import dsa_urlopen - - -MIXCLOUD_PROFILE_URL = 'https://api.mixcloud.com/me/' - - -class MixcloudBackend(OAuthBackend): - name = 'mixcloud' - - def get_user_id(self, details, response): - return response['username'] - - def get_user_details(self, response): - return {'username': response['username'], - 'email': None, - 'fullname': response['name'], - 'first_name': None, - 'last_name': None} - - -class MixcloudOAuth2(BaseOAuth2): - AUTH_BACKEND = MixcloudBackend - AUTHORIZATION_URL = 'https://www.mixcloud.com/oauth/authorize' - ACCESS_TOKEN_URL = 'https://www.mixcloud.com/oauth/access_token' - SETTINGS_KEY_NAME = 'MIXCLOUD_CLIENT_ID' - SETTINGS_SECRET_NAME = 'MIXCLOUD_CLIENT_SECRET' - - def user_data(self, access_token, *args, **kwargs): - return mixcloud_profile(access_token) - - -def mixcloud_profile(access_token): - data = {'access_token': access_token, 'alt': 'json'} - request = Request(MIXCLOUD_PROFILE_URL + '?' + urlencode(data)) - try: - return simplejson.loads(dsa_urlopen(request).read()) - except (ValueError, KeyError, IOError): - return None - - -BACKENDS = { - 'mixcloud': MixcloudOAuth2, -} +from social.backends.mixcloud import MixcloudOAuth2 diff --git a/social_auth/backends/contrib/odnoklassniki.py b/social_auth/backends/contrib/odnoklassniki.py index 07ec9aaec..e4ea25002 100644 --- a/social_auth/backends/contrib/odnoklassniki.py +++ b/social_auth/backends/contrib/odnoklassniki.py @@ -1,270 +1,3 @@ -""" -Odnoklassniki.ru OAuth2 and IFRAME application support -If you are using OAuth2 authentication, - * Take a look to: - http://dev.odnoklassniki.ru/wiki/display/ok/The+OAuth+2.0+Protocol - * You need to register OAuth application here: - http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=13992188 -elif you're building iframe application, - * Take a look to: - http://dev.odnoklassniki.ru/wiki/display/ok/ - Odnoklassniki.ru+Third+Party+Platform - * You need to register your iframe application here: - http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=5668937 - * You need to sign a public offer and do some bureaucracy if you want to be - listed in application registry -Then setup your application according manual and use information from -registration mail to set settings values. -""" -from urllib import urlencode, unquote -from urllib2 import Request -from hashlib import md5 - -from django import forms -from django.contrib.auth import authenticate -from django.utils import simplejson - -from social_auth.backends import OAuthBackend, BaseOAuth2, BaseAuth, \ - SocialAuthBackend -from social_auth.exceptions import AuthFailed -from social_auth.utils import log, dsa_urlopen, backend_setting - - -ODNOKLASSNIKI_API_SERVER = 'http://api.odnoklassniki.ru/' - - -class OdnoklassnikiBackend(OAuthBackend): - '''Odnoklassniki authentication backend''' - name = 'odnoklassniki' - EXTRA_DATA = [('refresh_token', 'refresh_token'), - ('expires_in', 'expires')] - - def get_user_id(self, details, response): - '''Return user unique id provided by Odnoklassniki''' - return response['uid'] - - def get_user_details(self, response): - '''Return user details from Odnoklassniki request''' - return { - 'username': response['uid'], - 'email': '', - 'fullname': unquote(response['name']), - 'first_name': unquote(response['first_name']), - 'last_name': unquote(response['last_name']) - } - - -class OdnoklassnikiMixin(object): - def get_settings(self): - client_key = backend_setting(self, self.SETTINGS_KEY_NAME) - client_secret = backend_setting(self, self.SETTINGS_SECRET_NAME) - public_key = backend_setting(self, self.SETTINGS_PUBLIC_NAME) - return client_key, client_secret, public_key - - -class OdnoklassnikiOAuth2(BaseOAuth2, OdnoklassnikiMixin): - '''Odnoklassniki OAuth2 support''' - AUTH_BACKEND = OdnoklassnikiBackend - AUTHORIZATION_URL = 'http://www.odnoklassniki.ru/oauth/authorize' - ACCESS_TOKEN_URL = 'http://api.odnoklassniki.ru/oauth/token.do' - SETTINGS_KEY_NAME = 'ODNOKLASSNIKI_OAUTH2_CLIENT_KEY' - SETTINGS_SECRET_NAME = 'ODNOKLASSNIKI_OAUTH2_CLIENT_SECRET' - SETTINGS_PUBLIC_NAME = 'ODNOKLASSNIKI_OAUTH2_APP_KEY' - - def get_scope(self): - return backend_setting(self, 'ODNOKLASSNIKI_OAUTH2_EXTRA_SCOPE', []) - - def user_data(self, access_token, *args, **kwargs): - '''Return user data from Odnoklassniki REST API''' - data = {'access_token': access_token, 'method': 'users.getCurrentUser'} - client_key, client_secret, public_key = self.get_settings() - return odnoklassniki_api(data, ODNOKLASSNIKI_API_SERVER, public_key, - client_secret, 'oauth') - - -def odnoklassniki_oauth_sig(data, client_secret): - '''Calculates signature of request data access_token value must be included - Algorithm is described at - http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=12878032, - search for "little bit different way" - ''' - suffix = md5('{0:s}{1:s}'.format(data['access_token'], - client_secret)).hexdigest() - check_list = sorted(['{0:s}={1:s}'.format(key, value) - for key, value in data.items() - if key != 'access_token']) - return md5(''.join(check_list) + suffix).hexdigest() - - -def odnoklassniki_iframe_sig(data, client_secret_or_session_secret): - '''Calculates signature as described at: - http://dev.odnoklassniki.ru/wiki/display/ok/ - Authentication+and+Authorization - If API method requires session context, request is signed with session - secret key. Otherwise it is signed with application secret key - ''' - param_list = sorted(['{0:s}={1:s}'.format(key, value) - for key, value in data.items()]) - return md5(''.join(param_list) + - client_secret_or_session_secret).hexdigest() - - -def odnoklassniki_api(data, api_url, public_key, client_secret, - request_type='oauth'): - ''' Calls Odnoklassniki REST API method - http://dev.odnoklassniki.ru/wiki/display/ok/Odnoklassniki+Rest+API - ''' - data.update({ - 'application_key': public_key, - 'format': 'JSON' - }) - if request_type == 'oauth': - data['sig'] = odnoklassniki_oauth_sig(data, client_secret) - elif request_type == 'iframe_session': - data['sig'] = odnoklassniki_iframe_sig(data, - data['session_secret_key']) - elif request_type == 'iframe_nosession': - data['sig'] = odnoklassniki_iframe_sig(data, client_secret) - else: - msg = 'Unknown request type {0}. How should it be signed?' - raise AuthFailed(msg.format(request_type)) - params = urlencode(data) - request = Request('{0}fb.do?{1}'.format(api_url, params)) - try: - return simplejson.loads(dsa_urlopen(request).read()) - except (TypeError, KeyError, IOError, ValueError, IndexError): - log('error', 'Could not load data from Odnoklassniki.', - exc_info=True, extra=dict(data=params)) - return None - - -class OdnoklassnikiIframeForm(forms.Form): - logged_user_id = forms.IntegerField() - api_server = forms.CharField() - application_key = forms.CharField() - session_key = forms.CharField() - session_secret_key = forms.CharField() - authorized = forms.IntegerField() - apiconnection = forms.CharField() - refplace = forms.CharField(required=False) - referer = forms.CharField(required=False) - auth_sig = forms.CharField() - sig = forms.CharField() - custom_args = forms.CharField(required=False) - - def __init__(self, auth, *args, **kwargs): - self.auth = auth - super(OdnoklassnikiIframeForm, self).__init__(*args, **kwargs) - - def get_auth_sig(self): - secret_key = backend_setting(self.auth, 'ODNOKLASSNIKI_APP_SECRET') - hash_source = '{0:d}{1:s}{2:s}'.format( - self.cleaned_data['logged_user_id'], - self.cleaned_data['session_key'], - secret_key - ) - return md5(hash_source).hexdigest() - - def clean_auth_sig(self): - correct_key = self.get_auth_sig() - key = self.cleaned_data['auth_sig'].lower() - if correct_key != key: - raise forms.ValidationError('Wrong authorization key') - return self.cleaned_data['auth_sig'] - - def get_response(self): - fields = ('logged_user_id', - 'api_server', - 'application_key', - 'session_key', - 'session_secret_key', - 'authorized', - 'apiconnection', - ) - response = {} - for fieldname in self.fields.keys(): - if fieldname in fields: - response[fieldname] = self.cleaned_data[fieldname] - return response - - -class OdnoklassnikiAppBackend(SocialAuthBackend): - '''Odnoklassniki iframe app authentication backend''' - name = 'odnoklassnikiapp' - - def get_user_id(self, details, response): - '''Return unique user id provided by Odnoklassniki''' - return response['uid'] - - def extra_data(self, user, uid, response, details): - return dict([(key, value) for key, value in response.items() - if key in response['extra_data_list']]) - - def get_user_details(self, response): - return {'username': response['uid'], - 'email': '', - 'fullname': unquote(response['name']), - 'first_name': unquote(response['first_name']), - 'last_name': unquote(response['last_name'])} - - -class OdnoklassnikiApp(BaseAuth, OdnoklassnikiMixin): - '''Odnoklassniki iframe app authentication class''' - SETTINGS_KEY_NAME = 'ODNOKLASSNIKI_APP_KEY' - SETTINGS_SECRET_NAME = 'ODNOKLASSNIKI_APP_SECRET' - SETTINGS_PUBLIC_NAME = 'ODNOKLASSNIKI_APP_PUBLIC_KEY' - AUTH_BACKEND = OdnoklassnikiAppBackend - - def auth_complete(self, request, user, *args, **kwargs): - form = OdnoklassnikiIframeForm(auth=self, data=request.GET) - if not form.is_valid(): - raise AuthFailed('Cannot authorize: malformed parameters') - else: - response = form.get_response() - extra_user_data = backend_setting( - self, 'ODNOKLASSNIKI_APP_EXTRA_USER_DATA_LIST', ()) - base_fields = ('uid', 'first_name', 'last_name', 'name') - fields = base_fields + extra_user_data - data = { - 'method': 'users.getInfo', - 'uids': '{0}'.format(response['logged_user_id']), - 'fields': ','.join(fields), - } - client_key, client_secret, public_key = self.get_settings() - details = odnoklassniki_api(data, response['api_server'], - public_key, client_secret, - 'iframe_nosession') - if len(details) == 1 and 'uid' in details[0]: - details = details[0] - auth_data_fields = backend_setting( - self, - 'ODNOKLASSNIKI_APP_EXTRA_AUTH_DATA_LIST', - ('api_server', 'apiconnection', 'session_key', - 'session_secret_key', 'authorized') - ) - - for field in auth_data_fields: - details[field] = response[field] - details['extra_data_list'] = fields + auth_data_fields - kwargs.update({ - 'auth': self, - 'response': details, - self.AUTH_BACKEND.name: True - }) - else: - raise AuthFailed('Cannot get user details: API error') - return authenticate(*args, **kwargs) - - @property - def uses_redirect(self): - ''' - Odnoklassniki API for iframe application does not require redirects - ''' - return False - - -# Backend definition -BACKENDS = { - 'odnoklassniki': OdnoklassnikiOAuth2, - 'odnoklassnikiapp': OdnoklassnikiApp -} +from social.backends.odnoklassniki import \ + OdnoklassnikiOAuth2 as OdnoklassnikiBackend, \ + OdnoklassnikiApp as OdnoklassnikiAppBackend diff --git a/social_auth/backends/contrib/orkut.py b/social_auth/backends/contrib/orkut.py index 2c60737a0..0b65a2ccd 100644 --- a/social_auth/backends/contrib/orkut.py +++ b/social_auth/backends/contrib/orkut.py @@ -1,81 +1 @@ -""" -Orkut OAuth support. - -This contribution adds support for Orkut OAuth service. The scope is -limited to http://orkut.gmodules.com/social/ by default, but can be -extended with ORKUT_EXTRA_SCOPE on project settings. Also name, display -name and emails are the default requested user data, but extra values -can be specified by defining ORKUT_EXTRA_DATA setting. - -OAuth settings ORKUT_CONSUMER_KEY and ORKUT_CONSUMER_SECRET are needed -to enable this service support. -""" -from django.utils import simplejson - -from social_auth.utils import setting, dsa_urlopen -from social_auth.backends import OAuthBackend -from social_auth.backends.google import BaseGoogleOAuth - - -# Orkut configuration -# default scope, specify extra scope in settings as in: -# ORKUT_EXTRA_SCOPE = ['...'] -ORKUT_SCOPE = ['http://orkut.gmodules.com/social/'] -ORKUT_REST_ENDPOINT = 'http://www.orkut.com/social/rpc' -ORKUT_DEFAULT_DATA = 'name,displayName,emails' - - -class OrkutBackend(OAuthBackend): - """Orkut OAuth authentication backend""" - name = 'orkut' - - def get_user_details(self, response): - """Return user details from Orkut account""" - try: - emails = response['emails'][0]['value'] - except (KeyError, IndexError): - emails = '' - - return {'username': response['displayName'], - 'email': emails, - 'fullname': response['displayName'], - 'first_name': response['name']['givenName'], - 'last_name': response['name']['familyName']} - - -class OrkutAuth(BaseGoogleOAuth): - """Orkut OAuth authentication mechanism""" - AUTH_BACKEND = OrkutBackend - SETTINGS_KEY_NAME = 'ORKUT_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'ORKUT_CONSUMER_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from Orkut service""" - fields = ORKUT_DEFAULT_DATA - if setting('ORKUT_EXTRA_DATA'): - fields += ',' + setting('ORKUT_EXTRA_DATA') - scope = ORKUT_SCOPE + setting('ORKUT_EXTRA_SCOPE', []) - params = {'method': 'people.get', - 'id': 'myself', - 'userId': '@me', - 'groupId': '@self', - 'fields': fields, - 'scope': ' '.join(scope)} - request = self.oauth_request(access_token, ORKUT_REST_ENDPOINT, params) - response = dsa_urlopen(request.to_url()).read() - try: - return simplejson.loads(response)['data'] - except (ValueError, KeyError): - return None - - def oauth_request(self, token, url, extra_params=None): - extra_params = extra_params or {} - scope = ORKUT_SCOPE + setting('ORKUT_EXTRA_SCOPE', []) - extra_params['scope'] = ' '.join(scope) - return super(OrkutAuth, self).oauth_request(token, url, extra_params) - - -# Backend definition -BACKENDS = { - 'orkut': OrkutAuth, -} +from social.backends.orkut import OrkutOAuth as OrkutAuth diff --git a/social_auth/backends/contrib/rdio.py b/social_auth/backends/contrib/rdio.py index 2a1cc2859..d68b43c3d 100644 --- a/social_auth/backends/contrib/rdio.py +++ b/social_auth/backends/contrib/rdio.py @@ -1,121 +1 @@ -import urllib - -from oauth2 import Request as OAuthRequest, SignatureMethod_HMAC_SHA1 - -from django.utils import simplejson - -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend, BaseOAuth2 -from social_auth.utils import dsa_urlopen - - -class RdioBaseBackend(OAuthBackend): - def get_user_id(self, details, response): - return response['key'] - - def get_user_details(self, response): - return { - 'username': response['username'], - 'first_name': response['firstName'], - 'last_name': response['lastName'], - 'fullname': response['displayName'], - } - - -class RdioOAuth1Backend(RdioBaseBackend): - """Rdio OAuth authentication backend""" - name = 'rdio-oauth1' - EXTRA_DATA = [ - ('key', 'rdio_id'), - ('icon', 'rdio_icon_url'), - ('url', 'rdio_profile_url'), - ('username', 'rdio_username'), - ('streamRegion', 'rdio_stream_region'), - ] - - @classmethod - def tokens(cls, instance): - token = super(RdioOAuth1Backend, cls).tokens(instance) - if token and 'access_token' in token: - token = dict(tok.split('=') - for tok in token['access_token'].split('&')) - return token - - -class RdioOAuth2Backend(RdioBaseBackend): - name = 'rdio-oauth2' - EXTRA_DATA = [ - ('key', 'rdio_id'), - ('icon', 'rdio_icon_url'), - ('url', 'rdio_profile_url'), - ('username', 'rdio_username'), - ('streamRegion', 'rdio_stream_region'), - ('refresh_token', 'refresh_token', True), - ('token_type', 'token_type', True), - ] - - -class RdioOAuth1(ConsumerBasedOAuth): - AUTH_BACKEND = RdioOAuth1Backend - REQUEST_TOKEN_URL = 'http://api.rdio.com/oauth/request_token' - AUTHORIZATION_URL = 'https://www.rdio.com/oauth/authorize' - ACCESS_TOKEN_URL = 'http://api.rdio.com/oauth/access_token' - RDIO_API_BASE = 'http://api.rdio.com/1/' - SETTINGS_KEY_NAME = 'RDIO_OAUTH1_KEY' - SETTINGS_SECRET_NAME = 'RDIO_OAUTH1_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - params = { - 'method': 'currentUser', - 'extras': 'username,displayName,streamRegion', - } - request = self.oauth_post_request(access_token, self.RDIO_API_BASE, - params=params) - response = dsa_urlopen(request.url, request.to_postdata()) - json = '\n'.join(response.readlines()) - try: - return simplejson.loads(json)['result'] - except ValueError: - return None - - def oauth_post_request(self, token, url, params): - """Generate OAuth request, setups callback url""" - if 'oauth_verifier' in self.data: - params['oauth_verifier'] = self.data['oauth_verifier'] - request = OAuthRequest.from_consumer_and_token(self.consumer, - token=token, - http_url=url, - parameters=params, - http_method='POST') - request.sign_request(SignatureMethod_HMAC_SHA1(), self.consumer, token) - return request - - -class RdioOAuth2(BaseOAuth2): - AUTH_BACKEND = RdioOAuth2Backend - AUTHORIZATION_URL = 'https://www.rdio.com/oauth2/authorize' - ACCESS_TOKEN_URL = 'https://www.rdio.com/oauth2/token' - RDIO_API_BASE = 'https://www.rdio.com/api/1/' - SETTINGS_KEY_NAME = 'RDIO_OAUTH2_KEY' - SETTINGS_SECRET_NAME = 'RDIO_OAUTH2_SECRET' - SCOPE_VAR_NAME = 'RDIO2_PERMISSIONS' - EXTRA_PARAMS_VAR_NAME = 'RDIO2_EXTRA_PARAMS' - - def user_data(self, access_token, *args, **kwargs): - params = { - 'method': 'currentUser', - 'extras': 'username,displayName,streamRegion', - 'access_token': access_token, - } - response = dsa_urlopen(self.RDIO_API_BASE, urllib.urlencode(params)) - try: - return simplejson.load(response)['result'] - except ValueError: - return None - - -# Backend definition -BACKENDS = { - 'rdio-oauth1': RdioOAuth1, - 'rdio-oauth2': RdioOAuth2 -} +from social.backends.rdio import RdioOAuth1, RdioOAuth2 diff --git a/social_auth/backends/contrib/readability.py b/social_auth/backends/contrib/readability.py index aef5520f7..7d70847ed 100644 --- a/social_auth/backends/contrib/readability.py +++ b/social_auth/backends/contrib/readability.py @@ -1,100 +1 @@ -""" -Readability OAuth support. - -This contribution adds support for Readability OAuth service. The settings -READABILITY_CONSUMER_KEY and READABILITY_CONSUMER_SECRET must be defined with -the values given by Readability in the Connections page of your account -settings.""" - -from django.utils import simplejson - -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend -from social_auth.exceptions import AuthCanceled -from social_auth.utils import setting - -# Readability configuration -READABILITY_SERVER = 'www.readability.com' -READABILITY_API = 'https://%s/api/rest/v1' % READABILITY_SERVER -READABILITY_AUTHORIZATION_URL = '%s/oauth/authorize/' % READABILITY_API -READABILITY_ACCESS_TOKEN_URL = '%s/oauth/access_token/' % READABILITY_API -READABILITY_REQUEST_TOKEN_URL = '%s/oauth/request_token/' % READABILITY_API -READABILITY_USER_DATA_URL = '%s/users/_current' % READABILITY_API - - -class ReadabilityBackend(OAuthBackend): - """Readability OAuth authentication backend""" - name = 'readability' - - EXTRA_DATA = [('date_joined', 'date_joined'), - ('kindle_email_address', 'kindle_email_address'), - ('avatar_url', 'avatar_url'), - ('email_into_address', 'email_into_address')] - - def get_user_details(self, response): - username = response['username'] - first_name, last_name = response['first_name'], response['last_name'] - return {'username': username, - 'first_name': first_name, - 'last_name': last_name} - - def get_user_id(self, details, response): - """Returns a unique username to use""" - return response['username'] - - @classmethod - def tokens(cls, instance): - """Return the tokens needed to authenticate the access to any API the - service might provide. Readability uses a pair of OAuthToken consisting - of an oauth_token and oauth_token_secret. - - instance must be a UserSocialAuth instance. - """ - token = super(ReadabilityBackend, cls).tokens(instance) - if token and 'access_token' in token: - # Split the OAuth query string and only return the values needed - token = dict( - filter( - lambda x: x[0] in ['oauth_token', 'oauth_token_secret'], - map( - lambda x: x.split('='), - token['access_token'].split('&')))) - - return token - - -class ReadabilityAuth(ConsumerBasedOAuth): - """Readability OAuth authentication mechanism""" - AUTHORIZATION_URL = READABILITY_AUTHORIZATION_URL - REQUEST_TOKEN_URL = READABILITY_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = READABILITY_ACCESS_TOKEN_URL - SERVER_URL = READABILITY_SERVER - AUTH_BACKEND = ReadabilityBackend - SETTINGS_KEY_NAME = 'READABILITY_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'READABILITY_CONSUMER_SECRET' - - def user_data(self, access_token, *args, **kwargs): - url = READABILITY_USER_DATA_URL - request = self.oauth_request(access_token, url) - json = self.fetch_response(request) - try: - return simplejson.loads(json) - except ValueError: - return None - - def auth_complete(self, *args, **kwargs): - """Completes login process, must return user instance""" - if 'error' in self.data: - raise AuthCanceled(self) - else: - return super(ReadabilityAuth, self).auth_complete(*args, **kwargs) - - @classmethod - def enabled(cls): - """Return backend enabled status by checking basic settings""" - - return setting('READABILITY_CONSUMER_KEY') \ - and setting('READABILITY_CONSUMER_SECRET') - -BACKENDS = { - 'readability': ReadabilityAuth, -} +from social.backends.readability import ReadabilityOAuth as ReadabilityBackend diff --git a/social_auth/backends/contrib/shopify.py b/social_auth/backends/contrib/shopify.py index 454566565..ce35711c8 100644 --- a/social_auth/backends/contrib/shopify.py +++ b/social_auth/backends/contrib/shopify.py @@ -1,124 +1 @@ -""" -Shopify OAuth support. - -You must: - -- Register an App in the shopify partner control panel -- Add the API Key and shared secret in your django settings -- Set the Application URL in shopify app settings -- Install the shopify package - -""" -import imp -from urllib2 import HTTPError - -from django.contrib.auth import authenticate - -from social_auth.utils import setting -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.exceptions import AuthFailed, AuthCanceled - - -class ShopifyBackend(OAuthBackend): - """Shopify OAuth2 authentication backend""" - name = 'shopify' - # Default extra data to store - EXTRA_DATA = [ - ('shop', 'shop'), - ('website', 'website'), - ('expires', 'expires') - ] - - def get_user_details(self, response): - """Use the shopify store name as the username""" - return { - 'username': unicode(response.get('shop', '') - .replace('.myshopify.com', '')) - } - - def get_user_id(self, details, response): - """OAuth providers return an unique user id in response""" - # For shopify, we'll use the shop ID - return response['shop'] - - -class ShopifyAuth(BaseOAuth2): - """Shopify OAuth authentication mechanism""" - AUTH_BACKEND = ShopifyBackend - SETTINGS_KEY_NAME = 'SHOPIFY_APP_API_KEY' - SETTINGS_SECRET_NAME = 'SHOPIFY_SHARED_SECRET' - # Look at http://api.shopify.com/authentication.html#scopes - SCOPE_VAR_NAME = 'SHOPIFY_SCOPE' - - def __init__(self, request, redirect): - super(ShopifyAuth, self).__init__(request, redirect) - fp, pathname, description = imp.find_module('shopify') - self.shopifyAPI = imp.load_module('shopify', fp, pathname, description) - - def auth_url(self): - self.shopifyAPI.Session.setup(api_key=setting('SHOPIFY_APP_API_KEY'), - secret=setting('SHOPIFY_SHARED_SECRET')) - scope = self.get_scope() - state = self.state_token() - self.request.session[self.AUTH_BACKEND.name + '_state'] = state - - redirect_uri = self.get_redirect_uri(state) - permission_url = self.shopifyAPI.Session.create_permission_url( - self.request.GET.get('shop').strip(), - scope=scope, redirect_uri=redirect_uri - ) - return permission_url - - def auth_complete(self, *args, **kwargs): - """Completes login process, must return user instance""" - access_token = None - if self.data.get('error'): - error = self.data.get('error_description') or self.data['error'] - raise AuthFailed(self, error) - - client_id, client_secret = self.get_key_and_secret() - try: - shop_url = self.request.GET.get('shop') - self.shopifyAPI.Session.setup( - api_key=setting('SHOPIFY_APP_API_KEY'), - secret=setting('SHOPIFY_SHARED_SECRET') - ) - shopify_session = self.shopifyAPI.Session(shop_url, - self.request.REQUEST) - access_token = shopify_session.token - except self.shopifyAPI.ValidationException, e: - raise AuthCanceled(self) - except HTTPError, e: - if e.code == 400: - raise AuthCanceled(self) - else: - raise - - if not access_token: - raise AuthFailed(self, 'Authentication Failed') - return self.do_auth(access_token, shop_url, shopify_session.url, - *args, **kwargs) - - def do_auth(self, access_token, shop_url, website, *args, **kwargs): - kwargs.update({ - 'auth': self, - 'response': { - 'shop': shop_url, - 'website': 'http://%s' % website, - 'access_token': access_token - }, - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - - @classmethod - def enabled(cls): - """Return backend enabled status by checking basic settings""" - return setting('SHOPIFY_APP_API_KEY') and \ - setting('SHOPIFY_SHARED_SECRET') - - -# Backend definition -BACKENDS = { - 'shopify': ShopifyAuth, -} +from social.backends.shopify import ShopifyOAuth2 as ShopifyBackend diff --git a/social_auth/backends/contrib/skyrock.py b/social_auth/backends/contrib/skyrock.py index 531e57f79..373b50723 100644 --- a/social_auth/backends/contrib/skyrock.py +++ b/social_auth/backends/contrib/skyrock.py @@ -1,74 +1 @@ -""" -Skyrock OAuth support. - -This adds support for Skyrock OAuth service. An application must -be registered first on skyrock and the settings SKYROCK_CONSUMER_KEY -and SKYROCK_CONSUMER_SECRET must be defined with they corresponding -values. - -By default account id is stored in extra_data field, check OAuthBackend -class for details on how to extend it. -""" -from django.utils import simplejson - -from social_auth.exceptions import AuthCanceled -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend - - -# Skyrock configuration -SKYROCK_SERVER = 'api.skyrock.com' -SKYROCK_REQUEST_TOKEN_URL = 'https://%s/v2/oauth/initiate' % SKYROCK_SERVER -SKYROCK_ACCESS_TOKEN_URL = 'https://%s/v2/oauth/token' % SKYROCK_SERVER -# Note: oauth/authorize forces the user to authorize every time. -# oauth/authenticate uses their previous selection, barring revocation. -SKYROCK_AUTHORIZATION_URL = 'http://%s/v2/oauth/authenticate' % SKYROCK_SERVER -SKYROCK_CHECK_AUTH = 'https://%s/v2/user/get.json' % SKYROCK_SERVER - - -class SkyrockBackend(OAuthBackend): - """Skyrock OAuth authentication backend""" - name = 'skyrock' - EXTRA_DATA = [('id', 'id')] - - def get_user_id(self, details, response): - return response['id_user'] - - def get_user_details(self, response): - """Return user details from Skyrock account""" - return {'username': response['username'], - 'email': response['email'], - 'fullname': response['firstname'] + ' ' + response['name'], - 'first_name': response['firstname'], - 'last_name': response['name']} - - -class SkyrockAuth(ConsumerBasedOAuth): - """Skyrock OAuth authentication mechanism""" - AUTHORIZATION_URL = SKYROCK_AUTHORIZATION_URL - REQUEST_TOKEN_URL = SKYROCK_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = SKYROCK_ACCESS_TOKEN_URL - AUTH_BACKEND = SkyrockBackend - SETTINGS_KEY_NAME = 'SKYROCK_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'SKYROCK_CONSUMER_SECRET' - - def user_data(self, access_token): - """Return user data provided""" - request = self.oauth_request(access_token, SKYROCK_CHECK_AUTH) - json = self.fetch_response(request) - try: - return simplejson.loads(json) - except ValueError: - return None - - def auth_complete(self, *args, **kwargs): - """Completes loging process, must return user instance""" - if 'denied' in self.data: - raise AuthCanceled(self) - else: - return super(SkyrockAuth, self).auth_complete(*args, **kwargs) - - -# Backend definition -BACKENDS = { - 'skyrock': SkyrockAuth, -} +from social.backends.skyrock import SkyrockOAuth as SkyrockBackend diff --git a/social_auth/backends/contrib/soundcloud.py b/social_auth/backends/contrib/soundcloud.py index c507d3b11..2bd333114 100644 --- a/social_auth/backends/contrib/soundcloud.py +++ b/social_auth/backends/contrib/soundcloud.py @@ -1,110 +1 @@ -""" -SoundCloud OAuth2 support. - -This contribution adds support for SoundCloud OAuth2 service. - -The settings SOUNDCLOUD_CLIENT_ID & SOUNDCLOUD_CLIENT_SECRET must be defined -with the values given by SoundCloud application registration process. - -http://developers.soundcloud.com/ -http://developers.soundcloud.com/docs - -By default account id and token expiration time are stored in extra_data -field, check OAuthBackend class for details on how to extend it. -""" -from urllib import urlencode - -from django.utils import simplejson - -from social_auth.utils import dsa_urlopen -from social_auth.backends import BaseOAuth2, OAuthBackend - - -# SoundCloud configuration -SOUNDCLOUD_AUTHORIZATION_URL = 'https://soundcloud.com/connect' -SOUNDCLOUD_ACCESS_TOKEN_URL = 'https://api.soundcloud.com/oauth2/token' -SOUNDCLOUD_USER_DATA_URL = 'https://api.soundcloud.com/me.json' -SOUNDCLOUD_SERVER = 'soundcloud.com' -EXTRA_DATA = [ - ('refresh_token', 'refresh_token'), - ('expires_in', 'expires') -] - - -class SoundcloudBackend(OAuthBackend): - """Soundcloud OAuth authentication backend""" - name = 'soundcloud' - # Default extra data to store - EXTRA_DATA = [ - ('id', 'id'), - ('expires', 'expires') - ] - - def get_user_details(self, response): - """Return user details from Soundcloud account""" - fullname = response.get('full_name') - full_name = fullname.split(' ') - first_name = full_name[0] - if len(full_name) > 1: - last_name = full_name[-1] - else: - last_name = '' - - return {'username': response.get('username'), - 'email': response.get('email') or '', - 'fullname': fullname, - 'first_name': first_name, - 'last_name': last_name} - - -class SoundcloudAuth(BaseOAuth2): - """Soundcloud OAuth2 mechanism""" - AUTHORIZATION_URL = SOUNDCLOUD_AUTHORIZATION_URL - ACCESS_TOKEN_URL = SOUNDCLOUD_ACCESS_TOKEN_URL - AUTH_BACKEND = SoundcloudBackend - SETTINGS_KEY_NAME = 'SOUNDCLOUD_CLIENT_ID' - SETTINGS_SECRET_NAME = 'SOUNDCLOUD_CLIENT_SECRET' - SCOPE_SEPARATOR = ',' - REDIRECT_STATE = False - #SCOPE_VAR_NAME = 'SOUNDCLOUD_EXTENDED_PERMISSIONS' - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - url = SOUNDCLOUD_USER_DATA_URL + '?' + urlencode({ - 'oauth_token': access_token - }) - try: - value = simplejson.load(dsa_urlopen(url)) - return value - except ValueError: - return None - - def auth_url(self): - """Return redirect url""" - if self.STATE_PARAMETER or self.REDIRECT_STATE: - # Store state in session for further request validation. The state - # value is passed as state parameter (as specified in OAuth2 spec), - # but also added to redirect_uri, that way we can still verify the - # request if the provider doesn't implement the state parameter. - # Reuse token if any. - name = self.AUTH_BACKEND.name + '_state' - state = self.request.session.get(name) or self.state_token() - self.request.session[self.AUTH_BACKEND.name + '_state'] = state - else: - state = None - - params = self.auth_params(state) - params.update(self.get_scope_argument()) - params.update(self.auth_extra_arguments()) - - if self.request.META.get('QUERY_STRING'): - query_string = '&' + self.request.META['QUERY_STRING'] - else: - query_string = '' - return self.AUTHORIZATION_URL + '?' + urlencode(params) + query_string - - -# Backend definition -BACKENDS = { - 'soundcloud': SoundcloudAuth -} +from social.backends.soundcloud import SoundcloudOAuth2 as SoundcloudAuth diff --git a/social_auth/backends/contrib/stackoverflow.py b/social_auth/backends/contrib/stackoverflow.py index 71d158962..58f425f18 100644 --- a/social_auth/backends/contrib/stackoverflow.py +++ b/social_auth/backends/contrib/stackoverflow.py @@ -1,110 +1 @@ -""" -Stackoverflow OAuth support. - -This contribution adds support for Stackoverflow OAuth service. The settings -STACKOVERFLOW_CLIENT_ID, STACKOVERFLOW_CLIENT_SECRET and -STACKOVERFLOW_CLIENT_SECRET must be defined with the values given by -Stackoverflow application registration process. - -Extended permissions are supported by defining -STACKOVERFLOW_EXTENDED_PERMISSIONS setting, it must be a list of values -to request. - -By default account id and token expiration time are stored in extra_data -field, check OAuthBackend class for details on how to extend it. -""" -from urllib import urlencode -from urllib2 import Request, HTTPError -from urlparse import parse_qsl -from gzip import GzipFile -from StringIO import StringIO - -from django.utils import simplejson -from django.conf import settings - -from social_auth.utils import dsa_urlopen -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.exceptions import AuthUnknownError, AuthCanceled - - -# Stackoverflow configuration -STACKOVERFLOW_AUTHORIZATION_URL = 'https://stackexchange.com/oauth' -STACKOVERFLOW_ACCESS_TOKEN_URL = 'https://stackexchange.com/oauth/access_token' -STACKOVERFLOW_USER_DATA_URL = 'https://api.stackexchange.com/2.1/me' - -STACKOVERFLOW_SERVER = 'stackexchange.com' - - -class StackoverflowBackend(OAuthBackend): - """Stackoverflow OAuth authentication backend""" - name = 'stackoverflow' - ID_KEY = 'user_id' - # Default extra data to store - EXTRA_DATA = [ - ('id', 'id'), - ('expires', 'expires') - ] - - def get_user_details(self, response): - """Return user details from Stackoverflow account""" - return {'username': response.get('link').split('/')[-1], - 'full_name': response.get('display_name')} - - -class StackoverflowAuth(BaseOAuth2): - """Stackoverflow OAuth2 mechanism""" - AUTHORIZATION_URL = STACKOVERFLOW_AUTHORIZATION_URL - ACCESS_TOKEN_URL = STACKOVERFLOW_ACCESS_TOKEN_URL - AUTH_BACKEND = StackoverflowBackend - SETTINGS_KEY_NAME = 'STACKOVERFLOW_CLIENT_ID' - SETTINGS_SECRET_NAME = 'STACKOVERFLOW_CLIENT_SECRET' - SCOPE_SEPARATOR = ',' - # See: https://api.stackexchange.com/docs/authentication#scope - SCOPE_VAR_NAME = 'STACKOVERFLOW_EXTENDED_PERMISSIONS' - - def auth_complete(self, *args, **kwargs): - """Completes loging process, must return user instance""" - self.process_error(self.data) - params = self.auth_complete_params(self.validate_state()) - request = Request(self.ACCESS_TOKEN_URL, data=urlencode(params), - headers=self.auth_headers()) - - try: - response = dict(parse_qsl(dsa_urlopen(request).read())) - except HTTPError, e: - if e.code == 400: - raise AuthCanceled(self) - else: - raise - except (ValueError, KeyError): - raise AuthUnknownError(self) - - self.process_error(response) - return self.do_auth(response['access_token'], response=response, - *args, **kwargs) - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - url = STACKOVERFLOW_USER_DATA_URL + '?' + urlencode({ - 'site': 'stackoverflow', - 'access_token': access_token, - 'key': getattr(settings, 'STACKOVERFLOW_KEY')}) - - opener = dsa_urlopen(url) - if opener.headers.get('content-encoding') == 'gzip': - ''' stackoverflow doesn't respect no gzip header ''' - gzip = GzipFile(fileobj=StringIO(opener.read()), mode='r') - response = gzip.read() - else: - response = opener.read() - - try: - data = simplejson.loads(response) - return data.get('items')[0] - except (ValueError, TypeError): - return None - -# Backend definition -BACKENDS = { - 'stackoverflow': StackoverflowAuth, -} +from social.backends.stackoverflow import StackoverflowOAuth2 as StackoverflowAuth diff --git a/social_auth/backends/contrib/stocktwits.py b/social_auth/backends/contrib/stocktwits.py index 8a2869057..287eee2ad 100644 --- a/social_auth/backends/contrib/stocktwits.py +++ b/social_auth/backends/contrib/stocktwits.py @@ -1,61 +1 @@ -from urllib import urlencode -from django.utils import simplejson - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import dsa_urlopen - -STOCKTWITS_SERVER = 'api.stocktwits.com' -STOCKTWITS_AUTHORIZATION_URL = 'https://%s/api/2/oauth/authorize' % \ - STOCKTWITS_SERVER -STOCKTWITS_ACCESS_TOKEN_URL = 'https://%s/api/2/oauth/token' % \ - STOCKTWITS_SERVER -STOCKTWITS_CHECK_AUTH = 'https://%s/api/2/account/verify.json' % \ - STOCKTWITS_SERVER - - -class StocktwitsBackend(OAuthBackend): - name = 'stocktwits' - - def get_user_id(self, details, response): - return response['user']['id'] - - def get_user_details(self, response): - """Return user details from Stocktwits account""" - try: - first_name, last_name = response['user']['name'].split(' ', 1) - except: - first_name = response['user']['name'] - last_name = '' - return {'username': response['user']['username'], - 'email': '', # not supplied - 'fullname': response['user']['name'], - 'first_name': first_name, - 'last_name': last_name} - - -class StocktwitsAuth(BaseOAuth2): - """Stocktwits OAuth mechanism""" - AUTHORIZATION_URL = STOCKTWITS_AUTHORIZATION_URL - ACCESS_TOKEN_URL = STOCKTWITS_ACCESS_TOKEN_URL - SERVER_URL = STOCKTWITS_SERVER - AUTH_BACKEND = StocktwitsBackend - SETTINGS_KEY_NAME = 'STOCKTWITS_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'STOCKTWITS_CONSUMER_SECRET' - SCOPE_SEPARATOR = ',' - DEFAULT_SCOPE = ['read', 'publish_messages', 'publish_watch_lists', - 'follow_users', 'follow_stocks'] - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - params = {'access_token': access_token} - url = STOCKTWITS_CHECK_AUTH + '?' + urlencode(params) - try: - return simplejson.load(dsa_urlopen(url)) - except ValueError: - return None - - -# Backend definition -BACKENDS = { - 'stocktwits': StocktwitsAuth, -} +from social.backends.stocktwits import StocktwitsOAuth2 as StocktwitsBackend diff --git a/social_auth/backends/contrib/trello.py b/social_auth/backends/contrib/trello.py index 5a5e40f97..5913a0ad5 100644 --- a/social_auth/backends/contrib/trello.py +++ b/social_auth/backends/contrib/trello.py @@ -1,100 +1 @@ -""" -Obtain -TRELLO_CONSUMER_KEY & TRELLO_CONSUMER_SECRET -at https://trello.com/1/appKey/generate -and put into settings.py - -Also you can put something like -TRELLO_AUTH_EXTRA_ARGUMENTS = { - 'name': '7WebPages Time Tracker', - 'expiration': 'never' -} - -into settings.py -""" - -from django.utils import simplejson - -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend -from social_auth.utils import dsa_urlopen, backend_setting -from urllib import urlencode - - -TRELLO_REQUEST_TOKEN_URL = 'https://trello.com/1/OAuthGetRequestToken' -TRELLO_ACCESS_TOKEN_URL = 'https://trello.com/1/OAuthGetAccessToken' -TRELLO_AUTHORIZATION_URL = 'https://trello.com/1/OAuthAuthorizeToken' -TRELLO_USER_DETAILS_URL = 'https://api.trello.com/1/members/me/' - - -class TrelloBackend(OAuthBackend): - """Trello OAuth authentication backend""" - name = 'trello' - EXTRA_DATA = [ - ('username', 'username'), - ('email', 'email'), - ('fullName', 'full_name'), - ] - - def get_user_details(self, response): - """Return user details from Trello account""" - name_arr = response.get('fullName').split() - first_name = None - last_name = None - - if len(name_arr) > 0: - first_name = name_arr[0] - if len(name_arr) > 1: - last_name = name_arr[1] - - return {'username': response.get('username'), - 'email': response.get('email'), - 'first_name': first_name, - 'last_name': last_name} - - def get_user_id(self, details, response): - """Return the user id, Trello only provides username as a unique - identifier""" - return response['username'] - - @classmethod - def tokens(cls, instance): - """Return the tokens needed to authenticate the access to any API the - service might provide. Trello uses a pair of OAuthToken consisting - on a oauth_token and oauth_token_secret. - - instance must be a UserSocialAuth instance. - """ - token = super(TrelloBackend, cls).tokens(instance) - if token and 'access_token' in token: - token = dict(tok.split('=') - for tok in token['access_token'].split('&')) - return token - - -class TrelloAuth(ConsumerBasedOAuth): - """Trello OAuth authentication mechanism""" - AUTHORIZATION_URL = TRELLO_AUTHORIZATION_URL - REQUEST_TOKEN_URL = TRELLO_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = TRELLO_ACCESS_TOKEN_URL - AUTH_BACKEND = TrelloBackend - SETTINGS_KEY_NAME = 'TRELLO_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'TRELLO_CONSUMER_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - token = access_token.key - params = { - 'token': token, - 'key': backend_setting(self, self.SETTINGS_KEY_NAME) - } - url = TRELLO_USER_DETAILS_URL + '?' + urlencode(params) - try: - return simplejson.load(dsa_urlopen(url)) - except ValueError: - return None - - -# Backend definition -BACKENDS = { - 'trello': TrelloAuth, -} +from social.backends.trello import TrelloOAuth as TrelloAuth diff --git a/social_auth/backends/contrib/tripit.py b/social_auth/backends/contrib/tripit.py index 48cb79abe..162a9fd43 100644 --- a/social_auth/backends/contrib/tripit.py +++ b/social_auth/backends/contrib/tripit.py @@ -1,70 +1 @@ -""" -TripIt OAuth support. - -This adds support for TripIt OAuth service. An application must -be registered first on TripIt and the settings TRIPIT_API_KEY -and TRIPIT_API_SECRET must be defined with the corresponding -values. - -User screen name is used to generate username. -""" -from xml.dom import minidom - -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend - - -TRIPIT_CHECK_AUTH = 'https://api.tripit.com/v1/get/profile' - - -class TripItBackend(OAuthBackend): - """TripIt OAuth authentication backend""" - name = 'tripit' - EXTRA_DATA = [('screen_name', 'screen_name')] - - def get_user_details(self, response): - """Return user details from TripIt account""" - try: - first_name, last_name = response['name'].split(' ', 1) - except ValueError: - first_name = response['name'] - last_name = '' - return {'username': response['screen_name'], - 'email': response['email'], - 'fullname': response['name'], - 'first_name': first_name, - 'last_name': last_name} - - -class TripItAuth(ConsumerBasedOAuth): - """TripIt OAuth authentication mechanism""" - AUTHORIZATION_URL = 'https://www.tripit.com/oauth/authorize' - REQUEST_TOKEN_URL = 'https://api.tripit.com/oauth/request_token' - ACCESS_TOKEN_URL = 'https://api.tripit.com/oauth/access_token' - AUTH_BACKEND = TripItBackend - SETTINGS_KEY_NAME = 'TRIPIT_API_KEY' - SETTINGS_SECRET_NAME = 'TRIPIT_API_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - request = self.oauth_request(access_token, TRIPIT_CHECK_AUTH) - content = self.fetch_response(request) - try: - dom = minidom.parseString(content) - except ValueError: - return None - - return { - 'id': dom.getElementsByTagName('Profile')[0].getAttribute('ref'), - 'name': dom.getElementsByTagName( - 'public_display_name')[0].childNodes[0].data, - 'screen_name': dom.getElementsByTagName( - 'screen_name')[0].childNodes[0].data, - 'email': dom.getElementsByTagName('is_primary')[0] - .parentNode.getElementsByTagName('address')[0] - .childNodes[0].data, - } - -# Backend definition -BACKENDS = { - 'tripit': TripItAuth, -} +from social.backends.tripit import TripItOAuth as TripItAuth diff --git a/social_auth/backends/contrib/tumblr.py b/social_auth/backends/contrib/tumblr.py index 42b2018c3..f68282674 100644 --- a/social_auth/backends/contrib/tumblr.py +++ b/social_auth/backends/contrib/tumblr.py @@ -1,115 +1 @@ -""" -Tumblr OAuth 1.0a support. - -Take a look to http://www.tumblr.com/docs/en/api/v2 - -You need to register OAuth site here: -http://www.tumblr.com/oauth/apps - -Then update your settings values using registration information - -ref: -https://github.com/gkmngrgn/django-tumblr-auth -""" -from urllib import urlopen - -from oauth2 import Request as OAuthRequest, Token as OAuthToken, \ - SignatureMethod_HMAC_SHA1 - -from django.utils import simplejson - -from social_auth.backends import ConsumerBasedOAuth -from social_auth.backends import OAuthBackend - - -TUMBLR_SERVER = 'www.tumblr.com' -TUMBLR_AUTHORIZATION_URL = 'http://%s/oauth/authorize' % TUMBLR_SERVER -TUMBLR_REQUEST_TOKEN_URL = 'http://%s/oauth/request_token' % TUMBLR_SERVER -TUMBLR_ACCESS_TOKEN_URL = 'http://%s/oauth/access_token' % TUMBLR_SERVER -TUMBLR_CHECK_AUTH = 'http://api.tumblr.com/v2/user/info' - - -class TumblrBackend(OAuthBackend): - name = 'tumblr' - - def get_user_id(self, details, response): - return details['username'] - - def get_user_details(self, response): - # http://www.tumblr.com/docs/en/api/v2#user-methods - user_info = response['response']['user'] - data = {'username': user_info['name']} - blogs = user_info['blogs'] - for blog in blogs: - if blog['primary']: - data['fullname'] = blog['title'] - break - return data - - @classmethod - def tokens(cls, instance): - """ - Return the tokens needed to authenticate the access to any API the - service might provide. Tumblr uses a pair of OAuthToken consisting - on a oauth_token and oauth_token_secret. - - instance must be a UserSocialAuth instance. - """ - token = super(TumblrBackend, cls).tokens(instance) - if token and 'access_token' in token: - token = dict(tok.split('=') - for tok in token['access_token'].split('&')) - return token - - -class TumblrAuth(ConsumerBasedOAuth): - AUTH_BACKEND = TumblrBackend - AUTHORIZATION_URL = TUMBLR_AUTHORIZATION_URL - REQUEST_TOKEN_URL = TUMBLR_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = TUMBLR_ACCESS_TOKEN_URL - SERVER_URL = TUMBLR_SERVER - SETTINGS_KEY_NAME = 'TUMBLR_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'TUMBLR_CONSUMER_SECRET' - - def user_data(self, access_token): - request = self.oauth_request(access_token, TUMBLR_CHECK_AUTH) - json = self.fetch_response(request) - - try: - return simplejson.loads(json) - except ValueError: - return None - - def unauthorized_token(self): - request = self.oauth_request(token=None, url=self.REQUEST_TOKEN_URL) - response = self.fetch_response(request) - - return OAuthToken.from_string(response) - - def oauth_request(self, token, url, extra_params=None): - params = { - 'oauth_callback': self.redirect_uri, - } - - if extra_params: - params.update(extra_params) - - if 'oauth_verifier' in self.data: - params['oauth_verifier'] = self.data['oauth_verifier'] - - request = OAuthRequest.from_consumer_and_token(self.consumer, - token=token, - http_url=url, - parameters=params) - request.sign_request(SignatureMethod_HMAC_SHA1(), self.consumer, token) - return request - - def fetch_response(self, request): - """Executes request and fetchs service response""" - response = urlopen(request.to_url()) - return response.read() - - -BACKENDS = { - 'tumblr': TumblrAuth -} +from social.backends.tumblr import TumblrOAuth as TumblrAuth diff --git a/social_auth/backends/contrib/twilio.py b/social_auth/backends/contrib/twilio.py index 68a67fc76..5c38f955d 100644 --- a/social_auth/backends/contrib/twilio.py +++ b/social_auth/backends/contrib/twilio.py @@ -1,77 +1 @@ -""" -Twilio support -""" -from urllib import urlencode -from re import sub - -from django.contrib.auth import authenticate -from django.conf import settings - -from social_auth.backends import SocialAuthBackend, BaseAuth - - -TWILIO_SERVER = 'https://www.twilio.com' -TWILIO_AUTHORIZATION_URL = 'https://www.twilio.com/authorize/' - - -class TwilioBackend(SocialAuthBackend): - name = 'twilio' - - def get_user_id(self, details, response): - return response['AccountSid'] - - def get_user_details(self, response): - """Return twilio details, Twilio only provides AccountSID as - parameters.""" - # /complete/twilio/?AccountSid=ACc65ea16c9ebd4d4684edf814995b27e - account_sid = response['AccountSid'] - return {'username': account_sid, - 'email': '', - 'fullname': '', - 'first_name': '', - 'last_name': ''} - - -# Auth classes -class TwilioAuth(BaseAuth): - """Twilio authentication""" - AUTH_BACKEND = TwilioBackend - SETTINGS_KEY_NAME = 'TWILIO_CONNECT_KEY' - SETTINGS_SECRET_NAME = 'TWILIO_AUTH_TOKEN' - - def auth_url(self): - """Return authorization redirect url.""" - key = self.connect_api_key() - callback = self.request.build_absolute_uri(self.redirect) - callback = sub(r'^https', u'http', callback) - query = urlencode({'cb': callback}) - return '%s%s?%s' % (TWILIO_AUTHORIZATION_URL, key, query) - - def auth_complete(self, *args, **kwargs): - """Completes loging process, must return user instance""" - account_sid = self.data.get('AccountSid') - - if not account_sid: - raise ValueError('No AccountSid returned') - - kwargs.update({'response': self.data, self.AUTH_BACKEND.name: True}) - return authenticate(*args, **kwargs) - - @classmethod - def enabled(cls): - """Enable only if settings are defined.""" - return cls.connect_api_key and cls.secret_key - - @classmethod - def connect_api_key(cls): - return getattr(settings, cls.SETTINGS_KEY_NAME, '') - - @classmethod - def secret_key(cls): - return getattr(settings, cls.SETTINGS_SECRET_NAME, '') - - -# Backend definition -BACKENDS = { - 'twilio': TwilioAuth -} +from social.backends.twilio import TwilioAuth as TwilioAuth diff --git a/social_auth/backends/contrib/vk.py b/social_auth/backends/contrib/vk.py index 2d9d8d226..623037746 100644 --- a/social_auth/backends/contrib/vk.py +++ b/social_auth/backends/contrib/vk.py @@ -1,304 +1,3 @@ -# -*- coding: utf-8 -*- -""" -vk.com OpenAPI and OAuth 2.0 support. - -This contribution adds support for VK.com OpenAPI, OAuth 2.0 and IFrame apps. -Username is retrieved from the identity returned by server. -""" - -from django.contrib.auth import authenticate -from django.utils import simplejson - -from urllib import urlencode -from hashlib import md5 -from time import time - -from social_auth.backends import SocialAuthBackend, OAuthBackend, BaseAuth, \ - BaseOAuth2 -from social_auth.exceptions import AuthTokenRevoked, AuthException, \ - AuthCanceled, AuthFailed -from social_auth.utils import setting, log, dsa_urlopen - - -# vk configuration -VK_AUTHORIZATION_URL = 'http://oauth.vk.com/authorize' -VK_ACCESS_TOKEN_URL = 'https://oauth.vk.com/access_token' -VK_SERVER = 'vk.com' -VK_DEFAULT_DATA = ['first_name', 'last_name', 'screen_name', - 'nickname', 'photo'] - -VK_API_URL = 'https://api.vk.com/method/' -VK_SERVER_API_URL = 'http://api.vk.com/api.php' -VK_API_VERSION = '3.0' - -LOCAL_HTML = setting('VK_LOCAL_HTML', setting('VKONTAKTE_LOCAL_HTML', - 'vkontakte.html')) - -USE_APP_AUTH = setting('VKAPP_APP_ID', False) - - -class VKOpenAPIBackend(SocialAuthBackend): - """VK OpenAPI authentication backend""" - name = 'vk-openapi' - - def get_user_id(self, details, response): - """Return user unique id provided by VK""" - return response['id'] - - def get_user_details(self, response): - """Return user details from VK request""" - nickname = response.get('nickname') or response['id'] - if isinstance(nickname, (list, tuple, )): - nickname = nickname[0] - return { - 'username': nickname, - 'email': '', - 'fullname': '', - 'first_name': response.get('first_name')[0] - if 'first_name' in response else '', - 'last_name': response.get('last_name')[0] - if 'last_name' in response else '' - } - - -class VKOpenAPIAuth(BaseAuth): - """VKontakte OpenAPI authorization mechanism""" - AUTH_BACKEND = VKOpenAPIBackend - APP_ID = setting('VKONTAKTE_APP_ID') - - def user_data(self, access_token, *args, **kwargs): - return dict(self.request.GET) - - def auth_html(self): - """Returns local VK authentication page, not necessary for - VK to authenticate. - """ - from django.template import RequestContext, loader - - dict = {'VK_APP_ID': self.APP_ID, - 'VK_COMPLETE_URL': self.redirect} - - vk_template = loader.get_template(LOCAL_HTML) - context = RequestContext(self.request, dict) - - return vk_template.render(context) - - def auth_complete(self, *args, **kwargs): - """Performs check of authentication in VK, returns User if - succeeded""" - app_cookie = 'vk_app_' + self.APP_ID - - if not 'id' in self.request.GET or \ - not app_cookie in self.request.COOKIES: - raise AuthCanceled(self) - - cookie_dict = dict(item.split('=') for item in - self.request.COOKIES[app_cookie].split('&')) - check_str = ''.join(item + '=' + cookie_dict[item] - for item in ['expire', 'mid', 'secret', 'sid']) - - hash = md5(check_str + setting('VK_API_SECRET')).hexdigest() - - if hash != cookie_dict['sig'] or int(cookie_dict['expire']) < time(): - raise AuthFailed('VK authentication failed: invalid hash') - else: - kwargs.update({ - 'auth': self, - 'response': self.user_data(cookie_dict['mid']), - self.AUTH_BACKEND.name: True - }) - return authenticate(*args, **kwargs) - - @property - def uses_redirect(self): - """VK does not require visiting server url in order - to do authentication, so auth_xxx methods are not needed to be called. - Their current implementation is just an example""" - return False - - -class VKOAuth2Backend(OAuthBackend): - """VKOAuth2 authentication backend""" - name = 'vk-oauth' - - EXTRA_DATA = [ - ('id', 'id'), - ('expires', 'expires') - ] - - def get_user_id(self, details, response): - """OAuth providers return an unique user id in response""" - return response['user_id'] - - def get_user_details(self, response): - """Return user details from VK account""" - return { - 'username': response.get('screen_name'), - 'email': '', - 'first_name': response.get('first_name'), - 'last_name': response.get('last_name') - } - - -class VKApplicationBackend(VKOAuth2Backend): - name = 'vk-app' - - -class VKOAuth2(BaseOAuth2): - """VK OAuth mechanism""" - AUTHORIZATION_URL = VK_AUTHORIZATION_URL - ACCESS_TOKEN_URL = VK_ACCESS_TOKEN_URL - AUTH_BACKEND = VKOAuth2Backend - SETTINGS_KEY_NAME = 'VK_APP_ID' - SETTINGS_SECRET_NAME = 'VK_API_SECRET' - # Look at: - # http://vk.com/developers.php?oid=-17680044&p=Application_Access_Rights - SCOPE_VAR_NAME = 'VK_EXTRA_SCOPE' - - def get_scope(self): - return setting(VKOAuth2.SCOPE_VAR_NAME) or \ - setting('VK_OAUTH2_EXTRA_SCOPE') - - def user_data(self, access_token, response, *args, **kwargs): - """Loads user data from service""" - fields = ','.join(VK_DEFAULT_DATA + setting('VK_EXTRA_DATA', [])) - params = {'access_token': access_token, - 'fields': fields, - 'uids': response.get('user_id')} - - data = vk_api('users.get', params) - - if data.get('error'): - error = data['error'] - msg = error.get('error_msg', 'Unknown error') - if error.get('error_code') == 5: - raise AuthTokenRevoked(self, msg) - else: - raise AuthException(self, msg) - - if data: - data = data.get('response')[0] - data['user_photo'] = data.get('photo') # Backward compatibility - - return data - - -class VKAppAuth(VKOAuth2): - """VKontakte Application Authentication support""" - AUTH_BACKEND = VKApplicationBackend - SETTINGS_KEY_NAME = 'VKAPP_APP_ID' - SETTINGS_SECRET_NAME = 'VKAPP_API_SECRET' - - def auth_complete(self, *args, **kwargs): - stop, app_auth = self.application_auth(*args, **kwargs) - - if app_auth: - return app_auth - - if stop: - return None - - return super(VKAppAuth, self).auth_complete(*args, **kwargs) - - def user_profile(self, user_id, access_token=None): - data = {'uids': user_id, 'fields': 'photo'} - - if access_token: - data['access_token'] = access_token - - profiles = vk_api('getProfiles', data, is_app=True).get('response', - None) - return profiles[0] if profiles else None - - def is_app_user(self, user_id, access_token=None): - """Returns app usage flag from VK API""" - - data = {'uid': user_id} - - if access_token: - data['access_token'] = access_token - - return vk_api('isAppUser', data, is_app=True).get('response', 0) - - def application_auth(self, *args, **kwargs): - required_params = ('is_app_user', 'viewer_id', 'access_token', - 'api_id') - - for param in required_params: - if not param in self.request.REQUEST: - return (False, None) - - auth_key = self.request.REQUEST.get('auth_key') - - # Verify signature, if present - if auth_key: - check_key = md5('_'.join([ - setting(self.SETTINGS_KEY_NAME), - self.request.REQUEST.get('viewer_id'), - setting(self.SETTINGS_SECRET_NAME) - ])).hexdigest() - - if check_key != auth_key: - raise ValueError('VK authentication failed: invalid ' - 'auth key') - - user_check = setting('VKAPP_USER_MODE', 0) - user_id = self.request.REQUEST.get('viewer_id') - - if user_check: - is_user = self.request.REQUEST.get('is_app_user') \ - if user_check == 1 else self.is_app_user(user_id) - - if not int(is_user): - return (True, None) - - data = self.user_profile(user_id) - data['user_id'] = user_id - - return (True, authenticate(*args, **{'auth': self, - 'request': self.request, - 'response': data, self.AUTH_BACKEND.name: True - })) - - -def vk_api(method, data, is_app=False): - """Calls VK OpenAPI method - https://vk.com/apiclub, - https://vk.com/pages.php?o=-1&p=%C2%FB%EF%EE%EB%ED%E5%ED%E8%E5%20%E7' - %E0%EF%F0%EE%F1%EE%E2%20%EA%20API - """ - - # We need to perform server-side call if no access_token - if not 'access_token' in data: - if not 'v' in data: - data['v'] = VK_API_VERSION - - if not 'api_id' in data: - data['api_id'] = setting('VKAPP_APP_ID' if is_app else 'VK_APP_ID') - - data['method'] = method - data['format'] = 'json' - - url = VK_SERVER_API_URL - secret = setting('VKAPP_API_SECRET' if is_app else 'VK_API_SECRET') - - param_list = sorted(list(item + '=' + data[item] for item in data)) - data['sig'] = md5(''.join(param_list) + secret).hexdigest() - else: - url = VK_API_URL + method - - params = urlencode(data) - url += '?' + params - try: - return simplejson.load(dsa_urlopen(url)) - except (TypeError, KeyError, IOError, ValueError, IndexError): - log('error', 'Could not load data from vk.com', - exc_info=True, extra=dict(data=data)) - return None - - -# Backend definition -BACKENDS = { - 'vk-openapi': VKOpenAPIAuth, - 'vk-oauth': VKOAuth2 if not USE_APP_AUTH else VKAppAuth, -} +from social.backends.vk import VKontakteOpenAPI as VKOpenAPIBackend, \ + VKOAuth2 as VKOAuth2Backend, \ + VKAppOAuth2 as VKAppAuth diff --git a/social_auth/backends/contrib/weibo.py b/social_auth/backends/contrib/weibo.py index d9d08364e..2f7b98295 100644 --- a/social_auth/backends/contrib/weibo.py +++ b/social_auth/backends/contrib/weibo.py @@ -1,74 +1 @@ -#coding:utf8 -#author:hepochen@gmail.com https://github.com/hepochen -""" -Weibo OAuth2 support. - -This script adds support for Weibo OAuth service. An application must -be registered first on http://open.weibo.com. - -WEIBO_CLIENT_KEY and WEIBO_CLIENT_SECRET must be defined in the settings.py -correctly. - -By default account id,profile_image_url,gender are stored in extra_data field, -check OAuthBackend class for details on how to extend it. -""" -from urllib import urlencode - -from django.utils import simplejson - -from social_auth.backends import OAuthBackend, BaseOAuth2 -from social_auth.utils import dsa_urlopen - - -WEIBO_SERVER = 'api.weibo.com' -WEIBO_REQUEST_TOKEN_URL = 'https://%s/oauth2/request_token' % WEIBO_SERVER -WEIBO_ACCESS_TOKEN_URL = 'https://%s/oauth2/access_token' % WEIBO_SERVER -WEIBO_AUTHORIZATION_URL = 'https://%s/oauth2/authorize' % WEIBO_SERVER - - -class WeiboBackend(OAuthBackend): - """Weibo (of sina) OAuth authentication backend""" - name = 'weibo' - # Default extra data to store - EXTRA_DATA = [ - ('id', 'id'), - ('name', 'username'), - ('profile_image_url', 'profile_image_url'), - ('gender', 'gender') - ] - - def get_user_id(self, details, response): - return response['uid'] - - def get_user_details(self, response): - """Return user details from Weibo. API URL is: - https://api.weibo.com/2/users/show.json/?uid=&access_token= - """ - return {'username': response.get("name", ""), - 'first_name': response.get('screen_name', '')} - - -class WeiboAuth(BaseOAuth2): - """Weibo OAuth authentication mechanism""" - AUTHORIZATION_URL = WEIBO_AUTHORIZATION_URL - REQUEST_TOKEN_URL = WEIBO_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = WEIBO_ACCESS_TOKEN_URL - AUTH_BACKEND = WeiboBackend - SETTINGS_KEY_NAME = 'WEIBO_CLIENT_KEY' - SETTINGS_SECRET_NAME = 'WEIBO_CLIENT_SECRET' - REDIRECT_STATE = False - - def user_data(self, access_token, *args, **kwargs): - uid = kwargs.get('response', {}).get('uid') - data = {'access_token': access_token, 'uid': uid} - url = 'https://api.weibo.com/2/users/show.json?' + urlencode(data) - try: - return simplejson.loads(dsa_urlopen(url).read()) - except (ValueError, KeyError, IOError): - return None - - -# Backend definition -BACKENDS = { - 'weibo': WeiboAuth -} +from social.backends.weibo import WeiboOAuth2 as WeiboAuth diff --git a/social_auth/backends/contrib/xing.py b/social_auth/backends/contrib/xing.py index 6c116793b..939a152f1 100644 --- a/social_auth/backends/contrib/xing.py +++ b/social_auth/backends/contrib/xing.py @@ -1,105 +1 @@ -""" -XING OAuth support - -No extra configurations are needed to make this work. -""" -import oauth2 as oauth -from oauth2 import Token - -from urllib import urlencode - -from django.utils import simplejson - -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend -from social_auth.exceptions import AuthCanceled, AuthUnknownError - - -XING_SERVER = 'xing.com' -XING_REQUEST_TOKEN_URL = 'https://api.%s/v1/request_token' % \ - XING_SERVER -XING_ACCESS_TOKEN_URL = 'https://api.%s/v1/access_token' % \ - XING_SERVER -XING_AUTHORIZATION_URL = 'https://www.%s/v1/authorize' % \ - XING_SERVER -XING_CHECK_AUTH = 'https://api.%s/v1/users/me.json' % XING_SERVER - - -class XingBackend(OAuthBackend): - """Xing OAuth authentication backend""" - name = 'xing' - EXTRA_DATA = [ - ('id', 'id'), - ('user_id', 'user_id') - ] - - def get_user_details(self, response): - """Return user details from Xing account""" - first_name, last_name = response['first_name'], response['last_name'] - email = response.get('email', '') - return {'username': first_name + last_name, - 'fullname': first_name + ' ' + last_name, - 'first_name': first_name, - 'last_name': last_name, - 'email': email} - - -class XingAuth(ConsumerBasedOAuth): - """Xing OAuth authentication mechanism""" - AUTH_BACKEND = XingBackend - AUTHORIZATION_URL = XING_AUTHORIZATION_URL - REQUEST_TOKEN_URL = XING_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = XING_ACCESS_TOKEN_URL - SETTINGS_KEY_NAME = 'XING_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'XING_CONSUMER_SECRET' - SCOPE_SEPARATOR = '+' - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - key, secret = self.get_key_and_secret() - consumer = oauth.Consumer(key=key, secret=secret) - client = oauth.Client(consumer, access_token) - resp, content = client.request(XING_CHECK_AUTH, 'GET') - profile = simplejson.loads(content)['users'][0] - - try: - return { - 'user_id': profile['id'], - 'id': profile['id'], - 'first_name': profile['first_name'], - 'last_name': profile['last_name'], - 'email': profile['active_email'] - } - except (KeyError, IndexError): - pass - - def auth_complete(self, *args, **kwargs): - """Complete auth process. Check Xing error response.""" - oauth_problem = self.request.GET.get('oauth_problem') - if oauth_problem: - if oauth_problem == 'user_refused': - raise AuthCanceled(self, '') - else: - raise AuthUnknownError(self, 'Xing error was %s' % - oauth_problem) - return super(XingAuth, self).auth_complete(*args, **kwargs) - - def unauthorized_token(self): - """Makes first request to oauth. Returns an unauthorized Token.""" - request_token_url = self.REQUEST_TOKEN_URL - scope = self.get_scope_argument() - if scope: - request_token_url = request_token_url + '?' + urlencode(scope) - - request = self.oauth_request( - token=None, - url=request_token_url, - extra_params=self.request_token_extra_arguments() - ) - response = self.fetch_response(request) - return Token.from_string(response) - - -# Backend definition -BACKENDS = { - 'xing': XingAuth, -} +from social.backends.xing import XingOAuth as XingAuth diff --git a/social_auth/backends/contrib/yahoo.py b/social_auth/backends/contrib/yahoo.py index 8f37f6e1c..4481a5dc4 100644 --- a/social_auth/backends/contrib/yahoo.py +++ b/social_auth/backends/contrib/yahoo.py @@ -1,104 +1 @@ -""" -OAuth 1.0 Yahoo backend - -Options: -YAHOO_CONSUMER_KEY -YAHOO_CONSUMER_SECRET - -References: -* http://developer.yahoo.com/oauth/guide/oauth-auth-flow.html -* http://developer.yahoo.com/social/rest_api_guide/ -* introspective-guid-resource.html -* http://developer.yahoo.com/social/rest_api_guide/ -* extended-profile-resource.html - -Scopes: -To make this extension works correctly you have to have at least -Yahoo Profile scope with Read permission - -Throws: -AuthUnknownError - if user data retrieval fails (guid or profile) -""" - -from django.utils import simplejson - -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend -from social_auth.exceptions import AuthUnknownError - - -# Google OAuth base configuration -YAHOO_OAUTH_SERVER = 'api.login.yahoo.com' -REQUEST_TOKEN_URL = 'https://api.login.yahoo.com/oauth/v2/get_request_token' -AUTHORIZATION_URL = 'https://api.login.yahoo.com/oauth/v2/request_auth' -ACCESS_TOKEN_URL = 'https://api.login.yahoo.com/oauth/v2/get_token' - - -class YahooOAuthBackend(OAuthBackend): - """Yahoo OAuth authentication backend""" - name = 'yahoo-oauth' - - EXTRA_DATA = [ - ('guid', 'id'), - ('access_token', 'access_token'), - ('expires', 'expires') - ] - - def get_user_id(self, details, response): - return response['guid'] - - def get_user_details(self, response): - """Return user details from Yahoo Profile""" - fname = response.get('givenName') - lname = response.get('familyName') - if 'emails' in response: - email = response.get('emails')[0]['handle'] - else: - email = '' - return {'username': response.get('nickname'), - 'email': email, - 'fullname': '%s %s' % (fname, lname), - 'first_name': fname, - 'last_name': lname} - - -class YahooOAuth(ConsumerBasedOAuth): - AUTHORIZATION_URL = AUTHORIZATION_URL - REQUEST_TOKEN_URL = REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = ACCESS_TOKEN_URL - AUTH_BACKEND = YahooOAuthBackend - SETTINGS_KEY_NAME = 'YAHOO_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'YAHOO_CONSUMER_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from service""" - guid = self._get_guid(access_token) - url = 'http://social.yahooapis.com/v1/user/%s/profile?format=json' \ - % guid - request = self.oauth_request(access_token, url) - response = self.fetch_response(request) - try: - return simplejson.loads(response)['profile'] - except ValueError: - raise AuthUnknownError('Error during profile retrieval, ' - 'please, try again later') - - def _get_guid(self, access_token): - """ - Beause you have to provide GUID for every API request - it's also returned during one of OAuth calls - """ - url = 'http://social.yahooapis.com/v1/me/guid?format=json' - request = self.oauth_request(access_token, url) - response = self.fetch_response(request) - try: - json = simplejson.loads(response) - return json['guid']['value'] - except ValueError: - raise AuthUnknownError('Error during user id retrieval, ' - 'please, try again later') - - -# Backend definition -BACKENDS = { - 'yahoo-oauth': YahooOAuth -} +from social.backends.yahoo import YahooOAuth as YahooOAuthBackend diff --git a/social_auth/backends/contrib/yammer.py b/social_auth/backends/contrib/yammer.py index 23875ebda..ce9da3d53 100644 --- a/social_auth/backends/contrib/yammer.py +++ b/social_auth/backends/contrib/yammer.py @@ -1,99 +1 @@ -""" -Yammer OAuth2 support -""" -import logging -from urllib import urlencode -from urlparse import parse_qs - -from django.utils import simplejson -from django.utils.datastructures import MergeDict - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.exceptions import AuthCanceled -from social_auth.utils import dsa_urlopen, setting - - -YAMMER_SERVER = 'yammer.com' -YAMMER_STAGING_SERVER = 'staging.yammer.com' -YAMMER_OAUTH_URL = 'https://www.%s/oauth2/' % YAMMER_SERVER -YAMMER_AUTH_URL = 'https://www.%s/dialog/oauth' % YAMMER_SERVER -YAMMER_API_URL = 'https://www.%s/api/v1/' % YAMMER_SERVER - - -class YammerBackend(OAuthBackend): - name = 'yammer' - EXTRA_DATA = [ - ('id', 'id'), - ('expires', 'expires'), - ('mugshot_url', 'mugshot_url') - ] - - def get_user_id(self, details, response): - return response['user']['id'] - - def get_user_details(self, response): - username = response['user']['name'] - first_name = response['user']['first_name'] - last_name = response['user']['last_name'] - full_name = response['user']['full_name'] - email = response['user']['contact']['email_addresses'][0]['address'] - mugshot_url = response['user']['mugshot_url'] - return { - 'username': username, - 'email': email, - 'fullname': full_name, - 'first_name': first_name, - 'last_name': last_name, - 'picture_url': mugshot_url - } - - -class YammerOAuth2(BaseOAuth2): - AUTH_BACKEND = YammerBackend - AUTHORIZATION_URL = YAMMER_AUTH_URL - ACCESS_TOKEN_URL = '%s%s' % (YAMMER_OAUTH_URL, 'access_token') - REQUEST_TOKEN_URL = '%s%s' % (YAMMER_OAUTH_URL, 'request_token') - SETTINGS_KEY_NAME = 'YAMMER_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'YAMMER_CONSUMER_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Load user data from yammer""" - params = { - 'client_id': setting(self.SETTINGS_KEY_NAME, ''), - 'client_secret': setting(self.SETTINGS_SECRET_NAME, ''), - 'code': access_token - } - - url = '%s?%s' % (self.ACCESS_TOKEN_URL, urlencode(params)) - - try: - return simplejson.load(dsa_urlopen(url)) - except Exception, e: - logging.exception(e) - return None - - def auth_complete(self, *args, **kwargs): - """Yammer API is a little strange""" - if 'error' in self.data: - logging.error("%s: %s:\n%s" % ( - self.data('error'), self.data('error_reason'), - self.data('error_description') - )) - raise AuthCanceled(self) - - # now we need to clean up the data params - data = dict(self.data.copy()) - redirect_state = data.get('redirect_state') - if redirect_state and '?' in redirect_state: - redirect_state, extra = redirect_state.split('?', 1) - extra = parse_qs(extra) - data['redirect_state'] = redirect_state - if 'code' in extra: - data['code'] = extra['code'][0] - self.data = MergeDict(data) - return super(YammerOAuth2, self).auth_complete(*args, **kwargs) - - -BACKENDS = { - 'yammer': YammerOAuth2 -} +from social.backends.yammer import YammerOAuth2 diff --git a/social_auth/backends/contrib/yammer_staging.py b/social_auth/backends/contrib/yammer_staging.py index 144b9e4eb..aa02361e0 100644 --- a/social_auth/backends/contrib/yammer_staging.py +++ b/social_auth/backends/contrib/yammer_staging.py @@ -1,27 +1 @@ -""" -Yammer Staging OAuth2 support -""" -from social_auth.backends.contrib.yammer import YammerBackend, YammerOAuth2 - - -YAMMER_STAGING_SERVER = 'staging.yammer.com' -YAMMER_STAGING_OAUTH_URL = 'https://www.%s/oauth2/' % YAMMER_STAGING_SERVER -YAMMER_STAGING_AUTH_URL = 'https://www.%s/dialog/oauth' % YAMMER_STAGING_SERVER - - -class YammerStagingBackend(YammerBackend): - name = 'yammer_staging' - - -class YammerStagingOAuth2(YammerOAuth2): - AUTH_BACKEND = YammerStagingBackend - AUTHORIZATION_URL = YAMMER_STAGING_AUTH_URL - ACCESS_TOKEN_URL = '%s%s' % (YAMMER_STAGING_OAUTH_URL, 'access_token') - REQUEST_TOKEN_URL = '%s%s' % (YAMMER_STAGING_OAUTH_URL, 'request_token') - SETTINGS_KEY_NAME = 'YAMMER_STAGING_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'YAMMER_STAGING_CONSUMER_SECRET' - - -BACKENDS = { - 'yammer_staging': YammerStagingOAuth2 -} +from social.backends.yammer import YammerStagingOAuth2 diff --git a/social_auth/backends/contrib/yandex.py b/social_auth/backends/contrib/yandex.py index 3e7294cd7..12dacbf5e 100644 --- a/social_auth/backends/contrib/yandex.py +++ b/social_auth/backends/contrib/yandex.py @@ -1,159 +1,3 @@ -""" -Yandex OpenID and OAuth2 support. - -This contribution adds support for Yandex.ru OpenID service in the form -openid.yandex.ru/user. Username is retrieved from the identity url. - -If username is not specified, OpenID 2.0 url used for authentication. -""" -from django.utils import simplejson - -from urllib import urlencode -from urlparse import urlparse, urlsplit - -from social_auth.backends import OpenIDBackend, OpenIdAuth, OAuthBackend, \ - BaseOAuth2 - -from social_auth.utils import setting, log, dsa_urlopen - -# Yandex configuration -YANDEX_AUTHORIZATION_URL = 'https://oauth.yandex.ru/authorize' -YANDEX_ACCESS_TOKEN_URL = 'https://oauth.yandex.ru/token' -YANDEX_SERVER = 'oauth.yandex.ru' - -YANDEX_OPENID_URL = 'http://openid.yandex.ru' - - -def get_username_from_url(links): - try: - host = urlparse(links.get('www')).hostname - return host.split('.')[0] - except (IndexError, AttributeError): - return None - - -class YandexBackend(OpenIDBackend): - """Yandex OpenID authentication backend""" - name = 'yandex' - - def get_user_id(self, details, response): - return details['email'] or response.identity_url - - def get_user_details(self, response): - """Generate username from identity url""" - values = super(YandexBackend, self).get_user_details(response) - values['username'] = values.get('username') or \ - urlsplit(response.identity_url).path.strip('/') - - values['email'] = values.get('email', '') - - return values - - -class YandexAuth(OpenIdAuth): - """Yandex OpenID authentication""" - AUTH_BACKEND = YandexBackend - - def openid_url(self): - """Returns Yandex authentication URL""" - return YANDEX_OPENID_URL - - -class YaruBackend(OAuthBackend): - """Yandex OAuth authentication backend""" - name = 'yaru' - EXTRA_DATA = [ - ('id', 'id'), - ('expires', 'expires') - ] - - def get_user_details(self, response): - """Return user details from Yandex account""" - name = response['name'] - last_name = '' - - if ' ' in name: - names = name.split(' ') - last_name = names[0] - first_name = names[1] - else: - first_name = name - - return { - 'username': get_username_from_url(response.get('links')), - 'email': response.get('email', ''), - 'first_name': first_name, - 'last_name': last_name, - } - - -class YaruAuth(BaseOAuth2): - """Yandex Ya.ru OAuth mechanism""" - AUTHORIZATION_URL = YANDEX_AUTHORIZATION_URL - ACCESS_TOKEN_URL = YANDEX_ACCESS_TOKEN_URL - AUTH_BACKEND = YaruBackend - REDIRECT_STATE = False - SETTINGS_KEY_NAME = 'YANDEX_APP_ID' - SETTINGS_SECRET_NAME = 'YANDEX_API_SECRET' - - def get_api_url(self): - return 'https://api-yaru.yandex.ru/me/' - - def user_data(self, access_token, response, *args, **kwargs): - """Loads user data from service""" - params = {'oauth_token': access_token, - 'format': 'json', - 'text': 1, - } - - url = self.get_api_url() + '?' + urlencode(params) - try: - return simplejson.load(dsa_urlopen(url)) - except (ValueError, IndexError): - log('error', 'Could not load data from Yandex.', - exc_info=True, extra=dict(data=params)) - return None - - -class YandexOAuth2Backend(YaruBackend): - """Legacy Yandex OAuth2 authentication backend""" - name = 'yandex-oauth2' - - -class YandexOAuth2(YaruAuth): - """Yandex Ya.ru/Moi Krug OAuth mechanism""" - AUTH_BACKEND = YandexOAuth2Backend - - def get_api_url(self): - return setting('YANDEX_OAUTH2_API_URL') - - def user_data(self, access_token, response, *args, **kwargs): - reply = super(YandexOAuth2, self).user_data(access_token, - response, args, kwargs) - - if reply: - if isinstance(reply, list) and len(reply) >= 1: - reply = reply[0] - - if 'links' in reply: - userpic = reply['links'].get('avatar') - elif 'avatar' in reply: - userpic = reply['avatar'].get('Portrait') - else: - userpic = '' - - reply.update({ - 'id': reply['id'].split("/")[-1], - 'access_token': access_token, - 'userpic': userpic - }) - - return reply - - -# Backend definition -BACKENDS = { - 'yandex': YandexAuth, - 'yaru': YaruAuth, - 'yandex-oauth2': YandexOAuth2 -} +from social.backends.yandex import YandexOpenId as YandexBackend, \ + YaruOAuth2 as YaruBackend, \ + YandexOAuth2 as YandexOAuth2Backend diff --git a/social_auth/backends/facebook.py b/social_auth/backends/facebook.py index df0d27cab..30539f49a 100644 --- a/social_auth/backends/facebook.py +++ b/social_auth/backends/facebook.py @@ -1,311 +1,5 @@ -""" -Facebook OAuth support. - -This contribution adds support for Facebook OAuth service. The settings -FACEBOOK_APP_ID and FACEBOOK_API_SECRET must be defined with the values -given by Facebook application registration process. - -Extended permissions are supported by defining FACEBOOK_EXTENDED_PERMISSIONS -setting, it must be a list of values to request. - -By default account id and token expiration time are stored in extra_data -field, check OAuthBackend class for details on how to extend it. -""" -import cgi -import base64 -import hmac -import hashlib -import time -from urllib import urlencode -from urllib2 import HTTPError - -from django.utils import simplejson -from django.contrib.auth import authenticate -from django.http import HttpResponse -from django.template import TemplateDoesNotExist, RequestContext, loader - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import sanitize_log_data, backend_setting, setting,\ - log, dsa_urlopen -from social_auth.exceptions import AuthException, AuthCanceled, AuthFailed,\ - AuthTokenError, AuthUnknownError - - -# Facebook configuration -FACEBOOK_ME = 'https://graph.facebook.com/me?' -ACCESS_TOKEN = 'https://graph.facebook.com/oauth/access_token?' -USE_APP_AUTH = setting('FACEBOOK_APP_AUTH', False) -LOCAL_HTML = setting('FACEBOOK_LOCAL_HTML', 'facebook.html') -APP_NAMESPACE = setting('FACEBOOK_APP_NAMESPACE', None) -REDIRECT_HTML = """ - -""" - - -class FacebookBackend(OAuthBackend): - """Facebook OAuth2 authentication backend""" - name = 'facebook' - # Default extra data to store - EXTRA_DATA = [ - ('id', 'id'), - ('expires', 'expires') - ] - - def get_user_details(self, response): - """Return user details from Facebook account""" - return {'username': response.get('username', response.get('name')), - 'email': response.get('email', ''), - 'fullname': response.get('name', ''), - 'first_name': response.get('first_name', ''), - 'last_name': response.get('last_name', '')} - - -class FacebookAuth(BaseOAuth2): - """Facebook OAuth2 support""" - AUTH_BACKEND = FacebookBackend - RESPONSE_TYPE = None - SCOPE_SEPARATOR = ',' - AUTHORIZATION_URL = 'https://www.facebook.com/dialog/oauth' - REVOKE_TOKEN_URL = 'https://graph.facebook.com/{uid}/permissions' - REVOKE_TOKEN_METHOD = 'DELETE' - ACCESS_TOKEN_URL = ACCESS_TOKEN - SETTINGS_KEY_NAME = 'FACEBOOK_APP_ID' - SETTINGS_SECRET_NAME = 'FACEBOOK_API_SECRET' - SCOPE_VAR_NAME = 'FACEBOOK_EXTENDED_PERMISSIONS' - EXTRA_PARAMS_VAR_NAME = 'FACEBOOK_PROFILE_EXTRA_PARAMS' - - def user_data(self, access_token, *args, **kwargs): - """ - Grab user profile information from facebook. - - returns: dict or None - """ - - data = None - params = backend_setting(self, self.EXTRA_PARAMS_VAR_NAME, {}) - params['access_token'] = access_token - url = FACEBOOK_ME + urlencode(params) - - try: - response = dsa_urlopen(url) - data = simplejson.load(response) - except ValueError: - extra = {'access_token': sanitize_log_data(access_token)} - log('error', 'Could not load user data from Facebook.', - exc_info=True, extra=extra) - except HTTPError: - extra = {'access_token': sanitize_log_data(access_token)} - log('error', 'Error validating access token.', - exc_info=True, extra=extra) - raise AuthTokenError(self) - else: - log('debug', 'Found user data for token %s', - sanitize_log_data(access_token), extra={'data': data}) - return data - - def auth_complete(self, *args, **kwargs): - """Completes loging process, must return user instance""" - access_token = None - expires = None - - if 'code' in self.data: - state = self.validate_state() - url = ACCESS_TOKEN + urlencode({ - 'client_id': backend_setting(self, self.SETTINGS_KEY_NAME), - 'redirect_uri': self.get_redirect_uri(state), - 'client_secret': backend_setting( - self, - self.SETTINGS_SECRET_NAME - ), - 'code': self.data['code'] - }) - try: - payload = dsa_urlopen(url) - except HTTPError: - raise AuthFailed(self, 'There was an error authenticating ' - 'the app') - - response = payload.read() - parsed_response = cgi.parse_qs(response) - - access_token = parsed_response['access_token'][0] - if 'expires' in parsed_response: - expires = parsed_response['expires'][0] - - if 'signed_request' in self.data: - response = load_signed_request( - self.data.get('signed_request'), - backend_setting(self, self.SETTINGS_SECRET_NAME) - ) - - if response is not None: - access_token = response.get('access_token') or\ - response.get('oauth_token') or\ - self.data.get('access_token') - - if 'expires' in response: - expires = response['expires'] - - if access_token: - return self.do_auth(access_token, expires=expires, *args, **kwargs) - else: - if self.data.get('error') == 'access_denied': - raise AuthCanceled(self) - else: - raise AuthException(self) - - @classmethod - def process_refresh_token_response(cls, response): - return dict((key, val[0]) - for key, val in cgi.parse_qs(response).iteritems()) - - @classmethod - def refresh_token_params(cls, token): - client_id, client_secret = cls.get_key_and_secret() - return { - 'fb_exchange_token': token, - 'grant_type': 'fb_exchange_token', - 'client_id': client_id, - 'client_secret': client_secret - } - - def do_auth(self, access_token, expires=None, *args, **kwargs): - data = self.user_data(access_token) - - if not isinstance(data, dict): - # From time to time Facebook responds back a JSON with just - # False as value, the reason is still unknown, but since the - # data is needed (it contains the user ID used to identify the - # account on further logins), this app cannot allow it to - # continue with the auth process. - raise AuthUnknownError(self, 'An error ocurred while ' - 'retrieving users Facebook ' - 'data') - - data['access_token'] = access_token - if expires: # expires is None on offline access - data['expires'] = expires - - kwargs.update({'auth': self, - 'response': data, - self.AUTH_BACKEND.name: True}) - return authenticate(*args, **kwargs) - - @classmethod - def enabled(cls): - """Return backend enabled status by checking basic settings""" - return backend_setting(cls, cls.SETTINGS_KEY_NAME) and\ - backend_setting(cls, cls.SETTINGS_SECRET_NAME) - - @classmethod - def revoke_token_params(cls, token, uid): - return {'access_token': token} - - @classmethod - def process_revoke_token_response(cls, response): - return response.code == 200 and response.read() == 'true' - - -def base64_url_decode(data): - data = data.encode(u'ascii') - data += '=' * (4 - (len(data) % 4)) - return base64.urlsafe_b64decode(data) - - -def base64_url_encode(data): - return base64.urlsafe_b64encode(data).rstrip('=') - - -def load_signed_request(signed_request, api_secret=None): - try: - sig, payload = signed_request.split(u'.', 1) - sig = base64_url_decode(sig) - data = simplejson.loads(base64_url_decode(payload)) - - expected_sig = hmac.new(api_secret or setting('FACEBOOK_API_SECRET'), - msg=payload, - digestmod=hashlib.sha256).digest() - - # allow the signed_request to function for upto 1 day - if sig == expected_sig and \ - data[u'issued_at'] > (time.time() - 86400): - return data - except ValueError: - pass # ignore if can't split on dot - - -class FacebookAppAuth(FacebookAuth): - """Facebook Application Authentication support""" - uses_redirect = False - - def auth_complete(self, *args, **kwargs): - if not self.application_auth() and 'error' not in self.data: - return HttpResponse(self.auth_html()) - - access_token = None - expires = None - - if 'signed_request' in self.data: - response = load_signed_request( - self.data.get('signed_request'), - backend_setting(self, self.SETTINGS_SECRET_NAME) - ) - - if response is not None: - access_token = response.get('access_token') or\ - response.get('oauth_token') or\ - self.data.get('access_token') - - if 'expires' in response: - expires = response['expires'] - - if access_token: - return self.do_auth(access_token, expires=expires, *args, **kwargs) - else: - if self.data.get('error') == 'access_denied': - raise AuthCanceled(self) - else: - raise AuthException(self) - - def application_auth(self): - required_params = ('user_id', 'oauth_token') - data = load_signed_request( - self.data.get('signed_request'), - backend_setting(self, self.SETTINGS_SECRET_NAME) - ) - for param in required_params: - if not param in data: - return False - return True - - def auth_html(self): - app_id = backend_setting(self, self.SETTINGS_KEY_NAME) - ctx = { - 'FACEBOOK_APP_ID': app_id, - 'FACEBOOK_EXTENDED_PERMISSIONS': ','.join( - backend_setting(self, self.SCOPE_VAR_NAME) - ), - 'FACEBOOK_COMPLETE_URI': self.redirect_uri, - 'FACEBOOK_APP_NAMESPACE': APP_NAMESPACE or app_id - } - - try: - fb_template = loader.get_template(LOCAL_HTML) - except TemplateDoesNotExist: - fb_template = loader.get_template_from_string(REDIRECT_HTML) - context = RequestContext(self.request, ctx) - - return fb_template.render(context) - - -# Backend definition -BACKENDS = { - 'facebook': FacebookAppAuth if USE_APP_AUTH else FacebookAuth, -} +# TODO: review HTML rendering in facebook app backend +if getattr(settings, 'FACEBOOK_APP_AUTH', False): + from social.backends.facebook import FacebookAppOAuth2 as FacebookBackend +else: + from social.backends.facebook import FacebookOAuth2 as FacebookBackend diff --git a/social_auth/backends/google.py b/social_auth/backends/google.py index 5faee1512..86aab326d 100644 --- a/social_auth/backends/google.py +++ b/social_auth/backends/google.py @@ -1,269 +1,3 @@ -""" -Google OpenID and OAuth support - -OAuth works straightforward using anonymous configurations, username -is generated by requesting email to the not documented, googleapis.com -service. Registered applications can define settings GOOGLE_CONSUMER_KEY -and GOOGLE_CONSUMER_SECRET and they will be used in the auth process. -Setting GOOGLE_OAUTH_EXTRA_SCOPE can be used to access different user -related data, like calendar, contacts, docs, etc. - -OAuth2 works similar to OAuth but application must be defined on Google -APIs console https://code.google.com/apis/console/ Identity option. - -OpenID also works straightforward, it doesn't need further configurations. -""" -from urllib import urlencode -from urllib2 import Request - -from oauth2 import Request as OAuthRequest - -from django.utils import simplejson - -from social_auth.utils import setting, dsa_urlopen -from social_auth.backends import OpenIdAuth, ConsumerBasedOAuth, BaseOAuth2, \ - OAuthBackend, OpenIDBackend -from social_auth.exceptions import AuthFailed - - -# Google OAuth base configuration -GOOGLE_OAUTH_SERVER = 'www.google.com' -AUTHORIZATION_URL = 'https://www.google.com/accounts/OAuthAuthorizeToken' -REQUEST_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetRequestToken' -ACCESS_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetAccessToken' - -# Google OAuth2 base configuration -GOOGLE_OAUTH2_SERVER = 'accounts.google.com' -GOOGLE_OATUH2_AUTHORIZATION_URL = 'https://accounts.google.com/o/oauth2/auth' - -# scope for user email, specify extra scopes in settings, for example: -# GOOGLE_OAUTH_EXTRA_SCOPE = ['https://www.google.com/m8/feeds/'] -GOOGLE_OAUTH_SCOPE = ['https://www.googleapis.com/auth/userinfo#email'] -GOOGLE_OAUTH2_SCOPE = ['https://www.googleapis.com/auth/userinfo.email', - 'https://www.googleapis.com/auth/userinfo.profile'] -GOOGLEAPIS_EMAIL = 'https://www.googleapis.com/userinfo/email' -GOOGLEAPIS_PROFILE = 'https://www.googleapis.com/oauth2/v1/userinfo' -GOOGLE_OPENID_URL = 'https://www.google.com/accounts/o8/id' - - -# Backends -class GoogleOAuthBackend(OAuthBackend): - """Google OAuth authentication backend""" - name = 'google-oauth' - - def get_user_id(self, details, response): - """Use google email as unique id""" - validate_whitelists(self, details['email']) - return details['email'] - - def get_user_details(self, response): - """Return user details from Orkut account""" - email = response.get('email', '') - return {'username': email.split('@', 1)[0], - 'email': email, - 'fullname': '', - 'first_name': '', - 'last_name': ''} - - -class GoogleOAuth2Backend(GoogleOAuthBackend): - """Google OAuth2 authentication backend""" - name = 'google-oauth2' - EXTRA_DATA = [ - ('refresh_token', 'refresh_token', True), - ('expires_in', 'expires'), - ('token_type', 'token_type', True) - ] - - def get_user_id(self, details, response): - """Use google email or id as unique id""" - user_id = super(GoogleOAuth2Backend, self).get_user_id(details, - response) - if setting('GOOGLE_OAUTH2_USE_UNIQUE_USER_ID', False): - return response['id'] - return user_id - - def get_user_details(self, response): - email = response.get('email', '') - return {'username': email.split('@', 1)[0], - 'email': email, - 'fullname': response.get('name', ''), - 'first_name': response.get('given_name', ''), - 'last_name': response.get('family_name', '')} - - -class GoogleBackend(OpenIDBackend): - """Google OpenID authentication backend""" - name = 'google' - - def get_user_id(self, details, response): - """ - Return user unique id provided by service. For google user email - is unique enought to flag a single user. Email comes from schema: - http://axschema.org/contact/email - """ - validate_whitelists(self, details['email']) - - return details['email'] - - -# Auth classes -class GoogleAuth(OpenIdAuth): - """Google OpenID authentication""" - AUTH_BACKEND = GoogleBackend - - def openid_url(self): - """Return Google OpenID service url""" - return GOOGLE_OPENID_URL - - -class BaseGoogleOAuth(ConsumerBasedOAuth): - """Base class for Google OAuth mechanism""" - AUTHORIZATION_URL = AUTHORIZATION_URL - REQUEST_TOKEN_URL = REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = ACCESS_TOKEN_URL - - def user_data(self, access_token, *args, **kwargs): - """Loads user data from G service""" - raise NotImplementedError('Implement in subclass') - - -class GoogleOAuth(BaseGoogleOAuth): - """Google OAuth authorization mechanism""" - AUTH_BACKEND = GoogleOAuthBackend - SETTINGS_KEY_NAME = 'GOOGLE_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'GOOGLE_CONSUMER_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Return user data from Google API""" - request = self.oauth_request(access_token, GOOGLEAPIS_EMAIL, - {'alt': 'json'}) - url, params = request.to_url().split('?', 1) - return googleapis_email(url, params) - - def oauth_authorization_request(self, token): - """Generate OAuth request to authorize token.""" - return OAuthRequest.from_consumer_and_token(self.consumer, - token=token, - http_url=self.AUTHORIZATION_URL) - - def oauth_request(self, token, url, extra_params=None): - extra_params = extra_params or {} - scope = GOOGLE_OAUTH_SCOPE + setting('GOOGLE_OAUTH_EXTRA_SCOPE', []) - extra_params.update({ - 'scope': ' '.join(scope), - }) - if not self.registered(): - xoauth_displayname = setting('GOOGLE_DISPLAY_NAME', 'Social Auth') - extra_params['xoauth_displayname'] = xoauth_displayname - return super(GoogleOAuth, self).oauth_request(token, url, extra_params) - - @classmethod - def get_key_and_secret(cls): - """Return Google OAuth Consumer Key and Consumer Secret pair, uses - anonymous by default, beware that this marks the application as not - registered and a security badge is displayed on authorization page. - http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth - """ - try: - return super(GoogleOAuth, cls).get_key_and_secret() - except AttributeError: - return 'anonymous', 'anonymous' - - @classmethod - def enabled(cls): - """Google OAuth is always enabled because of anonymous access""" - return True - - def registered(self): - """Check if Google OAuth Consumer Key and Consumer Secret are set""" - return self.get_key_and_secret() != ('anonymous', 'anonymous') - - -# TODO: Remove this setting name check, keep for backward compatibility -_OAUTH2_KEY_NAME = setting('GOOGLE_OAUTH2_CLIENT_ID') and \ - 'GOOGLE_OAUTH2_CLIENT_ID' or \ - 'GOOGLE_OAUTH2_CLIENT_KEY' - - -class GoogleOAuth2(BaseOAuth2): - """Google OAuth2 support""" - AUTH_BACKEND = GoogleOAuth2Backend - AUTHORIZATION_URL = 'https://accounts.google.com/o/oauth2/auth' - ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' - REVOKE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/revoke' - REVOKE_TOKEN_METHOD = 'GET' - SETTINGS_KEY_NAME = _OAUTH2_KEY_NAME - SETTINGS_SECRET_NAME = 'GOOGLE_OAUTH2_CLIENT_SECRET' - SCOPE_VAR_NAME = 'GOOGLE_OAUTH_EXTRA_SCOPE' - DEFAULT_SCOPE = GOOGLE_OAUTH2_SCOPE - REDIRECT_STATE = False - - def user_data(self, access_token, *args, **kwargs): - """Return user data from Google API""" - return googleapis_profile(GOOGLEAPIS_PROFILE, access_token) - - @classmethod - def revoke_token_params(cls, token, uid): - return {'token': token} - - @classmethod - def revoke_token_headers(cls, token, uid): - return {'Content-type': 'application/json'} - - -def googleapis_email(url, params): - """Loads user data from googleapis service, only email so far as it's - described in http://sites.google.com/site/oauthgoog/Home/emaildisplayscope - - Parameters must be passed in queryset and Authorization header as described - on Google OAuth documentation at: - http://groups.google.com/group/oauth/browse_thread/thread/d15add9beb418ebc - and: http://code.google.com/apis/accounts/docs/OAuth2.html#CallingAnAPI - """ - request = Request(url + '?' + params, headers={'Authorization': params}) - try: - return simplejson.loads(dsa_urlopen(request).read())['data'] - except (ValueError, KeyError, IOError): - return None - - -def googleapis_profile(url, access_token): - """ - Loads user data from googleapis service, such as name, given_name, - family_name, etc. as it's described in: - https://developers.google.com/accounts/docs/OAuth2Login - """ - data = {'access_token': access_token, 'alt': 'json'} - request = Request(url + '?' + urlencode(data)) - try: - return simplejson.loads(dsa_urlopen(request).read()) - except (ValueError, KeyError, IOError): - return None - - -def validate_whitelists(backend, email): - """ - Validates allowed domains and emails against the following settings: - GOOGLE_WHITE_LISTED_DOMAINS - GOOGLE_WHITE_LISTED_EMAILS - - All domains and emails are allowed if setting is an empty list. - """ - emails = setting('GOOGLE_WHITE_LISTED_EMAILS', []) - domains = setting('GOOGLE_WHITE_LISTED_DOMAINS', []) - if not emails and not domains: - return True - if email in set(emails): - return True # you're good - if email.split('@', 1)[1] in set(domains): - return True - raise AuthFailed(backend, 'User not allowed') - - -# Backend definition -BACKENDS = { - 'google': GoogleAuth, - 'google-oauth': GoogleOAuth, - 'google-oauth2': GoogleOAuth2, -} +from social.backends.google import GoogleOAuth2 as GoogleOAuth2Backend, \ + GoogleOAuth as GoogleOAuthBackend, \ + GoogleOpenId as GoogleBackend diff --git a/social_auth/backends/pipeline/associate.py b/social_auth/backends/pipeline/associate.py index fead77dc5..296e77e01 100644 --- a/social_auth/backends/pipeline/associate.py +++ b/social_auth/backends/pipeline/associate.py @@ -1,23 +1 @@ -from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist - -from social_auth.models import UserSocialAuth -from social_auth.exceptions import AuthException - - -def associate_by_email(details, user=None, *args, **kwargs): - """Return user entry with same email address as one returned on details.""" - if user: - return None - - email = details.get('email') - - if email: - # Try to associate accounts registered with the same email address, - # only if it's a single object. AuthException is raised if multiple - # objects are returned. - try: - return {'user': UserSocialAuth.get_user_by_email(email=email)} - except MultipleObjectsReturned: - raise AuthException(kwargs['backend'], 'Not unique email address.') - except ObjectDoesNotExist: - pass +from social.pipeline.social_auth import associate_by_email diff --git a/social_auth/backends/pipeline/misc.py b/social_auth/backends/pipeline/misc.py index 91ccdb246..e4c45808c 100644 --- a/social_auth/backends/pipeline/misc.py +++ b/social_auth/backends/pipeline/misc.py @@ -1,16 +1,2 @@ -from social_auth.backends import PIPELINE -from social_auth.utils import setting - - -def save_status_to_session(request, auth, pipeline_index, *args, **kwargs): - """Saves current social-auth status to session.""" - next_entry = setting('SOCIAL_AUTH_PIPELINE_RESUME_ENTRY') - - if next_entry and next_entry in PIPELINE: - idx = PIPELINE.index(next_entry) - else: - idx = pipeline_index + 1 - - data = auth.to_session_dict(idx, *args, **kwargs) - name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline') - request.session[name] = data +from social.pipeline.partial import save_status_to_session +save_status_to_session # placate pyflakes diff --git a/social_auth/backends/pipeline/sauth.py b/social_auth/backends/pipeline/sauth.py new file mode 100644 index 000000000..49ab7f983 --- /dev/null +++ b/social_auth/backends/pipeline/sauth.py @@ -0,0 +1,3 @@ +from social.pipeline.social_auth import social_user as social_auth_user, \ + associate_user, load_extra_data +social_auth_user, associate_user, load_extra_data # placate pyflakes diff --git a/social_auth/backends/pipeline/social.py b/social_auth/backends/pipeline/social.py deleted file mode 100644 index 5884c9569..000000000 --- a/social_auth/backends/pipeline/social.py +++ /dev/null @@ -1,66 +0,0 @@ -from django.utils.translation import ugettext - -from social_auth.models import UserSocialAuth, SOCIAL_AUTH_MODELS_MODULE -from social_auth.exceptions import AuthAlreadyAssociated - - -def social_auth_user(backend, uid, user=None, *args, **kwargs): - """Return UserSocialAuth account for backend/uid pair or None if it - doesn't exists. - - Raise AuthAlreadyAssociated if UserSocialAuth entry belongs to another - user. - """ - social_user = UserSocialAuth.get_social_auth(backend.name, uid) - if social_user: - if user and social_user.user != user: - msg = ugettext('This %(provider)s account is already in use.') - raise AuthAlreadyAssociated(backend, msg % { - 'provider': backend.name - }) - elif not user: - user = social_user.user - return {'social_user': social_user, - 'user': user, - 'new_association': False} - - -def associate_user(backend, user, uid, social_user=None, *args, **kwargs): - """Associate user social account with user instance.""" - if social_user or not user: - return None - - try: - social = UserSocialAuth.create_social_auth(user, uid, backend.name) - except Exception, e: - if not SOCIAL_AUTH_MODELS_MODULE.is_integrity_error(e): - raise - # Protect for possible race condition, those bastard with FTL - # clicking capabilities, check issue #131: - # https://github.com/omab/django-social-auth/issues/131 - return social_auth_user(backend, uid, user, social_user=social_user, - *args, **kwargs) - else: - return {'social_user': social, - 'user': social.user, - 'new_association': True} - - -def load_extra_data(backend, details, response, uid, user, social_user=None, - *args, **kwargs): - """Load extra data from provider and store it on current UserSocialAuth - extra_data field. - """ - social_user = social_user or \ - UserSocialAuth.get_social_auth(backend.name, uid) - if social_user: - extra_data = backend.extra_data(user, uid, response, details) - if kwargs.get('original_email') and not 'email' in extra_data: - extra_data['email'] = kwargs.get('original_email') - if extra_data and social_user.extra_data != extra_data: - if social_user.extra_data: - social_user.extra_data.update(extra_data) - else: - social_user.extra_data = extra_data - social_user.save() - return {'social_user': social_user} diff --git a/social_auth/backends/pipeline/user.py b/social_auth/backends/pipeline/user.py index 9a721b53e..473a423c6 100644 --- a/social_auth/backends/pipeline/user.py +++ b/social_auth/backends/pipeline/user.py @@ -1,134 +1,3 @@ -from uuid import uuid4 - -from social_auth.utils import setting, module_member -from social_auth.models import UserSocialAuth - - -slugify = module_member(setting('SOCIAL_AUTH_SLUGIFY_FUNCTION', - 'django.template.defaultfilters.slugify')) - - -def get_username(details, user=None, - user_exists=UserSocialAuth.simple_user_exists, - *args, **kwargs): - """Return an username for new user. Return current user username - if user was given. - """ - if user: - return {'username': UserSocialAuth.user_username(user)} - - email_as_username = setting('SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL', False) - uuid_length = setting('SOCIAL_AUTH_UUID_LENGTH', 16) - do_slugify = setting('SOCIAL_AUTH_SLUGIFY_USERNAMES', False) - - if email_as_username and details.get('email'): - username = details['email'] - elif details.get('username'): - username = unicode(details['username']) - else: - username = uuid4().get_hex() - - max_length = UserSocialAuth.username_max_length() - short_username = username[:max_length - uuid_length] - final_username = UserSocialAuth.clean_username(username[:max_length]) - if do_slugify: - final_username = slugify(final_username) - - # Generate a unique username for current user using username - # as base but adding a unique hash at the end. Original - # username is cut to avoid any field max_length. - while user_exists(username=final_username): - username = short_username + uuid4().get_hex()[:uuid_length] - username = username[:max_length] - final_username = UserSocialAuth.clean_username(username) - if do_slugify: - final_username = slugify(final_username) - return {'username': final_username} - - -def create_user(backend, details, response, uid, username, user=None, *args, - **kwargs): - """Create user. Depends on get_username pipeline.""" - if user: - return {'user': user} - if not username: - return None - - # Avoid hitting field max length - email = details.get('email') - original_email = None - if email and UserSocialAuth.email_max_length() < len(email): - original_email = email - email = '' - - return { - 'user': UserSocialAuth.create_user(username=username, email=email), - 'original_email': original_email, - 'is_new': True - } - - -def _ignore_field(name, is_new=False): - return name in ('username', 'id', 'pk') or \ - (not is_new and - name in setting('SOCIAL_AUTH_PROTECTED_USER_FIELDS', [])) - - -def mongoengine_orm_maxlength_truncate(backend, details, user=None, - is_new=False, *args, **kwargs): - """Truncate any value in details that corresponds with a field in the user - model. Add this entry to the pipeline before update_user_details""" - if user is None: - return - out = {} - names = list(user._fields.keys()) - for name, value in details.iteritems(): - if name in names and not _ignore_field(name, is_new): - max_length = user._fields[name].max_length - try: - if max_length and len(value) > max_length: - value = value[:max_length] - except TypeError: - pass - out[name] = value - return {'details': out} - - -def django_orm_maxlength_truncate(backend, details, user=None, is_new=False, - *args, **kwargs): - """Truncate any value in details that corresponds with a field in the user - model. Add this entry to the pipeline before update_user_details""" - if user is None: - return - out = {} - names = user._meta.get_all_field_names() - for name, value in details.iteritems(): - if name in names and not _ignore_field(name, is_new): - max_length = user._meta.get_field(name).max_length - try: - if max_length and len(value) > max_length: - value = value[:max_length] - except TypeError: - pass - out[name] = value - return {'details': out} - - -def update_user_details(backend, details, response, user=None, is_new=False, - *args, **kwargs): - """Update user details using data from provider.""" - if user is None: - return - - changed = False # flag to track changes - - for name, value in details.iteritems(): - # do not update username, it was already generated, do not update - # configured fields if user already existed - if not _ignore_field(name, is_new): - if value and value != getattr(user, name, None): - setattr(user, name, value) - changed = True - - if changed: - user.save() +from social.pipeline.user import get_username, create_user, \ + user_details as update_user_details +get_username, create_user, update_user_details # placate pyflakes diff --git a/social_auth/backends/reddit.py b/social_auth/backends/reddit.py index b9973b470..23ed4a697 100644 --- a/social_auth/backends/reddit.py +++ b/social_auth/backends/reddit.py @@ -1,76 +1 @@ -import base64 -from urllib2 import Request, HTTPError -from urllib import urlencode - -from django.utils import simplejson - -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.utils import dsa_urlopen -from social_auth.exceptions import AuthTokenError - - -class RedditBackend(OAuthBackend): - """Reddit OAuth2 authentication backend""" - name = 'reddit' - # Default extra data to store - EXTRA_DATA = [ - ('id', 'id'), - ('link_karma', 'link_karma'), - ('comment_karma', 'comment_karma'), - ('refresh_token', 'refresh_token'), - ('expires_in', 'expires') - ] - - def get_user_details(self, response): - """Return user details from reddit account""" - return {'username': response.get('name'), - 'email': '', 'fullname': '', - 'first_name': '', 'last_name': ''} - - -class RedditAuth(BaseOAuth2): - """Reddit OAuth2 support""" - REDIRECT_STATE = False - AUTH_BACKEND = RedditBackend - SCOPE_SEPARATOR = ',' - AUTHORIZATION_URL = 'https://ssl.reddit.com/api/v1/authorize' - ACCESS_TOKEN_URL = 'https://ssl.reddit.com/api/v1/access_token' - SETTINGS_KEY_NAME = 'REDDIT_APP_ID' - SETTINGS_SECRET_NAME = 'REDDIT_API_SECRET' - SCOPE_VAR_NAME = 'REDDIT_EXTENDED_PERMISSIONS' - DEFAULT_SCOPE = ['identity'] - - @classmethod - def refresh_token(cls, token, redirect_uri): - data = cls.refresh_token_params(token) - data['redirect_uri'] = redirect_uri - request = Request(cls.ACCESS_TOKEN_URL, - data=urlencode(data), - headers=cls.auth_headers()) - return cls.process_refresh_token_response(dsa_urlopen(request).read()) - - def user_data(self, access_token, *args, **kwargs): - """Grab user profile information from reddit.""" - try: - request = Request( - 'https://oauth.reddit.com/api/v1/me.json', - headers={'Authorization': 'bearer %s' % access_token} - ) - return simplejson.load(dsa_urlopen(request)) - except ValueError: - return None - except HTTPError: - raise AuthTokenError(self) - - @classmethod - def auth_headers(cls): - return { - 'Authorization': 'Basic %s' % base64.urlsafe_b64encode( - '%s:%s' % cls.get_key_and_secret() - ) - } - - -BACKENDS = { - 'reddit': RedditAuth -} +from social.backends.reddit import RedditOAuth2 as RedditBackend diff --git a/social_auth/backends/steam.py b/social_auth/backends/steam.py index 750dbecd9..6f2ddb51f 100644 --- a/social_auth/backends/steam.py +++ b/social_auth/backends/steam.py @@ -1,72 +1 @@ -"""Steam OpenId support""" -import re -import urllib -import urllib2 - -from django.utils import simplejson - -from social_auth.backends import OpenIdAuth, OpenIDBackend -from social_auth.exceptions import AuthFailed -from social_auth.utils import setting - - -STEAM_ID = re.compile('steamcommunity.com/openid/id/(.*?)$') -USER_INFO = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' - - -class SteamBackend(OpenIDBackend): - """Steam OpenId authentication backend""" - name = 'steam' - - def get_user_id(self, details, response): - """Return user unique id provided by service""" - return self._user_id(response) - - def get_user_details(self, response): - user_id = self._user_id(response) - url = USER_INFO + urllib.urlencode({'key': setting('STEAM_API_KEY'), - 'steamids': user_id}) - details = {} - try: - player = simplejson.load(urllib2.urlopen(url)) - except (ValueError, IOError): - pass - else: - if len(player['response']['players']) > 0: - player = player['response']['players'][0] - details = {'username': player.get('personaname'), - 'email': '', - 'fullname': '', - 'first_name': '', - 'last_name': '', - 'player': player} - return details - - def extra_data(self, user, uid, response, details): - return details['player'] - - def _user_id(self, response): - match = STEAM_ID.search(response.identity_url) - if match is None: - raise AuthFailed(self, 'Missing Steam Id') - return match.group(1) - - -class SteamAuth(OpenIdAuth): - """Steam OpenId authentication""" - AUTH_BACKEND = SteamBackend - - def openid_url(self): - """Return Steam OpenId service url""" - return 'http://steamcommunity.com/openid' - - @classmethod - def enabled(cls): - """Steam OpenId is enabled when STEAM_API_KEY is defined""" - return setting('STEAM_API_KEY') is not None - - -# Backend definition -BACKENDS = { - 'steam': SteamAuth -} +from social.backends.steam import SteamOpenId as SteamBackend diff --git a/social_auth/backends/stripe.py b/social_auth/backends/stripe.py index b56f5808e..f44b290aa 100644 --- a/social_auth/backends/stripe.py +++ b/social_auth/backends/stripe.py @@ -1,87 +1 @@ -""" -Stripe OAuth2 support. - -This backend adds support for Stripe OAuth2 service. The settings -STRIPE_APP_ID and STRIPE_API_SECRET must be defined with the values -given by Stripe application registration process. -""" -from social_auth.backends import BaseOAuth2, OAuthBackend -from social_auth.exceptions import AuthFailed, AuthCanceled - - -class StripeBackend(OAuthBackend): - """Stripe OAuth2 authentication backend""" - name = 'stripe' - ID_KEY = 'stripe_user_id' - EXTRA_DATA = [ - ('stripe_publishable_key', 'stripe_publishable_key'), - ('access_token', 'access_token'), - ('livemode', 'livemode'), - ('token_type', 'token_type'), - ('refresh_token', 'refresh_token'), - ('stripe_user_id', 'stripe_user_id'), - ] - - def get_user_details(self, response): - """Return user details from Stripe account""" - return {'username': response.get('stripe_user_id'), - 'email': ''} - - -class StripeAuth(BaseOAuth2): - """Facebook OAuth2 support""" - AUTH_BACKEND = StripeBackend - AUTHORIZATION_URL = 'https://connect.stripe.com/oauth/authorize' - ACCESS_TOKEN_URL = 'https://connect.stripe.com/oauth/token' - SCOPE_VAR_NAME = 'STRIPE_SCOPE' - SETTINGS_KEY_NAME = 'STRIPE_APP_ID' - SETTINGS_SECRET_NAME = 'STRIPE_APP_SECRET' - REDIRECT_STATE = False - - def process_error(self, data): - if self.data.get('error'): - error = self.data.get('error_description') or self.data['error'] - if self.data['error'] == 'access_denied': - raise AuthCanceled(self, error) - else: - raise AuthFailed(self, error) - - def auth_params(self, state=None): - client_id, client_secret = self.get_key_and_secret() - params = { - 'response_type': self.RESPONSE_TYPE, - 'client_id': client_id, - } - if state: - params['state'] = state - return params - - def auth_complete_params(self, state=None): - client_id, client_secret = self.get_key_and_secret() - return { - 'grant_type': 'authorization_code', - 'client_id': client_id, - 'scope': self.SCOPE_SEPARATOR.join(self.get_scope()), - 'code': self.data['code'] - } - - @classmethod - def auth_headers(cls): - client_id, client_secret = cls.get_key_and_secret() - return { - 'Accept': 'application/json', - 'Authorization': 'Bearer %s' % client_secret - } - - @classmethod - def refresh_token_params(cls, refresh_token): - return { - 'refresh_token': refresh_token, - 'grant_type': 'refresh_token' - } - - -# Backend definition -BACKENDS = { - 'stripe': StripeAuth -} +from social.backends.stripe import StripeOAuth2 as StripeBackend diff --git a/social_auth/backends/twitter.py b/social_auth/backends/twitter.py index 36f9677ef..5e20c8bb6 100644 --- a/social_auth/backends/twitter.py +++ b/social_auth/backends/twitter.py @@ -1,93 +1 @@ -""" -Twitter OAuth support. - -This adds support for Twitter OAuth service. An application must -be registered first on twitter and the settings TWITTER_CONSUMER_KEY -and TWITTER_CONSUMER_SECRET must be defined with the corresponding -values. - -User screen name is used to generate username. - -By default account id is stored in extra_data field, check OAuthBackend -class for details on how to extend it. -""" -from django.utils import simplejson - -from social_auth.backends import ConsumerBasedOAuth, OAuthBackend -from social_auth.exceptions import AuthCanceled - - -# Twitter configuration -TWITTER_SERVER = 'api.twitter.com' -TWITTER_REQUEST_TOKEN_URL = 'https://%s/oauth/request_token' % TWITTER_SERVER -TWITTER_ACCESS_TOKEN_URL = 'https://%s/oauth/access_token' % TWITTER_SERVER -# Note: oauth/authorize forces the user to authorize every time. -# oauth/authenticate uses their previous selection, barring revocation. -TWITTER_AUTHORIZATION_URL = 'https://%s/oauth/authenticate' % TWITTER_SERVER -TWITTER_CHECK_AUTH = 'https://%s/1.1/account/verify_credentials.json' % \ - TWITTER_SERVER - - -class TwitterBackend(OAuthBackend): - """Twitter OAuth authentication backend""" - name = 'twitter' - EXTRA_DATA = [('id', 'id')] - - def get_user_details(self, response): - """Return user details from Twitter account""" - try: - first_name, last_name = response['name'].split(' ', 1) - except: - first_name = response['name'] - last_name = '' - return {'username': response['screen_name'], - 'email': '', # not supplied - 'fullname': response['name'], - 'first_name': first_name, - 'last_name': last_name} - - @classmethod - def tokens(cls, instance): - """Return the tokens needed to authenticate the access to any API the - service might provide. Twitter uses a pair of OAuthToken consisting of - an oauth_token and oauth_token_secret. - - instance must be a UserSocialAuth instance. - """ - token = super(TwitterBackend, cls).tokens(instance) - if token and 'access_token' in token: - token = dict(tok.split('=') - for tok in token['access_token'].split('&')) - return token - - -class TwitterAuth(ConsumerBasedOAuth): - """Twitter OAuth authentication mechanism""" - AUTHORIZATION_URL = TWITTER_AUTHORIZATION_URL - REQUEST_TOKEN_URL = TWITTER_REQUEST_TOKEN_URL - ACCESS_TOKEN_URL = TWITTER_ACCESS_TOKEN_URL - AUTH_BACKEND = TwitterBackend - SETTINGS_KEY_NAME = 'TWITTER_CONSUMER_KEY' - SETTINGS_SECRET_NAME = 'TWITTER_CONSUMER_SECRET' - - def user_data(self, access_token, *args, **kwargs): - """Return user data provided""" - request = self.oauth_request(access_token, TWITTER_CHECK_AUTH) - json = self.fetch_response(request) - try: - return simplejson.loads(json) - except ValueError: - return None - - def auth_complete(self, *args, **kwargs): - """Completes login process, must return user instance""" - if 'denied' in self.data: - raise AuthCanceled(self) - else: - return super(TwitterAuth, self).auth_complete(*args, **kwargs) - - -# Backend definition -BACKENDS = { - 'twitter': TwitterAuth, -} +from social.backends.twitter import TwitterOAuth as TwitterBackend diff --git a/social_auth/backends/utils.py b/social_auth/backends/utils.py deleted file mode 100644 index 6b8c36f52..000000000 --- a/social_auth/backends/utils.py +++ /dev/null @@ -1,42 +0,0 @@ -from oauth2 import Consumer as OAuthConsumer, Token, Request as OAuthRequest, \ - SignatureMethod_HMAC_SHA1, HTTP_METHOD - -from django.utils import simplejson - -from social_auth.models import UserSocialAuth -from social_auth.utils import dsa_urlopen - - -def consumer_oauth_url_request(backend, url, user_or_id, redirect_uri='/', - json=True): - """Builds and retrieves an OAuth signed response.""" - user = UserSocialAuth.resolve_user_or_id(user_or_id) - oauth_info = user.social_auth.filter(provider=backend.AUTH_BACKEND.name)[0] - token = Token.from_string(oauth_info.tokens['access_token']) - request = build_consumer_oauth_request(backend, token, url, redirect_uri) - response = '\n'.join(dsa_urlopen(request.to_url()).readlines()) - - if json: - response = simplejson.loads(response) - return response - - -def build_consumer_oauth_request(backend, token, url, redirect_uri='/', - oauth_verifier=None, extra_params=None, - method=HTTP_METHOD): - """Builds a Consumer OAuth request.""" - params = {'oauth_callback': redirect_uri} - if extra_params: - params.update(extra_params) - - if oauth_verifier: - params['oauth_verifier'] = oauth_verifier - - consumer = OAuthConsumer(*backend.get_key_and_secret()) - request = OAuthRequest.from_consumer_and_token(consumer, - token=token, - http_method=method, - http_url=url, - parameters=params) - request.sign_request(SignatureMethod_HMAC_SHA1(), consumer, token) - return request diff --git a/social_auth/backends/yahoo.py b/social_auth/backends/yahoo.py index 65ff401a4..5055627b1 100644 --- a/social_auth/backends/yahoo.py +++ b/social_auth/backends/yahoo.py @@ -1,29 +1 @@ -""" -Yahoo OpenID support - -No extra configurations are needed to make this work. -""" -from social_auth.backends import OpenIDBackend, OpenIdAuth - - -YAHOO_OPENID_URL = 'http://me.yahoo.com' - - -class YahooBackend(OpenIDBackend): - """Yahoo OpenID authentication backend""" - name = 'yahoo' - - -class YahooAuth(OpenIdAuth): - """Yahoo OpenID authentication""" - AUTH_BACKEND = YahooBackend - - def openid_url(self): - """Return Yahoo OpenID service url""" - return YAHOO_OPENID_URL - - -# Backend definition -BACKENDS = { - 'yahoo': YahooAuth, -} +from social.backends.yahoo import YahooOpenId as YahooBackend diff --git a/social_auth/context_processors.py b/social_auth/context_processors.py index cbb2d9d66..543a54575 100644 --- a/social_auth/context_processors.py +++ b/social_auth/context_processors.py @@ -1,8 +1,12 @@ -from django.contrib.auth import REDIRECT_FIELD_NAME +from collections import defaultdict + +from social.apps.django_app.context_processors import login_redirect, \ + backends, LazyDict +from social.backends.oauth import BaseOAuth1, BaseOAuth2 +from social.backends.open_id import OpenIdAuth from social_auth.models import UserSocialAuth from social_auth.backends import get_backends -from social_auth.utils import group_backend_by_type, LazyDict # Note: social_auth_backends, social_auth_by_type_backends and # social_auth_by_name_backends don't play nice together. @@ -12,9 +16,7 @@ def social_auth_backends(request): """Load Social Auth current user data to context. Will add a output from backends_data to context under social_auth key. """ - def context_value(): - return backends_data(request.user) - return {'social_auth': LazyDict(context_value)} + return {'social_auth': backends(request)} def social_auth_by_type_backends(request): @@ -23,7 +25,7 @@ def social_auth_by_type_backends(request): each entry will be grouped by backend type (openid, oauth, oauth2). """ def context_value(): - data = backends_data(request.user) + data = dict(backends(request)['backends']) data['backends'] = group_backend_by_type(data['backends']) data['not_associated'] = group_backend_by_type(data['not_associated']) data['associated'] = group_backend_by_type( @@ -53,44 +55,24 @@ def context_value(): return {'social_auth': LazyDict(context_value)} -def backends_data(user): - """Return backends data for given user. - - Will return a dict with values: - associated: UserSocialAuth model instances for currently - associated accounts - not_associated: Not associated (yet) backend names. - backends: All backend names. - - If user is not authenticated, then first list is empty, and there's no - difference between the second and third lists. - """ - available = get_backends().keys() - values = {'associated': [], - 'not_associated': available, - 'backends': available} - - # user comes from request.user usually, on /admin/ it will be an instance - # of auth.User and this code will fail if a custom User model was defined - if hasattr(user, 'is_authenticated') and user.is_authenticated(): - associated = UserSocialAuth.get_social_auth_for_user(user) - not_associated = list(set(available) - - set(assoc.provider for assoc in associated)) - values['associated'] = associated - values['not_associated'] = not_associated - return values - - def social_auth_login_redirect(request): """Load current redirect to context.""" - redirect_value = request.REQUEST.get(REDIRECT_FIELD_NAME) - if redirect_value: - redirect_querystring = REDIRECT_FIELD_NAME + '=' + redirect_value - else: - redirect_querystring = '' - - return { - 'REDIRECT_FIELD_NAME': REDIRECT_FIELD_NAME, - 'REDIRECT_FIELD_VALUE': redirect_value, - 'redirect_querystring': redirect_querystring - } + data = login_redirect(request) + data['redirect_querystring'] = data.get('REDIRECT_QUERYSTRING') + return data + + +def group_backend_by_type(items, key=lambda x: x): + """Group items by backend type.""" + result = defaultdict(list) + backends_defined = get_backends() + + for item in items: + backend = backends_defined[key(item)] + if issubclass(backend, OpenIdAuth): + result['openid'].append(item) + elif issubclass(backend, BaseOAuth2): + result['oauth2'].append(item) + elif issubclass(backend, BaseOAuth1): + result['oauth'].append(item) + return dict(result) diff --git a/social_auth/db/__init__.py b/social_auth/db/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/social_auth/db/base.py b/social_auth/db/base.py deleted file mode 100644 index 95dfe7720..000000000 --- a/social_auth/db/base.py +++ /dev/null @@ -1,267 +0,0 @@ -"""Models mixins for Social Auth""" -import base64 -import time -import re -from datetime import datetime, timedelta - -from openid.association import Association as OIDAssociation - - -# django.contrib.auth and mongoengine.django.auth regex to validate usernames -# '^[\w@.+-_]+$', we use the opposite to clean invalid characters -CLEAN_USERNAME_REGEX = re.compile(r'[^\w.@+-_]+', re.UNICODE) - - -class UserSocialAuthMixin(object): - user = '' - provider = '' - - def __unicode__(self): - """Return associated user unicode representation""" - return u'%s - %s' % (unicode(self.user), self.provider.title()) - - def get_backend(self): - # Make import here to avoid recursive imports :-/ - from social_auth.backends import get_backends - return get_backends().get(self.provider) - - @property - def tokens(self): - """Return access_token stored in extra_data or None""" - backend = self.get_backend() - if backend: - return backend.AUTH_BACKEND.tokens(self) - else: - return {} - - def revoke_token(self, drop_token=True): - """Attempts to revoke permissions for provider.""" - if 'access_token' in self.tokens: - success = self.get_backend().revoke_token( - self.tokens['access_token'], - self.uid - ) - if success and drop_token: - self.extra_data.pop('access_token', None) - self.save() - - def refresh_token(self): - data = self.extra_data - if 'refresh_token' in data or 'access_token' in data: - backend = self.get_backend() - if hasattr(backend, 'refresh_token'): - token = data.get('refresh_token') or data.get('access_token') - response = backend.refresh_token(token) - self.extra_data.update( - backend.AUTH_BACKEND.extra_data(self.user, self.uid, - response) - ) - self.save() - - def expiration_datetime(self): - """Return provider session live seconds. Returns a timedelta ready to - use with session.set_expiry(). - - If provider returns a timestamp instead of session seconds to live, the - timedelta is inferred from current time (using UTC timezone). None is - returned if there's no value stored or it's invalid. - """ - if self.extra_data and 'expires' in self.extra_data: - try: - expires = int(self.extra_data['expires']) - except (ValueError, TypeError): - return None - - now = datetime.utcnow() - - # Detect if expires is a timestamp - if expires > time.mktime(now.timetuple()): - # expires is a datetime - return datetime.fromtimestamp(expires) - now - else: - # expires is a timedelta - return timedelta(seconds=expires) - - @classmethod - def user_model(cls): - raise NotImplementedError('Implement in subclass') - - @classmethod - def username_max_length(cls): - raise NotImplementedError('Implement in subclass') - - @classmethod - def email_max_length(cls): - raise NotImplementedError('Implement in subclass') - - @classmethod - def clean_username(cls, value): - return CLEAN_USERNAME_REGEX.sub('', value) - - @classmethod - def allowed_to_disconnect(cls, user, backend_name, association_id=None): - if association_id is not None: - qs = cls.objects.exclude(id=association_id) - else: - qs = cls.objects.exclude(provider=backend_name) - qs = qs.filter(user=user) - - if hasattr(user, 'has_usable_password'): - valid_password = user.has_usable_password() - else: - valid_password = True - - return valid_password or qs.count() > 0 - - @classmethod - def user_username(cls, user): - if hasattr(user, 'USERNAME_FIELD'): - # Django 1.5 custom user model, 'username' is just for internal - # use, doesn't imply that the model should have an username field - field_name = user.USERNAME_FIELD - else: - field_name = 'username' - return getattr(user, field_name) - - @classmethod - def username_field(cls, values): - user_model = cls.user_model() - if hasattr(user_model, 'USERNAME_FIELD'): - # Django 1.5 custom user model, 'username' is just for internal - # use, doesn't imply that the model should have an username field - values[user_model.USERNAME_FIELD] = values.pop('username') - return values - - @classmethod - def simple_user_exists(cls, *args, **kwargs): - """ - Return True/False if a User instance exists with the given arguments. - Arguments are directly passed to filter() manager method. - TODO: consider how to ensure case-insensitive email matching - """ - kwargs = cls.username_field(kwargs) - # Use count() > 0 since mongoengine doesn't support .exists(), - # Check issue #728 - return cls.user_model().objects.filter(*args, **kwargs).count() > 0 - - @classmethod - def create_user(cls, *args, **kwargs): - kwargs = cls.username_field(kwargs) - return cls.user_model().objects.create_user(*args, **kwargs) - - @classmethod - def get_user(cls, pk): - try: - return cls.user_model().objects.get(pk=pk) - except cls.user_model().DoesNotExist: - return None - - @classmethod - def get_user_by_email(cls, email): - """Case insensitive search""" - # Do case-insensitive match, since real-world email address is - # case-insensitive. - return cls.user_model().objects.get(email__iexact=email) - - @classmethod - def resolve_user_or_id(cls, user_or_id): - if isinstance(user_or_id, cls.user_model()): - return user_or_id - return cls.user_model().objects.get(pk=user_or_id) - - @classmethod - def get_social_auth(cls, provider, uid): - if not isinstance(uid, basestring): - uid = str(uid) - try: - return cls.objects.get(provider=provider, uid=uid) - except cls.DoesNotExist: - return None - - @classmethod - def get_social_auth_for_user(cls, user): - return user.social_auth.all() - - @classmethod - def create_social_auth(cls, user, uid, provider): - if not isinstance(uid, basestring): - uid = str(uid) - return cls.objects.create(user=user, uid=uid, provider=provider) - - @classmethod - def store_association(cls, server_url, association): - from social_auth.models import Association - args = {'server_url': server_url, 'handle': association.handle} - try: - assoc = Association.objects.get(**args) - except Association.DoesNotExist: - assoc = Association(**args) - assoc.secret = base64.encodestring(association.secret) - assoc.issued = association.issued - assoc.lifetime = association.lifetime - assoc.assoc_type = association.assoc_type - assoc.save() - - @classmethod - def remove_association(cls, server_url, handle): - from social_auth.models import Association - assocs = list(Association.objects.filter( - server_url=server_url, handle=handle)) - assocs_exist = len(assocs) > 0 - for assoc in assocs: - assoc.delete() - return assocs_exist - - @classmethod - def get_oid_associations(cls, server_url, handle=None): - from social_auth.models import Association - args = {'server_url': server_url} - if handle is not None: - args['handle'] = handle - - return sorted([ - (assoc.id, - OIDAssociation(assoc.handle, - base64.decodestring(assoc.secret), - assoc.issued, - assoc.lifetime, - assoc.assoc_type)) - for assoc in Association.objects.filter(**args) - ], key=lambda x: x[1].issued, reverse=True) - - @classmethod - def delete_associations(cls, ids_to_delete): - from social_auth.models import Association - Association.objects.filter(pk__in=ids_to_delete).delete() - - @classmethod - def use_nonce(cls, server_url, timestamp, salt): - from social_auth.models import Nonce - return Nonce.objects.get_or_create(server_url=server_url, - timestamp=timestamp, - salt=salt)[1] - - -class NonceMixin(object): - """One use numbers""" - server_url = '' - timestamp = 0 - salt = '' - - def __unicode__(self): - """Unicode representation""" - return self.server_url - - -class AssociationMixin(object): - """OpenId account association""" - server_url = '' - handle = '' - secret = '' - issued = 0 - lifetime = 0 - assoc_type = '' - - def __unicode__(self): - """Unicode representation""" - return '%s %s' % (self.handle, self.issued) diff --git a/social_auth/db/django_models.py b/social_auth/db/django_models.py deleted file mode 100644 index 194a290cc..000000000 --- a/social_auth/db/django_models.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Django ORM models for Social Auth""" -from django.db import models -from django.db.models.loading import get_model -from django.db.utils import IntegrityError - -from social_auth.db.base import UserSocialAuthMixin, AssociationMixin, \ - NonceMixin -from social_auth.fields import JSONField -from social_auth.utils import setting - - -# If User class is overridden, it *must* provide the following fields -# and methods work with django-social-auth: -# -# username = CharField() -# last_login = DateTimeField() -# is_active = BooleanField() -# def is_authenticated(): -# ... -USER_MODEL = setting('SOCIAL_AUTH_USER_MODEL') or \ - setting('AUTH_USER_MODEL') or \ - 'auth.User' -UID_LENGTH = setting('SOCIAL_AUTH_UID_LENGTH', 255) -NONCE_SERVER_URL_LENGTH = setting('SOCIAL_AUTH_NONCE_SERVER_URL_LENGTH', 255) -ASSOCIATION_SERVER_URL_LENGTH = setting( - 'SOCIAL_AUTH_ASSOCIATION_SERVER_URL_LENGTH', - 255 -) -ASSOCIATION_HANDLE_LENGTH = setting( - 'SOCIAL_AUTH_ASSOCIATION_HANDLE_LENGTH', - 255 -) - - -class UserSocialAuth(models.Model, UserSocialAuthMixin): - """Social Auth association model""" - user = models.ForeignKey(USER_MODEL, related_name='social_auth') - provider = models.CharField(max_length=32) - uid = models.CharField(max_length=UID_LENGTH) - extra_data = JSONField(default='{}') - - class Meta: - """Meta data""" - unique_together = ('provider', 'uid') - app_label = 'social_auth' - - @classmethod - def get_social_auth(cls, provider, uid): - try: - return cls.objects.select_related('user').get(provider=provider, - uid=uid) - except UserSocialAuth.DoesNotExist: - return None - - @classmethod - def username_max_length(cls): - return cls._field_length('USERNAME_FIELD', 'username') - - @classmethod - def email_max_length(cls): - return cls._field_length('EMAIL_FIELD', 'email') - - @classmethod - def _field_length(self, setting_name, default_name): - model = UserSocialAuth.user_model() - field_name = getattr(model, setting_name, default_name) - return model._meta.get_field(field_name).max_length - - @classmethod - def user_model(cls): - return get_model(*USER_MODEL.split('.')) - - -class Nonce(models.Model, NonceMixin): - """One use numbers""" - server_url = models.CharField(max_length=NONCE_SERVER_URL_LENGTH) - timestamp = models.IntegerField(db_index=True) - salt = models.CharField(max_length=40) - - class Meta: - app_label = 'social_auth' - unique_together = ('server_url', 'timestamp', 'salt') - - -class Association(models.Model, AssociationMixin): - """OpenId account association""" - server_url = models.CharField(max_length=ASSOCIATION_SERVER_URL_LENGTH) - handle = models.CharField(max_length=ASSOCIATION_HANDLE_LENGTH) - secret = models.CharField(max_length=255) # Stored base64 encoded - issued = models.IntegerField(db_index=True) - lifetime = models.IntegerField() - assoc_type = models.CharField(max_length=64) - - class Meta: - app_label = 'social_auth' - unique_together = ('server_url', 'handle') - - -def is_integrity_error(exc): - return exc.__class__ is IntegrityError diff --git a/social_auth/db/mongoengine_models.py b/social_auth/db/mongoengine_models.py deleted file mode 100644 index 4fac1f305..000000000 --- a/social_auth/db/mongoengine_models.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -MongoEngine models for Social Auth - -Requires MongoEngine 0.6.10 -""" -try: - from django.contrib.auth.hashers import UNUSABLE_PASSWORD - _ = UNUSABLE_PASSWORD # to quiet flake -except (ImportError, AttributeError): - UNUSABLE_PASSWORD = '!' - -from django.db.models import get_model -from django.utils.importlib import import_module - -from mongoengine import DictField, Document, IntField, ReferenceField, \ - StringField -from mongoengine.queryset import OperationError - -from social_auth.utils import setting -from social_auth.db.base import UserSocialAuthMixin, AssociationMixin, \ - NonceMixin - - -USER_MODEL_APP = setting('SOCIAL_AUTH_USER_MODEL') or \ - setting('AUTH_USER_MODEL') - -if USER_MODEL_APP: - USER_MODEL = get_model(*USER_MODEL_APP.rsplit('.', 1)) -else: - USER_MODEL_MODULE, USER_MODEL_NAME = \ - 'mongoengine.django.auth.User'.rsplit('.', 1) - USER_MODEL = getattr(import_module(USER_MODEL_MODULE), USER_MODEL_NAME) - - -class UserSocialAuth(Document, UserSocialAuthMixin): - """Social Auth association model""" - user = ReferenceField(USER_MODEL, dbref=True) - provider = StringField(max_length=32) - uid = StringField(max_length=255, unique_with='provider') - extra_data = DictField() - - @classmethod - def get_social_auth_for_user(cls, user): - return cls.objects(user=user) - - @classmethod - def create_social_auth(cls, user, uid, provider): - if not isinstance(type(uid), basestring): - uid = str(uid) - return cls.objects.create(user=user, uid=uid, provider=provider) - - @classmethod - def username_max_length(cls): - return UserSocialAuth.user_model().username.max_length - - @classmethod - def email_max_length(cls): - return UserSocialAuth.user_model().email.max_length - - @classmethod - def user_model(cls): - return USER_MODEL - - @classmethod - def create_user(cls, *args, **kwargs): - # Empty string makes email regex validation fail - if kwargs.get('email') == '': - kwargs['email'] = None - kwargs.setdefault('password', UNUSABLE_PASSWORD) - return cls.user_model().create_user(*args, **kwargs) - - @classmethod - def allowed_to_disconnect(cls, user, backend_name, association_id=None): - if association_id is not None: - qs = cls.objects.filter(id__ne=association_id) - else: - qs = cls.objects.filter(provider__ne=backend_name) - qs = qs.filter(user=user) - - if hasattr(user, 'has_usable_password'): - valid_password = user.has_usable_password() - else: - valid_password = True - - return valid_password or qs.count() > 0 - - -class Nonce(Document, NonceMixin): - """One use numbers""" - server_url = StringField(max_length=255) - timestamp = IntField() - salt = StringField(max_length=40) - - -class Association(Document, AssociationMixin): - """OpenId account association""" - server_url = StringField(max_length=255) - handle = StringField(max_length=255) - secret = StringField(max_length=255) # Stored base64 encoded - issued = IntField() - lifetime = IntField() - assoc_type = StringField(max_length=64) - - -def is_integrity_error(exc): - return exc.__class__ is OperationError and 'E11000' in exc.message diff --git a/social_auth/decorators.py b/social_auth/decorators.py deleted file mode 100644 index 686a801bb..000000000 --- a/social_auth/decorators.py +++ /dev/null @@ -1,41 +0,0 @@ -from functools import wraps - -from django.core.urlresolvers import reverse -from django.views.decorators.http import require_POST -from django.views.decorators.csrf import csrf_protect - -from social_auth.backends import get_backend -from social_auth.exceptions import WrongBackend -from social_auth.utils import setting - - -def dsa_view(redirect_name=None): - """Decorate djangos-social-auth views. Will check and retrieve backend - or return HttpResponseServerError if backend is not found. - - redirect_name parameter is used to build redirect URL used by backend. - """ - def dec(func): - @wraps(func) - def wrapper(request, backend, *args, **kwargs): - if redirect_name: - redirect = reverse(redirect_name, args=(backend,)) - else: - redirect = request.path - request.social_auth_backend = get_backend(backend, request, - redirect) - if request.social_auth_backend is None: - raise WrongBackend(backend) - return func(request, request.social_auth_backend, *args, **kwargs) - return wrapper - return dec - - -def disconnect_view(func): - @wraps(func) - def wrapper(request, *args, **kwargs): - return func(request, *args, **kwargs) - - if setting('SOCIAL_AUTH_FORCE_POST_DISCONNECT'): - wrapper = require_POST(csrf_protect(wrapper)) - return wrapper diff --git a/social_auth/exceptions.py b/social_auth/exceptions.py index 3c335a4e2..cce76d99c 100644 --- a/social_auth/exceptions.py +++ b/social_auth/exceptions.py @@ -1,98 +1 @@ -from django.utils.translation import ugettext - - -class SocialAuthBaseException(ValueError): - """Base class for pipeline exceptions.""" - pass - - -class WrongBackend(SocialAuthBaseException): - def __init__(self, backend_name): - self.backend_name = backend_name - - def __unicode__(self): - return ugettext(u'Incorrect authentication service "%s"') % \ - self.backend_name - - -class NotAllowedToDisconnect(SocialAuthBaseException): - """User is not allowed to disconnect it's social account.""" - pass - - -class StopPipeline(SocialAuthBaseException): - """Stop pipeline process exception. - Raise this exception to stop the rest of the pipeline process. - """ - def __unicode__(self): - return ugettext(u'Stop pipeline') - - -class AuthException(SocialAuthBaseException): - """Auth process exception.""" - def __init__(self, backend, *args, **kwargs): - self.backend = backend - super(AuthException, self).__init__(*args, **kwargs) - - -class AuthFailed(AuthException): - """Auth process failed for some reason.""" - def __unicode__(self): - if self.message == 'access_denied': - return ugettext(u'Authentication process was cancelled') - else: - return ugettext(u'Authentication failed: %s') % \ - super(AuthFailed, self).__unicode__() - - -class AuthCanceled(AuthException): - """Auth process was canceled by user.""" - def __unicode__(self): - return ugettext(u'Authentication process canceled') - - -class AuthUnknownError(AuthException): - """Unknown auth process error.""" - def __unicode__(self): - err = u'An unknown error happened while authenticating %s' - return ugettext(err) % super(AuthUnknownError, self).__unicode__() - - -class AuthTokenError(AuthException): - """Auth token error.""" - def __unicode__(self): - msg = super(AuthTokenError, self).__unicode__() - return ugettext(u'Token error: %s') % msg - - -class AuthMissingParameter(AuthException): - """Missing parameter needed to start or complete the process.""" - def __init__(self, backend, parameter, *args, **kwargs): - self.parameter = parameter - super(AuthMissingParameter, self).__init__(backend, *args, **kwargs) - - def __unicode__(self): - return ugettext(u'Missing needed parameter %s') % self.parameter - - -class AuthStateMissing(AuthException): - """State parameter is incorrect.""" - def __unicode__(self): - return ugettext(u'Session value state missing.') - - -class AuthStateForbidden(AuthException): - """State parameter is incorrect.""" - def __unicode__(self): - return ugettext(u'Wrong state parameter given.') - - -class AuthAlreadyAssociated(AuthException): - """A different user has already associated the target social account""" - pass - - -class AuthTokenRevoked(AuthException): - """User revoked the access_token in the provider.""" - def __unicode__(self): - return ugettext(u'User revoke access to the token') +from social.exceptions import * diff --git a/social_auth/fields.py b/social_auth/fields.py deleted file mode 100644 index 695f32ed8..000000000 --- a/social_auth/fields.py +++ /dev/null @@ -1,58 +0,0 @@ -from django.core.exceptions import ValidationError -from django.db import models -from django.utils import simplejson -from django.utils.encoding import smart_unicode - - -class JSONField(models.TextField): - """Simple JSON field that stores python structures as JSON strings - on database. - """ - __metaclass__ = models.SubfieldBase - - def to_python(self, value): - """ - Convert the input JSON value into python structures, raises - django.core.exceptions.ValidationError if the data can't be converted. - """ - if self.blank and not value: - return None - if isinstance(value, basestring): - try: - return simplejson.loads(value) - except Exception, e: - raise ValidationError(str(e)) - else: - return value - - def validate(self, value, model_instance): - """Check value is a valid JSON string, raise ValidationError on - error.""" - if isinstance(value, basestring): - super(JSONField, self).validate(value, model_instance) - try: - simplejson.loads(value) - except Exception, e: - raise ValidationError(str(e)) - - def get_prep_value(self, value): - """Convert value to JSON string before save""" - try: - return simplejson.dumps(value) - except Exception, e: - raise ValidationError(str(e)) - - def value_to_string(self, obj): - """Return value from object converted to string properly""" - return smart_unicode(self.get_prep_value(self._get_val_from_obj(obj))) - - def value_from_object(self, obj): - """Return value dumped to string.""" - return self.get_prep_value(self._get_val_from_obj(obj)) - - -try: - from south.modelsinspector import add_introspection_rules - add_introspection_rules([], ["^social_auth\.fields\.JSONField"]) -except: - pass diff --git a/social_auth/locale/de/LC_MESSAGES/django.mo b/social_auth/locale/de/LC_MESSAGES/django.mo deleted file mode 100644 index b75e0d8d8..000000000 Binary files a/social_auth/locale/de/LC_MESSAGES/django.mo and /dev/null differ diff --git a/social_auth/locale/de/LC_MESSAGES/django.po b/social_auth/locale/de/LC_MESSAGES/django.po deleted file mode 100644 index bde278791..000000000 --- a/social_auth/locale/de/LC_MESSAGES/django.po +++ /dev/null @@ -1,46 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-07-10 16:01+0200\n" -"PO-Revision-Date: 2013-07-10 16:03+0100\n" -"Last-Translator: Stephan Jaekel \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.5.5\n" - -#: exceptions.py:14 -#, python-format -msgid "Incorrect authentication service \"%s\"" -msgstr "Unbekannter Anmeldedienst \"%s\"" - -#: exceptions.py:42 -msgid "Authentication process was cancelled" -msgstr "Anmeldevorgang wurde abgebrochen" - -#: exceptions.py:44 -#, python-format -msgid "Authentication failed: %s" -msgstr "Anmeldevorgang fehlgeschlagen: %s" - -#: exceptions.py:51 -msgid "Authentication process canceled" -msgstr "Anmeldevorgang wurde abgebrochen" - -#: exceptions.py:65 -#, python-format -msgid "Token error: %s" -msgstr "Token Fehler: %s" - -#: backends/pipeline/social.py:17 -#, python-format -msgid "This %(provider)s account is already in use." -msgstr "Dieser %(provider)s Account wird bereits verwendet." diff --git a/social_auth/locale/ru/LC_MESSAGES/django.mo b/social_auth/locale/ru/LC_MESSAGES/django.mo deleted file mode 100644 index 333556395..000000000 Binary files a/social_auth/locale/ru/LC_MESSAGES/django.mo and /dev/null differ diff --git a/social_auth/locale/ru/LC_MESSAGES/django.po b/social_auth/locale/ru/LC_MESSAGES/django.po deleted file mode 100644 index 5be690606..000000000 --- a/social_auth/locale/ru/LC_MESSAGES/django.po +++ /dev/null @@ -1,38 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-02-17 15:25+0400\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" - -#: views.py:71 -msgid "Unknown authentication error. Try again later." -msgstr "Непредвиденная ошибка авторизации. Попробуйте позже." - -#: backends/__init__.py:635 backends/__init__.py:656 -msgid "Authentication process was cancelled" -msgstr "Процесс авторизации был прерван" - -#: backends/__init__.py:637 backends/__init__.py:658 -#, python-format -msgid "Authentication failed: %s" -msgstr "Ошибка авторизации: %s" - -#: backends/pipeline/social.py:25 -#, python-format -msgid "This %(provider)s account already in use." -msgstr "Этот аккаунт %(provider)s уже используется." diff --git a/social_auth/locale/tr/LC_MESSAGES/django.mo b/social_auth/locale/tr/LC_MESSAGES/django.mo deleted file mode 100644 index 7bc520fd9..000000000 Binary files a/social_auth/locale/tr/LC_MESSAGES/django.mo and /dev/null differ diff --git a/social_auth/locale/tr/LC_MESSAGES/django.po b/social_auth/locale/tr/LC_MESSAGES/django.po deleted file mode 100644 index d179148fb..000000000 --- a/social_auth/locale/tr/LC_MESSAGES/django.po +++ /dev/null @@ -1,33 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-15 18:58+0300\n" -"PO-Revision-Date: 2012-05-15 22:02+0200\n" -"Last-Translator: Cihan Okyay \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0\n" - -#: backends/exceptions.py:29 -msgid "Authentication process was cancelled" -msgstr "Kimlik denetimi işlemi kapatıldı" - -#: backends/exceptions.py:31 -#, python-format -msgid "Authentication failed: %s" -msgstr "Kimlik denetimi başarısız: %s" - -#: backends/pipeline/social.py:25 -#, python-format -msgid "This %(provider)s account already in use." -msgstr "Bu %(provider)s hesabı kullanımda." - diff --git a/social_auth/management/__init__.py b/social_auth/management/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/social_auth/management/commands/__init__.py b/social_auth/management/commands/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/social_auth/management/commands/clean_associations.py b/social_auth/management/commands/clean_associations.py deleted file mode 100644 index bc0ab338e..000000000 --- a/social_auth/management/commands/clean_associations.py +++ /dev/null @@ -1,32 +0,0 @@ -import time -import base64 - -from openid.server.server import Signatory -from openid.association import Association as OIDAssociation - -from django.core.management.base import BaseCommand - - -class Command(BaseCommand): - help = 'Clear expired Associations instances from db' - - def handle(self, *args, **options): - from social_auth.models import Association - print 'Clearing expired Association instances' - timestamp = time.time() + Signatory.SECRET_LIFETIME - associations = Association.objects.filter(issued__lt=timestamp) - remove = [] - - for assoc in associations: - oid = OIDAssociation(assoc.handle, - base64.decodestring(assoc.secret), - assoc.issued, - assoc.lifetime, - assoc.assoc_type) - if oid.getExpiresIn() == 0: - remove.append(assoc.pk) - if remove: - print 'Cleaning %s Associations' % len(remove) - Association.filter(pk__in=remove).delete() - else: - print 'No Associations to remove' diff --git a/social_auth/management/commands/clean_nonces.py b/social_auth/management/commands/clean_nonces.py deleted file mode 100644 index 751d825b8..000000000 --- a/social_auth/management/commands/clean_nonces.py +++ /dev/null @@ -1,20 +0,0 @@ -import time - -from openid.store.nonce import SKEW - -from django.core.management.base import BaseCommand - - -class Command(BaseCommand): - help = 'Clear expired Nonce instances from db' - - def handle(self, *args, **options): - from social_auth.models import Nonce - print 'Clearing expired Nonce instances' - qs = Nonce.objects.filter(timestamp__lt=(time.time() + SKEW)) - count = qs.count() - if count > 0: - print 'Cleaning %s Nonces' % qs.count() - qs.delete() - else: - print 'No Nonces to remove' diff --git a/social_auth/middleware.py b/social_auth/middleware.py index 6ad33e70a..5532676a4 100644 --- a/social_auth/middleware.py +++ b/social_auth/middleware.py @@ -1,60 +1,2 @@ -# -*- coding: utf-8 -*- -from django.conf import settings -from django.contrib.messages.api import error, MessageFailure -from django.shortcuts import redirect - -from social_auth.exceptions import SocialAuthBaseException -from social_auth.utils import backend_setting, get_backend_name - - -class SocialAuthExceptionMiddleware(object): - """Middleware that handles Social Auth AuthExceptions by providing the user - with a message, logging an error, and redirecting to some next location. - - By default, the exception message itself is sent to the user and they are - redirected to the location specified in the LOGIN_ERROR_URL setting. - - This middleware can be extended by overriding the get_message or - get_redirect_uri methods, which each accept request and exception. - """ - def process_exception(self, request, exception): - self.backend = self.get_backend(request, exception) - if self.raise_exception(request, exception): - return - - if isinstance(exception, SocialAuthBaseException): - backend_name = get_backend_name(self.backend) - message = self.get_message(request, exception) - url = self.get_redirect_uri(request, exception) - tags = ['social-auth'] - if backend_name: - tags.append(backend_name) - - try: - error(request, message, extra_tags=' '.join(tags)) - except MessageFailure: # messages app is not installed - url += ('?' in url and '&' or '?') + 'message=' + message - if backend_name: - url += '&backend=' + backend_name - return redirect(url) - - def get_backend(self, request, exception): - if not hasattr(self, 'backend'): - self.backend = getattr(request, 'backend', None) or \ - getattr(exception, 'backend', None) - return self.backend - - def raise_exception(self, request, exception): - backend = self.backend - return backend and \ - backend_setting(backend, 'SOCIAL_AUTH_RAISE_EXCEPTIONS') - - def get_message(self, request, exception): - return unicode(exception) - - def get_redirect_uri(self, request, exception): - if self.backend is not None: - return backend_setting(self.backend, - 'SOCIAL_AUTH_BACKEND_ERROR_URL') or \ - settings.LOGIN_ERROR_URL - return settings.LOGIN_ERROR_URL +from social.apps.django_app.middleware import SocialAuthExceptionMiddleware +SocialAuthExceptionMiddleware # placate pyflakes diff --git a/social_auth/migrations/0001_initial.py b/social_auth/migrations/0001_initial.py deleted file mode 100644 index 9e779d13b..000000000 --- a/social_auth/migrations/0001_initial.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration - -from django.db import models - -from django.conf import settings -from social_auth.utils import get_custom_user_model_for_migrations, \ - custom_user_frozen_models - -USER_MODEL = get_custom_user_model_for_migrations() -UID_LENGTH = getattr(settings, 'SOCIAL_AUTH_UID_LENGTH', 255) -NONCE_SERVER_URL_LENGTH = getattr(settings, 'SOCIAL_AUTH_NONCE_SERVER_URL_LENGTH', 255) -ASSOCIATION_SERVER_URL_LENGTH = getattr(settings, 'SOCIAL_AUTH_ASSOCIATION_SERVER_URL_LENGTH', 255) -ASSOCIATION_HANDLE_LENGTH = getattr(settings, 'SOCIAL_AUTH_ASSOCIATION_HANDLE_LENGTH', 255) - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'UserSocialAuth' - db.create_table('social_auth_usersocialauth', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='social_auth', to=orm[USER_MODEL])), - ('provider', self.gf('django.db.models.fields.CharField')(max_length=32)), - ('uid', self.gf('django.db.models.fields.CharField')(max_length=UID_LENGTH)), - ('extra_data', self.gf('social_auth.fields.JSONField')(default='{}')), - )) - db.send_create_signal('social_auth', ['UserSocialAuth']) - - # Adding unique constraint on 'UserSocialAuth', fields ['provider', 'uid'] - db.create_unique('social_auth_usersocialauth', ['provider', 'uid']) - - # Adding model 'Nonce' - db.create_table('social_auth_nonce', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('server_url', self.gf('django.db.models.fields.CharField')(max_length=NONCE_SERVER_URL_LENGTH)), - ('timestamp', self.gf('django.db.models.fields.IntegerField')()), - ('salt', self.gf('django.db.models.fields.CharField')(max_length=40)), - )) - db.send_create_signal('social_auth', ['Nonce']) - - # Adding model 'Association' - db.create_table('social_auth_association', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('server_url', self.gf('django.db.models.fields.CharField')(max_length=ASSOCIATION_SERVER_URL_LENGTH)), - ('handle', self.gf('django.db.models.fields.CharField')(max_length=ASSOCIATION_HANDLE_LENGTH)), - ('secret', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('issued', self.gf('django.db.models.fields.IntegerField')()), - ('lifetime', self.gf('django.db.models.fields.IntegerField')()), - ('assoc_type', self.gf('django.db.models.fields.CharField')(max_length=64)), - )) - db.send_create_signal('social_auth', ['Association']) - - - def backwards(self, orm): - # Removing unique constraint on 'UserSocialAuth', fields ['provider', 'uid'] - db.delete_unique('social_auth_usersocialauth', ['provider', 'uid']) - - # Deleting model 'UserSocialAuth' - db.delete_table('social_auth_usersocialauth') - - # Deleting model 'Nonce' - db.delete_table('social_auth_nonce') - - # Deleting model 'Association' - db.delete_table('social_auth_association') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'social_auth.association': { - 'Meta': {'object_name': 'Association'}, - 'assoc_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'handle': ('django.db.models.fields.CharField', [], {'max_length': str(ASSOCIATION_HANDLE_LENGTH)}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'issued': ('django.db.models.fields.IntegerField', [], {}), - 'lifetime': ('django.db.models.fields.IntegerField', [], {}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'server_url': ('django.db.models.fields.CharField', [], {'max_length': str(ASSOCIATION_SERVER_URL_LENGTH)}) - }, - 'social_auth.nonce': { - 'Meta': {'object_name': 'Nonce'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'salt': ('django.db.models.fields.CharField', [], {'max_length': '40'}), - 'server_url': ('django.db.models.fields.CharField', [], {'max_length': str(NONCE_SERVER_URL_LENGTH)}), - 'timestamp': ('django.db.models.fields.IntegerField', [], {}) - }, - 'social_auth.usersocialauth': { - 'Meta': {'unique_together': "(('provider', 'uid'),)", 'object_name': 'UserSocialAuth'}, - 'extra_data': ('social_auth.fields.JSONField', [], {'default': "'{}'"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'provider': ('django.db.models.fields.CharField', [], {'max_length': '32'}), - 'uid': ('django.db.models.fields.CharField', [], {'max_length': str(UID_LENGTH)}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'social_auth'", 'to': "orm['" + USER_MODEL + "']"}) - } - } - models.update(custom_user_frozen_models(USER_MODEL)) - - complete_apps = ['social_auth'] diff --git a/social_auth/migrations/0002_auto__add_unique_nonce_timestamp_salt_server_url__add_unique_associati.py b/social_auth/migrations/0002_auto__add_unique_nonce_timestamp_salt_server_url__add_unique_associati.py deleted file mode 100644 index fba230947..000000000 --- a/social_auth/migrations/0002_auto__add_unique_nonce_timestamp_salt_server_url__add_unique_associati.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -from django.conf import settings -from social_auth.utils import get_custom_user_model_for_migrations, \ - custom_user_frozen_models - - -USER_MODEL = get_custom_user_model_for_migrations() -UID_LENGTH = getattr(settings, 'SOCIAL_AUTH_UID_LENGTH', 255) -NONCE_SERVER_URL_LENGTH = getattr(settings, 'SOCIAL_AUTH_NONCE_SERVER_URL_LENGTH', 255) -ASSOCIATION_SERVER_URL_LENGTH = getattr(settings, 'SOCIAL_AUTH_ASSOCIATION_SERVER_URL_LENGTH', 255) -ASSOCIATION_HANDLE_LENGTH = getattr(settings, 'SOCIAL_AUTH_ASSOCIATION_HANDLE_LENGTH', 255) - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding index on 'Nonce', fields ['timestamp'] - db.create_index('social_auth_nonce', ['timestamp']) - - # Adding unique constraint on 'Nonce', fields ['timestamp', 'salt', 'server_url'] - db.create_unique('social_auth_nonce', ['timestamp', 'salt', 'server_url']) - - # Adding index on 'Association', fields ['issued'] - db.create_index('social_auth_association', ['issued']) - - # Adding unique constraint on 'Association', fields ['handle', 'server_url'] - db.create_unique('social_auth_association', ['handle', 'server_url']) - - - def backwards(self, orm): - # Removing unique constraint on 'Association', fields ['handle', 'server_url'] - db.delete_unique('social_auth_association', ['handle', 'server_url']) - - # Removing index on 'Association', fields ['issued'] - db.delete_index('social_auth_association', ['issued']) - - # Removing unique constraint on 'Nonce', fields ['timestamp', 'salt', 'server_url'] - db.delete_unique('social_auth_nonce', ['timestamp', 'salt', 'server_url']) - - # Removing index on 'Nonce', fields ['timestamp'] - db.delete_index('social_auth_nonce', ['timestamp']) - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'social_auth.association': { - 'Meta': {'unique_together': "(('server_url', 'handle'),)", 'object_name': 'Association'}, - 'assoc_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'handle': ('django.db.models.fields.CharField', [], {'max_length': str(ASSOCIATION_HANDLE_LENGTH)}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'issued': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'lifetime': ('django.db.models.fields.IntegerField', [], {}), - 'secret': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'server_url': ('django.db.models.fields.CharField', [], {'max_length': str(ASSOCIATION_SERVER_URL_LENGTH)}) - }, - 'social_auth.nonce': { - 'Meta': {'unique_together': "(('server_url', 'timestamp', 'salt'),)", 'object_name': 'Nonce'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'salt': ('django.db.models.fields.CharField', [], {'max_length': '40'}), - 'server_url': ('django.db.models.fields.CharField', [], {'max_length': str(NONCE_SERVER_URL_LENGTH)}), - 'timestamp': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) - }, - 'social_auth.usersocialauth': { - 'Meta': {'unique_together': "(('provider', 'uid'),)", 'object_name': 'UserSocialAuth'}, - 'extra_data': ('social_auth.fields.JSONField', [], {'default': "'{}'"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'provider': ('django.db.models.fields.CharField', [], {'max_length': '32'}), - 'uid': ('django.db.models.fields.CharField', [], {'max_length': str(UID_LENGTH)}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'social_auth'", 'to': "orm['" + USER_MODEL + "']"}) - } - } - models.update(custom_user_frozen_models(USER_MODEL)) - complete_apps = ['social_auth'] diff --git a/social_auth/migrations/__init__.py b/social_auth/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/social_auth/models.py b/social_auth/models.py index d976bb6de..0dde2c272 100644 --- a/social_auth/models.py +++ b/social_auth/models.py @@ -1,15 +1,36 @@ -"""Social auth models""" -import types +from social_auth import IS_DJANGO_MODELS -from django.utils.importlib import import_module -from social_auth.utils import setting +if IS_DJANGO_MODELS: + from social.apps.django_app.default.models import \ + UserSocialAuth as UserSocialAuthBase, \ + Nonce as NonceBase, \ + Association as AssociationBase, \ + DjangoStorage as DjangoStorageBase +else: + from social.apps.django_app.me.models import \ + UserSocialAuth as UserSocialAuthBase, \ + Nonce as NonceBase, \ + Association as AssociationBase, \ + DjangoStorage as DjangoStorageBase -SOCIAL_AUTH_MODELS_MODULE = import_module(setting('SOCIAL_AUTH_MODELS', - 'social_auth.db.django_models')) +class UserSocialAuth(UserSocialAuthBase): + class Meta: + proxy = True -globals().update((name, value) for name, value in - ((name, getattr(SOCIAL_AUTH_MODELS_MODULE, name)) - for name in dir(SOCIAL_AUTH_MODELS_MODULE)) - if isinstance(value, (type, types.ClassType))) + +class Nonce(NonceBase): + class Meta: + proxy = True + + +class Association(AssociationBase): + class Meta: + proxy = True + + +class DjangoStorage(DjangoStorageBase): + user = UserSocialAuth + nonce = Nonce + association = Association diff --git a/social_auth/signals.py b/social_auth/signals.py deleted file mode 100644 index fea9b8200..000000000 --- a/social_auth/signals.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.dispatch import Signal - - -# This module is deprecated, this signals aren't used by the code anymore -# and it's functionality should be replaced by pipeline methods. - - -pre_update = Signal(providing_args=['user', 'response', 'details']) -socialauth_registered = Signal(providing_args=['user', 'response', 'details']) diff --git a/social_auth/store.py b/social_auth/store.py deleted file mode 100644 index a872e14db..000000000 --- a/social_auth/store.py +++ /dev/null @@ -1,44 +0,0 @@ -"""OpenId storage that saves to django models""" -import time - -from openid.store.interface import OpenIDStore -from openid.store.nonce import SKEW - -from social_auth.models import UserSocialAuth - - -class DjangoOpenIDStore(OpenIDStore): - """Storage class""" - def __init__(self): - """Init method""" - super(DjangoOpenIDStore, self).__init__() - self.max_nonce_age = 6 * 60 * 60 # Six hours - - def storeAssociation(self, server_url, association): - """Store new assocition if doesn't exist""" - UserSocialAuth.store_association(server_url, association) - - def removeAssociation(self, server_url, handle): - return UserSocialAuth.remove_association(server_url, handle) - - def getAssociation(self, server_url, handle=None): - """Return stored assocition""" - oid_associations = UserSocialAuth.get_oid_associations(server_url, - handle) - associations = [association - for assoc_id, association in oid_associations - if association.getExpiresIn() > 0] - expired = [assoc_id for assoc_id, association in oid_associations - if association.getExpiresIn() == 0] - - if expired: # clear expired associations - UserSocialAuth.delete_associations(expired) - - if associations: # return most recet association - return associations[0] - - def useNonce(self, server_url, timestamp, salt): - """Generate one use number and return *if* it was created""" - if abs(timestamp - time.time()) > SKEW: - return False - return UserSocialAuth.use_nonce(server_url, timestamp, salt) diff --git a/social_auth/strategy.py b/social_auth/strategy.py new file mode 100644 index 000000000..3fba44e65 --- /dev/null +++ b/social_auth/strategy.py @@ -0,0 +1,154 @@ +from social.strategies.django_strategy import DjangoStrategy + + +class DSAStrategy(DjangoStrategy): + settings_map = { + 'AMAZON_SECRET': 'AMAZON_API_SECRET', + 'AMAZON_KEY': 'AMAZON_APP_ID', + 'AMAZON_SCOPE': 'AMAZON_EXTENDED_PERMISSIONS', + 'ANGEL_KEY': 'ANGEL_CLIENT_ID', + 'ANGEL_SECRET': 'ANGEL_CLIENT_SECRET', + 'APPSFUEL_KEY': 'APPSFUEL_CLIENT_ID', + 'APPSFUEL_SECRET': 'APPSFUEL_CLIENT_SECRET', + 'BEHANCE_KEY': 'BEHANCE_CLIENT_ID', + 'BEHANCE_SECRET': 'BEHANCE_CLIENT_SECRET', + 'BEHANCE_SCOPE': 'BEHANCE_EXTENDED_PERMISSIONS', + 'BITBUCKET_KEY': 'BITBUCKET_CONSUMER_KEY', + 'BITBUCKET_SECRET': 'BITBUCKET_CONSUMER_SECRET', + 'DAILYMOTION_KEY': 'DAILYMOTION_OAUTH2_KEY', + 'DAILYMOTION_SECRET': 'DAILYMOTION_OAUTH2_SECRET', + 'DISQUS_KEY': 'DISQUS_CLIENT_ID', + 'DISQUS_SECRET': 'DISQUS_CLIENT_SECRET', + 'DOUBAN_OAUTH2_KEY': 'DOUBAN2_CONSUMER_KEY', + 'DOUBAN_OAUTH2_SECRET': 'DOUBAN2_CONSUMER_SECRET', + 'DOUBAN_KEY': 'DOUBAN_CONSUMER_KEY', + 'DOUBAN_KEY': 'DOUBAN_CONSUMER_SECRET', + 'DROPBOX_KEY': 'DROPBOX_API_SECRET', + 'DROPBOX_SECRET': 'DROPBOX_APP_ID', + 'EVERNOTE_KEY': 'EVERNOTE_CONSUMER_KEY', + 'EVERNOTE_SECRET': 'EVERNOTE_CONSUMER_SECRET', + 'EXACTTARGET_SECRET': 'EXACTTARGET_APP_SIGNATURE', + 'EXACTTARGET_KEY': 'EXACTTARGET_UNUSED', + 'FACEBOOK_KEY': 'FACEBOOK_APP_ID', + 'FACEBOOK_SECRET': 'FACEBOOK_API_SECRET', + 'FACEBOOK_SCOPE': 'FACEBOOK_EXTENDED_PERMISSIONS', + 'FACEBOOK_APP_KEY': 'FACEBOOK_APP_ID', + 'FACEBOOK_APP_LOCAL_HTML': 'FACEBOOK_LOCAL_HTML', + 'FITBIT_KEY': 'FITBIT_CONSUMER_KEY', + 'FITBIT_SECRET': 'FITBIT_CONSUMER_SECRET', + 'FLICKR_SECRET': 'FLICKR_API_SECRET', + 'FLICKR_KEY': 'FLICKR_APP_ID', + 'FOURSQUARE_KEY': 'FOURSQUARE_CONSUMER_KEY', + 'FOURSQUARE_SECRET': 'FOURSQUARE_CONSUMER_SECRET', + 'GITHUB_SECRET': 'GITHUB_API_SECRET', + 'GITHUB_KEY': 'GITHUB_APP_ID', + 'GITHUB_SCOPE': 'GITHUB_EXTENDED_PERMISSIONS', + 'GOOGLE_OAUTH_KEY': 'GOOGLE_CONSUMER_KEY', + 'GOOGLE_OAUTH_SECRET': 'GOOGLE_CONSUMER_SECRET', + 'GOOGLE_OAUTH_SCOPE': 'GOOGLE_OAUTH_EXTRA_SCOPE', + 'GOOGLE_OAUTH2_KEY': 'GOOGLE_OAUTH2_CLIENT_KEY', + 'GOOGLE_OAUTH2_SECRET': 'GOOGLE_OAUTH2_CLIENT_SECRET', + 'GOOGLE_OAUTH2_SCOPE': 'GOOGLE_OAUTH_EXTRA_SCOPE', + 'INSTAGRAM_KEY': 'INSTAGRAM_CLIENT_ID', + 'INSTAGRAM_SECRET': 'INSTAGRAM_CLIENT_SECRET', + 'JAWBONE_KEY': 'JAWBONE_CONSUMER_KEY', + 'JAWBONE_SECRET': 'JAWBONE_CONSUMER_SECRET', + 'JAWBONE_SCOPE': 'JAWBONE_EXTENDED_PERMISSIONS', + 'LINKEDIN_KEY': 'LINKEDIN_CONSUMER_KEY', + 'LINKEDIN_SECRET': 'LINKEDIN_CONSUMER_SECRET', + 'LINKEDIN_FIELDS_SELECTORS': 'LINKEDIN_EXTRA_FIELD_SELECTORS', + 'LINKEDIN_OAUTH2_KEY': 'LINKEDIN_CONSUMER_KEY', + 'LINKEDIN_OAUTH2_SECRET': 'LINKEDIN_CONSUMER_SECRET', + 'LINKEDIN_OAUTH2_FIELDS_SELECTORS': 'LINKEDIN_EXTRA_FIELD_SELECTORS', + 'LINKEDIN_OAUTH2_SCOPE': 'LINKEDIN_SCOPE', + 'LIVE_KEY': 'LIVE_CLIENT_ID', + 'LIVE_SECRET': 'LIVE_CLIENT_SECRET', + 'LIVE_SCOPE': 'LIVE_EXTENDED_PERMISSIONS', + 'MAILRU_OAUTH2_KEY': 'MAILRU_OAUTH2_CLIENT_KEY', + 'MAILRU_OAUTH2_SECRET': 'MAILRU_OAUTH2_CLIENT_SECRET', + 'MAILRU_OAUTH2_SCOPE': 'MAILRU_OAUTH2_EXTRA_SCOPE', + 'MENDELEY_KEY': 'MENDELEY_CONSUMER_KEY', + 'MENDELEY_SECRET': 'MENDELEY_CONSUMER_SECRET', + 'MIXCLOUD_KEY': 'MIXCLOUD_CLIENT_ID', + 'MIXCLOUD_SECRET': 'MIXCLOUD_CLIENT_SECRET', + 'ODNOKLASSNIKI_OAUTH2_KEY': 'ODNOKLASSNIKI_OAUTH2_CLIENT_KEY', + 'ODNOKLASSNIKI_OAUTH2_SECRET': 'ODNOKLASSNIKI_OAUTH2_CLIENT_SECRET', + 'ODNOKLASSNIKI_OAUTH2_SCOPE': 'ODNOKLASSNIKI_OAUTH2_EXTRA_SCOPE', + 'ORKUT_KEY': 'ORKUT_CONSUMER_KEY', + 'ORKUT_SECRET': 'ORKUT_CONSUMER_SECRET', + 'RDIO_OAUTH2_SCOPE': 'RDIO2_PERMISSIONS', + 'READABILITY_KEY': 'READABILITY_CONSUMER_KEY', + 'READABILITY_SECRET': 'READABILITY_CONSUMER_SECRET', + 'REDDIT_SECRET': 'REDDIT_API_SECRET', + 'REDDIT_KEY': 'REDDIT_APP_ID', + 'REDDIT_SCOPE': 'REDDIT_EXTENDED_PERMISSIONS', + 'SHOPIFY_KEY': 'SHOPIFY_APP_API_KEY', + 'SHOPIFY_SECRET': 'SHOPIFY_SHARED_SECRET', + 'SKYROCK_KEY': 'SKYROCK_CONSUMER_KEY', + 'SKYROCK_SECRET': 'SKYROCK_CONSUMER_SECRET', + 'SOUNDCLOUD_KEY': 'SOUNDCLOUD_CLIENT_ID', + 'SOUNDCLOUD_SECRET': 'SOUNDCLOUD_CLIENT_SECRET', + 'SOUNDCLOUD_SCOPE': 'SOUNDCLOUD_EXTENDED_PERMISSIONS', + 'STACKOVERFLOW_KEY': 'STACKOVERFLOW_CLIENT_ID', + 'STACKOVERFLOW_SECRET': 'STACKOVERFLOW_CLIENT_SECRET', + 'STACKOVERFLOW_SCOPE': 'STACKOVERFLOW_EXTENDED_PERMISSIONS', + 'STOCKTWITS_KEY': 'STOCKTWITS_CONSUMER_KEY', + 'STOCKTWITS_SECRET': 'STOCKTWITS_CONSUMER_SECRET', + 'STRIPE_KEY': 'STRIPE_APP_ID', + 'STRIPE_SECRET': 'STRIPE_APP_SECRET', + 'TRELLO_KEY': 'TRELLO_CONSUMER_KEY', + 'TRELLO_SECRET': 'TRELLO_CONSUMER_SECRET', + 'TRIPIT_KEY': 'TRIPIT_API_KEY', + 'TRIPIT_SECRET': 'TRIPIT_API_SECRET', + 'TUMBLR_KEY': 'TUMBLR_CONSUMER_KEY', + 'TUMBLR_SECRET': 'TUMBLR_CONSUMER_SECRET', + 'TWILIO_SECRET': 'TWILIO_AUTH_TOKEN', + 'TWILIO_KEY': 'TWILIO_CONNECT_KEY', + 'TWITTER_KEY': 'TWITTER_CONSUMER_KEY', + 'TWITTER_SECRET': 'TWITTER_CONSUMER_SECRET', + 'VK_APP_SECRET': 'VKAPP_API_SECRET', + 'VK_APP_KEY': 'VKAPP_APP_ID', + 'VK_APP_USERMODE': 'VKAPP_USER_MODE', + 'VK_OAUTH2_EXTRA_DATA': 'VK_EXTRA_DATA', + 'VK_OAUTH2_SCOPE': 'VK_EXTRA_SCOPE', + 'VK_OAUTH2_SECRET': 'VK_API_SECRET', + 'VK_OPENAPI_LOCAL_HTML': 'VK_LOCAL_HTML', + 'VK_OPENAPI_APP_ID': 'VK_APP_ID', + 'WEIBO_KEY': 'WEIBO_CLIENT_KEY', + 'WEIBO_SECRET': 'WEIBO_CLIENT_SECRET', + 'XING_KEY': 'XING_CONSUMER_KEY', + 'XING_SECRET': 'XING_CONSUMER_SECRET', + 'YAHOO_KEY': 'YAHOO_CONSUMER_KEY', + 'YAHOO_SECRET': 'YAHOO_CONSUMER_SECRET', + 'YAMMER_KEY': 'YAMMER_CONSUMER_KEY', + 'YAMMER_SECRET': 'YAMMER_CONSUMER_SECRET', + 'YAMMER_STAGING_KEY': 'YAMMER_STAGING_CONSUMER_KEY', + 'YAMMER_STAGING_SECRET': 'YAMMER_STAGING_CONSUMER_SECRET', + 'YANDEX_SECRET': 'YANDEX_API_SECRET', + 'YANDEX_KEY': 'YANDEX_APP_ID', + 'ON_HTTPS': 'SOCIAL_AUTH_REDIRECT_IS_HTTPS', + } + + def get_setting(self, name): + if name in self.settings_map: + # Try DSA setting name from map defined above + try: + return super(DSAStrategy, self).get_setting( + self.settings_map[name] + ) + except (AttributeError, KeyError): + pass + # Fallback to PSA setting name + return super(DSAStrategy, self).get_setting(name) + + def get_pipeline(self): + pipeline = super(DSAStrategy, self).get_pipeline() + pipeline_renamed = [] + for entry in pipeline: + if entry.startswith('social_auth.backends.pipeline.social'): + entry = entry.replace( + 'social_auth.backends.pipeline.social', + 'social_auth.backends.pipeline.sauth' + ) + pipeline_renamed.append(entry) + return pipeline_renamed diff --git a/social_auth/tests/__init__.py b/social_auth/tests/__init__.py deleted file mode 100644 index 969a45ce7..000000000 --- a/social_auth/tests/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -from social_auth.utils import setting - - -if setting('SOCIAL_AUTH_TEST_TWITTER', True): - from social_auth.tests.twitter import * - -if setting('SOCIAL_AUTH_TEST_FACEBOOK', True): - from social_auth.tests.facebook import * - -if setting('SOCIAL_AUTH_TEST_GOOGLE', True): - from social_auth.tests.google import * - -if setting('SOCIAL_AUTH_TEST_ODNOKLASSNIKI', True): - from social_auth.tests.odnoklassniki import * \ No newline at end of file diff --git a/social_auth/tests/base.py b/social_auth/tests/base.py deleted file mode 100644 index f5bed808a..000000000 --- a/social_auth/tests/base.py +++ /dev/null @@ -1,175 +0,0 @@ -import re -import urllib2 -import cookielib -import urllib -import urlparse -import unittest -from sgmllib import SGMLParser -from django.conf import settings - -from django.test.client import Client -from django.core.urlresolvers import reverse - - -USER_AGENT = 'Mozilla/5.0' -REFRESH_RE = re.compile(r'\d;\s*url=') - - -class SocialAuthTestsCase(unittest.TestCase): - """Base class for social auth tests""" - SERVER_NAME = None - SERVER_PORT = None - - def __init__(self, *args, **kwargs): - client_kwargs = {} - if self.SERVER_NAME: - client_kwargs['SERVER_NAME'] = self.SERVER_NAME - if self.SERVER_PORT: - client_kwargs['SERVER_PORT'] = self.SERVER_PORT - self.jar = None - self.client = Client(**client_kwargs) - super(SocialAuthTestsCase, self).__init__(*args, **kwargs) - - def setUp(self): - from social_auth import backends - self.old_PIPELINE = backends.PIPELINE - backends.PIPELINE = ( - 'social_auth.backends.pipeline.social.social_auth_user', - 'social_auth.backends.pipeline.associate.associate_by_email', - 'social_auth.backends.pipeline.user.get_username', - 'social_auth.backends.pipeline.user.create_user', - 'social_auth.backends.pipeline.social.associate_user', - 'social_auth.backends.pipeline.social.load_extra_data', - 'social_auth.backends.pipeline.user.update_user_details', - ) - super(SocialAuthTestsCase, self).setUp() - - def tearDown(self): - from social_auth import backends - backends.PIPELINE = self.old_PIPELINE - super(SocialAuthTestsCase, self).tearDown() - - def test_backend_cache(self): - """Ensure that the backend for the testcase gets cached.""" - try: - self.name - except AttributeError: - pass - else: - if self.name not in settings.SOCIAL_AUTH_ENABLED_BACKENDS: - # this backend is not enabled (for example, google-openid/google-oauth2) - return - from social_auth import backends - backends.BACKENDS = {} - self.client.get(self.reverse('socialauth_begin', self.name)) - self.assertTrue(self.name in backends.BACKENDSCACHE) - - def get_content(self, url, data=None, use_cookies=False): - """Return content for given url, if data is not None, then a POST - request will be issued, otherwise GET will be used""" - data = data and urllib.urlencode(data, doseq=True) or data - request = urllib2.Request(url) - agent = urllib2.build_opener() - - if use_cookies: - agent.add_handler(urllib2.HTTPCookieProcessor(self.get_jar())) - request.add_header('User-Agent', USER_AGENT) - return ''.join(agent.open(request, data=data).readlines()) - - def get_redirect(self, url, data=None, use_cookies=False): - """Return content for given url, if data is not None, then a POST - request will be issued, otherwise GET will be used""" - data = data and urllib.urlencode(data, doseq=True) or data - request = urllib2.Request(url) - agent = urllib2.build_opener(RedirectHandler()) - - if use_cookies: - agent.add_handler(urllib2.HTTPCookieProcessor(self.get_jar())) - request.add_header('User-Agent', USER_AGENT) - return agent.open(request, data=data) - - def get_jar(self): - if not self.jar: - self.jar = cookielib.CookieJar() - return self.jar - - def reverse(self, name, backend): - """Reverses backend URL by name""" - return reverse(name, args=(backend,)) - - def make_relative(self, value): - """Converst URL to relative, useful for server responses""" - parsed = urlparse.urlparse(value) - return urlparse.urlunparse(('', '', parsed.path, parsed.params, - parsed.query, parsed.fragment)) - - -class CustomParser(SGMLParser): - """Custom SGMLParser that closes the parser once it's fed""" - def feed(self, data): - SGMLParser.feed(self, data) - self.close() - - -class FormParser(CustomParser): - """Form parser, load form data and action for given form""" - def __init__(self, *args, **kwargs): - CustomParser.__init__(self, *args, **kwargs) - self.inside_form = False - self.action = None - self.values = {} - - def start_form(self, attributes): - """Start form parsing detecting if form is the one requested""" - attrs = dict(attributes) - if self.in_form(attrs): - # flag that we are inside the form and save action - self.inside_form = True - self.action = attrs.get('action') - - def in_form(self, attrs): - """Override below""" - return True - - def end_form(self): - """End form parsing, unset inside_form flag""" - self.inside_form = False - - def start_input(self, attributes): - """Parse input fields, we only keep data for fields of type text, - hidden or password and that has a valid name.""" - attrs = dict(attributes) - if self.inside_form: - type, name, value = attrs.get('type'), attrs.get('name'), \ - attrs.get('value') - if name and type in ('text', 'hidden', 'password'): - self.values[name] = value - - -class FormParserByID(FormParser): - """Form parser, load form data and action for given form identified - by its id""" - def __init__(self, form_id, *args, **kwargs): - FormParser.__init__(self, *args, **kwargs) - self.form_id = form_id - - def in_form(self, attrs): - return attrs.get('id') == self.form_id - - -class RefreshParser(CustomParser): - """Refresh parser, will check refresh by meta tag and store refresh URL""" - def __init__(self, *args, **kwargs): - CustomParser.__init__(self, *args, **kwargs) - self.value = None - - def start_meta(self, attributes): - """Start meta parsing checking by http-equiv attribute""" - attrs = dict(attributes) - if attrs.get('http-equiv') == 'refresh': - self.value = REFRESH_RE.sub('', attrs.get('content')).strip("'") - - -class RedirectHandler(urllib2.HTTPRedirectHandler): - def http_error_302(self, req, fp, code, msg, headers): - return fp diff --git a/social_auth/tests/client.py b/social_auth/tests/client.py deleted file mode 100644 index e1b2acb56..000000000 --- a/social_auth/tests/client.py +++ /dev/null @@ -1,161 +0,0 @@ -import urllib -from django.conf import settings -from django.contrib.auth.models import AnonymousUser -from django.test.client import Client, RequestFactory -from django.utils import simplejson -from django.utils.importlib import import_module -from mock import patch -from social_auth.views import complete - -class DumbResponse(object): - """ - Response from a call to, urllib2.urlopen() - """ - - def __init__(self, data_str, url=None): - self.data_str = data_str - self.url = url - - def read(self): - return self.data_str - - -class NoBackendError(Exception): - """ - Used when a client attempts to login with a invalid backend. - """ - pass - - -class SocialClient(Client): - """ - Test client to login/register a user - Does so by mocking api posts/responses. - - Only supports facebook. - """ - - @patch('social_auth.backends.facebook.FacebookAuth.enabled') - @patch('social_auth.utils.urlopen') - def login(self, user, mock_urlopen, mock_facebook_enabled, backend='facebook'): - """ - Login or Register a facebook user. - - If the user has never logged in then they get registered and logged in. - If the user has already registered, then they are logged in. - - user: dict - backend: 'facebook' - - example user: - { - 'first_name': 'Django', - 'last_name': 'Reinhardt', - 'verified': True, - 'name': 'Django Reinhardt', - 'locale': 'en_US', - 'hometown': { - 'id': '12345678', - 'name': 'Any Town, Any State' - }, - 'expires': '4812', - 'updated_time': '2012-01-29T19:27:32+0000', - 'access_token': 'dummyToken', - 'link': 'http://www.facebook.com/profile.php?id=1234', - 'location': { - 'id': '108659242498155', - 'name': 'Chicago, Illinois' - }, - 'gender': 'male', - 'timezone': -6, - 'id': '1234', - 'email': 'user@domain.com' - } - """ - - token = 'dummyToken' - backends = { - 'facebook': ( - urllib.urlencode({ - 'access_token': token, - 'expires': 3600, - }), - simplejson.dumps(user), - ), - - 'google': ( - simplejson.dumps({ - "access_token": token, - "token_type": "Bearer", - "expires_in": 3600, - }), - simplejson.dumps(user), - ), - - 'linkedin': ( - urllib.urlencode({ - 'oauth_token': token, - 'oauth_token_secret': token, - 'oauth_callback_confirmed': 'true', - 'xoauth_request_auth_url': ( - 'https://api.linkedin.com/uas/oauth/authorize'), - 'oauth_expires_in': 3600, - }), - urllib.urlencode({ - 'oauth_token': token, - 'oauth_token_secret': token, - 'oauth_expires_in': 3600, - 'oauth_authorization_expires_in': 3600, - }), - (('\n' - '\n' - ' {id}\n' - ' {email}\n' - ' {first_name}\n' - ' {last_name}\n' - '\n').format(**user)), - ), - } - - if backend not in backends: - raise NoBackendError("%s is not supported" % backend) - - """ - mock out urlopen - """ - mock_urlopen.side_effect = [ - DumbResponse(r) for r in backends[backend] - ] - # make it work when no FACEBOOK_APP_ID declared - mock_facebook_enabled.return_value = True - factory = RequestFactory() - request = factory.post('', {'code': 'dummy', - 'redirect_state': 'dummy'}) - - engine = import_module(settings.SESSION_ENGINE) - if self.session: - request.session = self.session - else: - request.session = engine.SessionStore() - - request.user = AnonymousUser() - request.session['facebook_state'] = 'dummy' - - # make it happen. - redirect = complete(request, backend) - - request.session.save() - - # Set the cookie for this session. - session_cookie = settings.SESSION_COOKIE_NAME - self.cookies[session_cookie] = request.session.session_key - cookie_data = { - 'max-age': None, - 'path': '/', - 'domain': settings.SESSION_COOKIE_DOMAIN, - 'secure': settings.SESSION_COOKIE_SECURE or None, - 'expires': None, - } - self.cookies[session_cookie].update(cookie_data) - - return True diff --git a/social_auth/tests/facebook.py b/social_auth/tests/facebook.py deleted file mode 100644 index cf76adc7e..000000000 --- a/social_auth/tests/facebook.py +++ /dev/null @@ -1,88 +0,0 @@ -import re - -from unittest import skip - -from social_auth.utils import setting -from social_auth.tests.base import SocialAuthTestsCase, FormParserByID -from django.contrib.sites.models import Site - - -class FacebookTestCase(SocialAuthTestsCase): - SERVER_NAME = 'myapp.com' - SERVER_PORT = '8000' - - def __init__(self, methodName='runTest'): - super(FacebookTestCase, self).__init__(methodName) - - name = 'facebook' - - def setUp(self, *args, **kwargs): - self.SERVER_NAME = Site.objects.get_current() - super(FacebookTestCase, self).setUp(*args, **kwargs) - self.user = setting('TEST_FACEBOOK_USER') - self.passwd = setting('TEST_FACEBOOK_PASSWORD') - # check that user and password are setup properly - # Ugh, these fail too. - #self.assertTrue(self.user) - #self.assertTrue(self.passwd) - - -REDIRECT_RE = re.compile('window.location.replace\("(.*)"\);') - -class FacebookTestLogin(FacebookTestCase): - @skip("FacebookTestCase.setUp() is broken") - def test_login_succeful(self): - """ - - """ - response = self.client.get('http://%s%s' % (self.SERVER_NAME, self.reverse('socialauth_begin', 'facebook'))) - # social_auth must redirect to service page - self.assertEqual(response.status_code, 302) - - # Open first redirect page, it contains user login form because - # we don't have cookie to send to twitter - parser = FormParserByID('login_form') - content = self.get_content(response['Location'], use_cookies=True) - parser.feed(content) - auth = {'email': self.user, - 'pass': self.passwd} - - # Check that action and values were loaded properly - self.assertTrue(parser.action) - self.assertTrue(parser.values) - - # Post login form, will return authorization or redirect page - parser.values.update(auth) - redirect = self.get_redirect(parser.action, parser.values, - use_cookies=True) - # If page contains a form#login_form, then we are in the app - # authorization page because the app is not authorized yet, - # otherwise the app already gained permission and twitter sends - # a page that redirects to redirect_url - if 'login_form' in content: - # authorization form post, returns redirect_page - parser = FormParserByID('login_form') - parser.feed(content) - self.assertTrue(parser.action) - self.assertTrue(parser.values) - parser.values.update(auth) - redirect = self.get_redirect(parser.action, parser.values, - use_cookies=True) - redirect_page = redirect.read() - else: - redirect = self.get_redirect(redirect.headers['Location'], - use_cookies=True) - redirect_page = redirect.read() - - if 'uiserver_form' in redirect_page: - # authorization form post, returns redirect_page - parser = FormParserByID('uiserver_form') - parser.feed(redirect_page) - self.assertTrue(parser.action) - self.assertTrue(parser.values) - parser.values.update(auth) - redirect = self.get_redirect(parser.action, parser.values, - use_cookies=True) - - - self.assertTrue(setting('LOGIN_REDIRECT_URL') in self.make_relative(redirect.headers['Location'])) diff --git a/social_auth/tests/google.py b/social_auth/tests/google.py deleted file mode 100644 index 91258f189..000000000 --- a/social_auth/tests/google.py +++ /dev/null @@ -1,82 +0,0 @@ -import re - -from unittest import expectedFailure, skip - -from social_auth.utils import setting -from social_auth.tests.base import SocialAuthTestsCase, FormParserByID, \ - FormParser, RefreshParser -from django.conf import settings - -class GoogleTestCase(SocialAuthTestsCase): - - name = 'google' - - def setUp(self, *args, **kwargs): - super(GoogleTestCase, self).setUp(*args, **kwargs) - self.user = setting('TEST_GOOGLE_USER') - self.passwd = setting('TEST_GOOGLE_PASSWORD') - # check that user and password are setup properly - # These fail. - #self.assertTrue(self.user) - #self.assertTrue(self.passwd) - - -REDIRECT_RE = re.compile('window.location.replace\("(.*)"\);') - - -class GoogleOpenIdTestLogin(GoogleTestCase): - SERVER_NAME = 'myapp.com' - SERVER_PORT = '8000' - - @skip("GoogleTestCase.setUp() is broken") - def test_login_succeful(self): - if self.name not in settings.SOCIAL_AUTH_ENABLED_BACKENDS: - self.skipTest('Google OpenID is not enabled') - response = self.client.get(self.reverse('socialauth_begin', 'google')) - - parser = FormParserByID('openid_message') - parser.feed(response.content) - # Check that action and values were loaded properly - self.assertTrue(parser.action) - self.assertTrue(parser.values) - content = self.get_content(parser.action, parser.values, - use_cookies=True) - - parser = FormParserByID('gaia_loginform') - parser.feed(content) - auth = {'Email': self.user, 'Passwd': self.passwd} - parser.values.update(auth) - # Check that action and values were loaded properly - self.assertTrue(parser.action) - self.assertTrue(parser.values) - - content = self.get_content(parser.action, parser.values, - use_cookies=True) - parser = RefreshParser() - parser.feed(content) - - # approved? - result = self.get_redirect(parser.value, use_cookies=True) - if result.headers.get('Location', ''): # approved? - # damn, google has a hell of redirects :-( - result = self.get_redirect(result.headers['Location'], - use_cookies=True) - result = self.get_redirect(result.headers['Location'], - use_cookies=True) - result = self.get_redirect(result.headers['Location'], - use_cookies=True) - - # app was not approved - if self.SERVER_NAME not in result.headers.get('Location', ''): - content = self.get_content(parser.value, use_cookies=True) - parser = FormParser() - parser.feed(content) - parser.values['submit_true'] = 'yes' - parser.values['remember_choices'] = 'yes' - result = self.get_redirect(parser.action, parser.values, - use_cookies=True) - - response = self.client.get(self.make_relative( - result.headers['Location'])) - self.assertTrue(setting('LOGIN_REDIRECT_URL') in \ - self.make_relative(response['Location'])) diff --git a/social_auth/tests/odnoklassniki.py b/social_auth/tests/odnoklassniki.py deleted file mode 100644 index 82d2abde8..000000000 --- a/social_auth/tests/odnoklassniki.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding:utf-8 -*- -from __future__ import unicode_literals -from unittest import skipUnless -from django.conf import settings -from django.core.urlresolvers import reverse -from django.test.testcases import LiveServerTestCase, SimpleTestCase -from django.test.utils import override_settings -from selenium.webdriver.firefox.webdriver import WebDriver -from selenium.webdriver.support.ui import WebDriverWait -from social_auth.backends.contrib.odnoklassniki import odnoklassniki_oauth_sig -from social_auth.models import UserSocialAuth -import time - -class SignatureTest(SimpleTestCase): - def test_oauth_signature(self): - data = {'access_token': 'cq240efje3pd0gdXUmrvvMaHyb-74XQi8', - 'application_key': 'CBAJLNABABABABABA', - 'method': 'users.getCurrentUser', - 'format': 'JSON'} - secret = '31D6095131175A7C9656EC2C' - signature = '755fe7af274abbe545916039eb428c98' - self.assertEqual(odnoklassniki_oauth_sig(data, secret), signature) - -class OdnoklassnikiLiveTest(LiveServerTestCase): - @classmethod - def setUpClass(cls): - cls.selenium = WebDriver() - super(OdnoklassnikiLiveTest, cls).setUpClass() - - @classmethod - def tearDownClass(cls): - super(OdnoklassnikiLiveTest, cls).tearDownClass() - cls.selenium.quit() - - def get_odnoklassniki_name(self): - raise NotImplementedError('This method is part of interface, but should be implemented in subclass') - -class BaseOdnoklassnikiAppTest(OdnoklassnikiLiveTest): - @skipUnless(hasattr(settings, 'ODNOKLASSNIKI_APP_ID'), - "You need to have ODNOKLASSNIKI_APP_ID in settings to test iframe app") - @skipUnless(hasattr(settings, 'ODNOKLASSNIKI_SANDBOX_DEV_USERNAME'), - "You need to have ODNOKLASSNIKI_SANDBOX_DEV_USERNAME in settings to test iframe app") - @skipUnless(hasattr(settings, 'ODNOKLASSNIKI_SANDBOX_DEV_PASSWORD'), - "You need to have ODNOKLASSNIKI_SANDBOX_DEV_PASSWORD in settings to test iframe app") - def setUp(self): - self.app_id = settings.ODNOKLASSNIKI_APP_ID - self.dev_username = settings.ODNOKLASSNIKI_SANDBOX_DEV_USERNAME - self.dev_password = settings.ODNOKLASSNIKI_SANDBOX_DEV_PASSWORD - self.get_odnoklassniki_name() - - def sandbox_login(self): - WebDriverWait(self.selenium, 3).until(lambda ff: ff.find_element_by_name('j_username')) - dev_username_input = self.selenium.find_element_by_name('j_username') - dev_username_input.send_keys(self.dev_username) - dev_password_input = self.selenium.find_element_by_name('j_password') - dev_password_input.send_keys(self.dev_password) - self.selenium.find_element_by_name('actionId').click() - - def sandbox_logout(self): - self.selenium.get('http://api-sandbox.odnoklassniki.ru:8088/sandbox/logout.do') - WebDriverWait(self.selenium, 3).until(lambda ff: ff.find_element_by_name('j_username')) - - def get_odnoklassniki_name(self): - self.selenium.get('http://api-sandbox.odnoklassniki.ru:8088/sandbox/protected/main.do') - self.sandbox_login() - WebDriverWait(self.selenium, 3).until(lambda ff: ff.find_element_by_tag_name('fieldset')) - self.odnoklassniki_name = self.selenium.find_element_by_xpath('//*[@id="command"]/fieldset/table/tbody/tr[2]/td[2]').text - self.sandbox_logout() - - def login_into_sandbox(self): - self.selenium.get('http://api-sandbox.odnoklassniki.ru:8088/sandbox/protected/application/launch.do?appId={0:s}&userId=0'.format(self.app_id)) - self.sandbox_login() - WebDriverWait(self.selenium, 3).until(lambda ff: ff.find_element_by_tag_name('iframe')) - time.sleep(1) - -class OdnoklassnikiAppTest(BaseOdnoklassnikiAppTest): - def test_auth(self): - self.login_into_sandbox() - self.assertEquals(UserSocialAuth.objects.count(), 1) - social_auth = UserSocialAuth.objects.get() - user = social_auth.user - full_name = '{0} {1}'.format(user.first_name, user.last_name) - self.assertEquals(full_name, self.odnoklassniki_name) - self.assertTrue('apiconnection' in social_auth.extra_data) - self.assertTrue('api_server' in social_auth.extra_data) - -class OdnoklassnikiAppTestExtraData(BaseOdnoklassnikiAppTest): - @override_settings(ODNOKLASSNIKI_APP_EXTRA_USER_DATA_LIST = ('gender', 'birthday', 'age')) - def test_extra_data(self): - self.login_into_sandbox() - self.assertEquals(UserSocialAuth.objects.count(), 1) - social_user = UserSocialAuth.objects.get() - user = social_user.user - full_name = '{0} {1}'.format(user.first_name, user.last_name) - self.assertEquals(full_name, self.odnoklassniki_name) - self.assertTrue(all([field in social_user.extra_data for field in ('gender', 'birthday', 'age')])) - -class OdnoklassnikiOAuthTest(OdnoklassnikiLiveTest): - @skipUnless(hasattr(settings, "ODNOKLASSNIKI_OAUTH2_CLIENT_KEY"), - "You need to have ODNOKLASSNIKI_OAUTH2_CLIENT_KEY in settings to test odnoklassniki OAuth") - @skipUnless(hasattr(settings, "ODNOKLASSNIKI_TEST_USERNAME"), - "You need to have ODNOKLASSNIKI_TEST_USERNAME in settings to test odnoklassniki OAuth") - @skipUnless(hasattr(settings, "ODNOKLASSNIKI_TEST_PASSWORD"), - "You need to have ODNOKLASSNIKI_TEST_PASSWORD in settings to test odnoklassniki OAuth") - def setUp(self): - self.username = settings.ODNOKLASSNIKI_TEST_USERNAME - self.password = settings.ODNOKLASSNIKI_TEST_PASSWORD - self.get_odnoklassniki_name() - - def get_odnoklassniki_name(self): - #Load login page - self.selenium.get('http://www.odnoklassniki.ru/') - WebDriverWait(self.selenium, 3).until(lambda ff: ff.find_element_by_id('field_email')) - email_input = self.selenium.find_element_by_id('field_email') - email_input.send_keys(self.username) - pw_input = self.selenium.find_element_by_id('field_password') - pw_input.send_keys(self.password) - self.selenium.find_element_by_id('hook_FormButton_button_go').click() - #Submit form, wait for successful login - name_css_sel = '#hook_Block_MiddleColumnTopCardUser .mctc_name>a.mctc_nameLink' - WebDriverWait(self.selenium, 2).until(lambda ff: ff.find_element_by_css_selector(name_css_sel)) - self.odnoklassniki_name = self.selenium.find_element_by_css_selector(name_css_sel).text - #Remember the name of logged user - link = [el for el in self.selenium.find_elements_by_css_selector('.portal-headline__login__link') if el.text == 'выход'] - self.assertTrue(len(link) == 1) - link[0].click() - #Click on logout link to show logout popup - WebDriverWait(self.selenium, 2).until(lambda ff: ff.find_element_by_id('hook_Form_PopLayerLogoffUserForm') and ff.find_element_by_id('hook_Form_PopLayerLogoffUserForm').is_displayed()) - self.selenium.find_element_by_css_selector('#hook_FormButton_button_logoff').click() - #Click logout popup and wait for the login form be shown - WebDriverWait(self.selenium, 2).until(lambda ff: ff.find_element_by_id('field_email')) - - def login_into_odnoklassniki(self): - url = reverse('socialauth_begin', args=('odnoklassniki',)) - self.selenium.get('{0:s}{1:s}'.format(self.live_server_url, url)) - WebDriverWait(self.selenium, 2).until(lambda ff: ff.find_element_by_id('field_email')) - email_input = self.selenium.find_element_by_id('field_email') - pw_input = self.selenium.find_element_by_id('field_password') - email_input.send_keys(self.username) - pw_input.send_keys(self.password) - self.selenium.find_element_by_name('button_continue').click() - WebDriverWait(self.selenium, 2).until(lambda ff: ff.find_element_by_name('button_accept_request')) - self.selenium.find_element_by_name('button_accept_request').click() - self.selenium.implicitly_wait(2) - time.sleep(1)#We need this for the server to close database connection - #If this line is removed, following line will fail - - def test_auth(self): - self.login_into_odnoklassniki() - self.assertEquals(UserSocialAuth.objects.count(), 1) - user = UserSocialAuth.objects.get().user - full_name = '{0} {1}'.format(user.first_name, user.last_name) - self.assertEquals(full_name, self.odnoklassniki_name) - diff --git a/social_auth/tests/twitter.py b/social_auth/tests/twitter.py deleted file mode 100644 index 1176901af..000000000 --- a/social_auth/tests/twitter.py +++ /dev/null @@ -1,79 +0,0 @@ -from unittest import expectedFailure, skip - -from social_auth.utils import setting -from social_auth.tests.base import SocialAuthTestsCase, FormParserByID, \ - RefreshParser -from django.test.utils import override_settings - - -class TwitterTestCase(SocialAuthTestsCase): - - name = 'twitter' - - def setUp(self, *args, **kwargs): - super(TwitterTestCase, self).setUp(*args, **kwargs) - self.user = setting('TEST_TWITTER_USER') - self.passwd = setting('TEST_TWITTER_PASSWORD') - # check that user and password are setup properly - # These fail spectacularly, and it's annoying to - # have asserts in setUp() anyway, especially in - # classes that are inherited. - #self.assertTrue(self.user) - #self.assertTrue(self.passwd) - - -class TwitterTestLogin(TwitterTestCase): - @skip("TwitterTestCase.setUp() is broken") - @override_settings(SOCIAL_AUTH_PIPELINE = ( - 'social_auth.backends.pipeline.social.social_auth_user', - 'social_auth.backends.pipeline.associate.associate_by_email', - 'social_auth.backends.pipeline.user.get_username', - 'social_auth.backends.pipeline.misc.save_status_to_session', - 'social_auth.backends.pipeline.social.associate_user', - 'social_auth.backends.pipeline.social.load_extra_data', - 'social_auth.backends.pipeline.user.update_user_details', - )) - def test_login_successful(self): - response = self.client.get(self.reverse('socialauth_begin', 'twitter')) - # social_auth must redirect to service page - self.assertEqual(response.status_code, 302) - - # Open first redirect page, it contains user login form because - # we don't have cookie to send to twitter - login_content = self.get_content(response['Location']) - parser = FormParserByID('oauth_form') - parser.feed(login_content) - auth = {'session[username_or_email]': self.user, - 'session[password]': self.passwd} - - # Check that action and values were loaded properly - self.assertTrue(parser.action) - self.assertTrue(parser.values) - - # Post login form, will return authorization or redirect page - parser.values.update(auth) - content = self.get_content(parser.action, data=parser.values) - - # If page contains a form#login_form, then we are in the app - # authorization page because the app is not authorized yet, - # otherwise the app already gained permission and twitter sends - # a page that redirects to redirect_url - if 'login_form' in content: - # authorization form post, returns redirect_page - parser = FormParserByID('login_form').feed(content) - self.assertTrue(parser.action) - self.assertTrue(parser.values) - parser.values.update(auth) - redirect_page = self.get_content(parser.action, data=parser.values) - else: - redirect_page = content - - parser = RefreshParser() - parser.feed(redirect_page) - self.assertTrue(parser.value) - - response = self.client.get(self.make_relative(parser.value)) - self.assertEqual(response.status_code, 302) - location = self.make_relative(response['Location']) - login_redirect = setting('LOGIN_REDIRECT_URL') - self.assertTrue(location == login_redirect) diff --git a/social_auth/urls.py b/social_auth/urls.py index dabaae072..640577d87 100644 --- a/social_auth/urls.py +++ b/social_auth/urls.py @@ -1,23 +1,20 @@ """URLs module""" -try: - from django.conf.urls import patterns, url -except ImportError: +try: + from django.conf.urls import patterns, url +except ImportError: # for Django version less then 1.4 from django.conf.urls.defaults import patterns, url - + from social_auth.views import auth, complete, disconnect urlpatterns = patterns('', # authentication - url(r'^login/(?P[^/]+)/$', auth, - name='socialauth_begin'), + url(r'^login/(?P[^/]+)/$', auth, name='socialauth_begin'), url(r'^complete/(?P[^/]+)/$', complete, name='socialauth_complete'), - # XXX: Deprecated, this URLs are deprecated, instead use the login and - # complete ones directly, they will differentiate the user intention - # by checking it's authenticated status association. + # associate url(r'^associate/(?P[^/]+)/$', auth, name='socialauth_associate_begin'), url(r'^associate/complete/(?P[^/]+)/$', complete, diff --git a/social_auth/utils.py b/social_auth/utils.py deleted file mode 100644 index 353b63da8..000000000 --- a/social_auth/utils.py +++ /dev/null @@ -1,284 +0,0 @@ -import time -import random -import hashlib -import urlparse -import urllib -import logging -from urllib2 import urlopen -from cgi import parse_qsl - -from collections import defaultdict - -from django.conf import settings -from django.db.models import Model -from django.db.models.loading import get_model -from django.contrib.contenttypes.models import ContentType -from django.utils.functional import SimpleLazyObject -from django.utils.importlib import import_module - - -try: - random = random.SystemRandom() - using_sysrandom = True -except NotImplementedError: - using_sysrandom = False - - -try: - from django.utils.crypto import get_random_string as random_string -except ImportError: # django < 1.4 - # Implementation borrowed from django 1.4 - def random_string(length=12, - allowed_chars='abcdefghijklmnopqrstuvwxyz' - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): - if not using_sysrandom: - random.seed(hashlib.sha256('%s%s%s' % (random.getstate(), - time.time(), - settings.SECRET_KEY)) - .digest()) - return ''.join([random.choice(allowed_chars) for i in range(length)]) - - -try: - from django.utils.crypto import constant_time_compare as ct_compare -except ImportError: # django < 1.4 - def ct_compare(val1, val2): - if len(val1) != len(val2): - return False - result = 0 - for x, y in zip(val1, val2): - result |= ord(x) ^ ord(y) - return result == 0 - - -try: - from django.utils.functional import empty - empty # placate pyflakes -except ImportError: # django < 1.4 - empty = None - - -get_random_string = random_string -constant_time_compare = ct_compare - -LEAVE_CHARS = getattr(settings, 'SOCIAL_AUTH_LOG_SANITIZE_LEAVE_CHARS', 4) - - -def sanitize_log_data(secret, data=None, leave_characters=LEAVE_CHARS): - """ - Clean private/secret data from log statements and other data. - - Assumes data and secret are strings. Replaces all but the first - `leave_characters` of `secret`, as found in `data`, with '*'. - - If no data is given, all but the first `leave_characters` of secret - are simply replaced and returned. - """ - replace_secret = (secret[:leave_characters] + - (len(secret) - leave_characters) * '*') - - if data: - return data.replace(secret, replace_secret) - - return replace_secret - - -def sanitize_redirect(host, redirect_to): - """ - Given the hostname and an untrusted URL to redirect to, - this method tests it to make sure it isn't garbage/harmful - and returns it, else returns None, similar as how's it done - on django.contrib.auth.views. - - >>> print sanitize_redirect('myapp.com', None) - None - >>> print sanitize_redirect('myapp.com', '') - None - >>> print sanitize_redirect('myapp.com', {}) - None - >>> print sanitize_redirect('myapp.com', 'http://notmyapp.com/path/') - None - >>> print sanitize_redirect('myapp.com', 'http://myapp.com/path/') - http://myapp.com/path/ - >>> print sanitize_redirect('myapp.com', '/path/') - /path/ - """ - # Quick sanity check. - if not redirect_to: - return None - - # Heavier security check, don't allow redirection to a different host. - try: - netloc = urlparse.urlparse(redirect_to)[1] - except TypeError: # not valid redirect_to value - return None - - if netloc and netloc != host: - return None - - return redirect_to - - -def group_backend_by_type(items, key=lambda x: x): - """Group items by backend type.""" - - # Beware of cyclical imports! - from social_auth.backends import \ - get_backends, OpenIdAuth, BaseOAuth, BaseOAuth2 - - result = defaultdict(list) - backends = get_backends() - - for item in items: - backend = backends[key(item)] - if issubclass(backend, OpenIdAuth): - result['openid'].append(item) - elif issubclass(backend, BaseOAuth2): - result['oauth2'].append(item) - elif issubclass(backend, BaseOAuth): - result['oauth'].append(item) - return dict(result) - - -def setting(name, default=None): - """Return setting value for given name or default value.""" - return getattr(settings, name, default) - - -def backend_setting(backend, name, default=None): - """ - Looks for setting value following these rules: - 1. Search for prefixed setting - 2. Search for setting given by name - 3. Return default - """ - backend_name = get_backend_name(backend) - setting_name = '%s_%s' % (backend_name.upper().replace('-', '_'), name) - if hasattr(settings, setting_name): - return setting(setting_name) - elif hasattr(settings, name): - return setting(name) - else: - return default - - -logger = None -if not logger: - logger = logging.getLogger('SocialAuth') - logger.setLevel(logging.DEBUG) - - -def log(level, *args, **kwargs): - """Small wrapper around logger functions.""" - {'debug': logger.debug, - 'error': logger.error, - 'exception': logger.exception, - 'warn': logger.warn}[level](*args, **kwargs) - - -def model_to_ctype(val): - """Converts values that are instance of Model to a dictionary - with enough information to retrieve the instance back later.""" - if isinstance(val, Model): - val = { - 'pk': val.pk, - 'ctype': ContentType.objects.get_for_model(val).pk - } - return val - - -def ctype_to_model(val): - """Converts back the instance saved by model_to_ctype function.""" - if isinstance(val, dict) and 'pk' in val and 'ctype' in val: - ctype = ContentType.objects.get_for_id(val['ctype']) - ModelClass = ctype.model_class() - val = ModelClass.objects.get(pk=val['pk']) - return val - - -def clean_partial_pipeline(request): - """Cleans any data for partial pipeline.""" - name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline') - # Check for key to avoid flagging the session as modified unnecessary - if name in request.session: - request.session.pop(name, None) - - -def url_add_parameters(url, params): - """Adds parameters to URL, parameter will be repeated if already present""" - if params: - fragments = list(urlparse.urlparse(url)) - fragments[4] = urllib.urlencode(parse_qsl(fragments[4]) + - params.items()) - url = urlparse.urlunparse(fragments) - return url - - -class LazyDict(SimpleLazyObject): - """Lazy dict initialization.""" - def __getitem__(self, name): - if self._wrapped is empty: - self._setup() - return self._wrapped[name] - - def __setitem__(self, name, value): - if self._wrapped is empty: - self._setup() - self._wrapped[name] = value - - -def dsa_urlopen(*args, **kwargs): - """Like urllib2.urlopen but sets a timeout defined by - SOCIAL_AUTH_URLOPEN_TIMEOUT setting if defined (and not already in - kwargs).""" - timeout = setting('SOCIAL_AUTH_URLOPEN_TIMEOUT') - if timeout and 'timeout' not in kwargs: - kwargs['timeout'] = timeout - return urlopen(*args, **kwargs) - - -def get_backend_name(backend): - return getattr(getattr(backend, 'AUTH_BACKEND', backend), 'name', None) - - -def get_custom_user_model_for_migrations(): - user_model = getattr(settings, 'SOCIAL_AUTH_USER_MODEL', None) or \ - getattr(settings, 'AUTH_USER_MODEL', None) or 'auth.User' - if user_model != 'auth.User': - # In case of having a proxy model defined as USER_MODEL - # We use auth.User instead to prevent migration errors - # Since proxy models aren't present in migrations - if get_model(*user_model.split('.'))._meta.proxy: - user_model = 'auth.User' - return user_model - - -def custom_user_frozen_models(user_model): - migration_name = getattr(settings, 'INITIAL_CUSTOM_USER_MIGRATION', - '0001_initial.py') - if user_model != 'auth.User': - from south.migration.base import Migrations - from south.exceptions import NoMigrations - from south.creator.freezer import freeze_apps - user_app, user_model = user_model.split('.') - try: - user_migrations = Migrations(user_app) - except NoMigrations: - extra_model = freeze_apps(user_app) - else: - initial_user_migration = user_migrations.migration(migration_name) - extra_model = initial_user_migration.migration_class().models - else: - extra_model = {} - return extra_model - - -def module_member(name): - mod, member = name.rsplit('.', 1) - module = import_module(mod) - return getattr(module, member) - - -if __name__ == '__main__': - import doctest - doctest.testmod() diff --git a/social_auth/views.py b/social_auth/views.py index c2dbabea1..eb80bc82b 100644 --- a/social_auth/views.py +++ b/social_auth/views.py @@ -1,190 +1,41 @@ -"""Views +from django.conf import settings +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.views.decorators.csrf import csrf_exempt, csrf_protect +from django.contrib.auth.decorators import login_required +from django.views.decorators.http import require_POST -Notes: - * Some views are marked to avoid csrf tocken check because they rely - on third party providers that (if using POST) won't be sending csrf - token back. -""" -from urllib2 import quote +from social.utils import setting_name +from social.actions import do_auth, do_complete, do_disconnect +from social.strategies.utils import get_strategy +from social.apps.django_app.utils import strategy, BACKENDS, STORAGE +from social.apps.django_app.views import _do_login -from django.http import HttpResponseRedirect, HttpResponse -from django.contrib.auth import login, REDIRECT_FIELD_NAME -from django.contrib.auth.decorators import login_required -from django.contrib import messages -from django.views.decorators.csrf import csrf_exempt -from social_auth.utils import sanitize_redirect, setting, \ - backend_setting, clean_partial_pipeline -from social_auth.decorators import dsa_view, disconnect_view +STRATEGY = getattr(settings, setting_name('STRATEGY'), + 'social_auth.strategy.DSAStrategy') -DEFAULT_REDIRECT = setting('SOCIAL_AUTH_LOGIN_REDIRECT_URL', - setting('LOGIN_REDIRECT_URL')) -LOGIN_ERROR_URL = setting('LOGIN_ERROR_URL', setting('LOGIN_URL')) -PIPELINE_KEY = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline') +def load_strategy(*args, **kwargs): + return get_strategy(BACKENDS, STRATEGY, STORAGE, *args, **kwargs) -@dsa_view(setting('SOCIAL_AUTH_COMPLETE_URL_NAME', 'socialauth_complete')) +@strategy('socialauth_complete', load_strategy=load_strategy) def auth(request, backend): - """Start authentication process""" - return auth_process(request, backend) + return do_auth(request.strategy, redirect_name=REDIRECT_FIELD_NAME) @csrf_exempt -@dsa_view() +@strategy('socialauth_complete', load_strategy=load_strategy) def complete(request, backend, *args, **kwargs): - """Authentication complete view, override this view if transaction - management doesn't suit your needs.""" - if request.user.is_authenticated(): - return associate_complete(request, backend, *args, **kwargs) - else: - return complete_process(request, backend, *args, **kwargs) - - -@login_required -def associate_complete(request, backend, *args, **kwargs): - """Authentication complete process""" - # pop redirect value before the session is trashed on login() - redirect_value = request.session.get(REDIRECT_FIELD_NAME, '') - user = auth_complete(request, backend, request.user, *args, **kwargs) - - if not user: - url = backend_setting(backend, 'LOGIN_ERROR_URL', LOGIN_ERROR_URL) - elif isinstance(user, HttpResponse): - return user - else: - url = redirect_value or \ - backend_setting(backend, - 'SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL') or \ - DEFAULT_REDIRECT - return HttpResponseRedirect(url) + return do_complete(request.strategy, _do_login, request.user, + redirect_name=REDIRECT_FIELD_NAME, request=request, + *args, **kwargs) @login_required -@dsa_view() -@disconnect_view +@strategy(load_strategy=load_strategy) +@require_POST +@csrf_protect def disconnect(request, backend, association_id=None): - """Disconnects given backend from current logged in user.""" - backend.disconnect(request.user, association_id) - url = request.REQUEST.get(REDIRECT_FIELD_NAME, '') or \ - backend_setting(backend, 'SOCIAL_AUTH_DISCONNECT_REDIRECT_URL') or \ - DEFAULT_REDIRECT - return HttpResponseRedirect(url) - - -def auth_process(request, backend): - """Authenticate using social backend""" - data = request.POST if request.method == 'POST' else request.GET - - # Save extra data into session. - for field_name in setting('SOCIAL_AUTH_FIELDS_STORED_IN_SESSION', []): - if field_name in data: - request.session[field_name] = data[field_name] - - # Save any defined next value into session - if REDIRECT_FIELD_NAME in data: - # Check and sanitize a user-defined GET/POST next field value - redirect = data[REDIRECT_FIELD_NAME] - if setting('SOCIAL_AUTH_SANITIZE_REDIRECTS', True): - redirect = sanitize_redirect(request.get_host(), redirect) - request.session[REDIRECT_FIELD_NAME] = redirect or DEFAULT_REDIRECT - - # Clean any partial pipeline info before starting the process - clean_partial_pipeline(request) - - if backend.uses_redirect: - return HttpResponseRedirect(backend.auth_url()) - else: - return HttpResponse(backend.auth_html(), - content_type='text/html;charset=UTF-8') - - -def complete_process(request, backend, *args, **kwargs): - """Authentication complete process""" - # pop redirect value before the session is trashed on login() - redirect_value = request.session.get(REDIRECT_FIELD_NAME, '') or \ - request.REQUEST.get(REDIRECT_FIELD_NAME, '') - user = auth_complete(request, backend, *args, **kwargs) - - if isinstance(user, HttpResponse): - return user - - if not user and request.user.is_authenticated(): - return HttpResponseRedirect(redirect_value) - - msg = None - if user: - if getattr(user, 'is_active', True): - # catch is_new flag before login() might reset the instance - is_new = getattr(user, 'is_new', False) - login(request, user) - # user.social_user is the used UserSocialAuth instance defined - # in authenticate process - social_user = user.social_user - if redirect_value: - request.session[REDIRECT_FIELD_NAME] = redirect_value or \ - DEFAULT_REDIRECT - - if setting('SOCIAL_AUTH_SESSION_EXPIRATION', True): - # Set session expiration date if present and not disabled by - # setting. Use last social-auth instance for current provider, - # users can associate several accounts with a same provider. - expiration = social_user.expiration_datetime() - if expiration: - try: - request.session.set_expiry(expiration) - except OverflowError: - # Handle django time zone overflow, set default expiry. - request.session.set_expiry(None) - - # store last login backend name in session - request.session['social_auth_last_login_backend'] = \ - social_user.provider - - # Remove possible redirect URL from session, if this is a new - # account, send him to the new-users-page if defined. - new_user_redirect = backend_setting(backend, - 'SOCIAL_AUTH_NEW_USER_REDIRECT_URL') - if new_user_redirect and is_new: - url = new_user_redirect - else: - url = redirect_value or \ - backend_setting(backend, - 'SOCIAL_AUTH_LOGIN_REDIRECT_URL') or \ - DEFAULT_REDIRECT - else: - msg = setting('SOCIAL_AUTH_INACTIVE_USER_MESSAGE', None) - url = backend_setting(backend, 'SOCIAL_AUTH_INACTIVE_USER_URL', - LOGIN_ERROR_URL) - else: - msg = setting('LOGIN_ERROR_MESSAGE', None) - url = backend_setting(backend, 'LOGIN_ERROR_URL', LOGIN_ERROR_URL) - if msg: - messages.error(request, msg) - - if redirect_value and redirect_value != url: - redirect_value = quote(redirect_value) - if '?' in url: - url += '&%s=%s' % (REDIRECT_FIELD_NAME, redirect_value) - else: - url += '?%s=%s' % (REDIRECT_FIELD_NAME, redirect_value) - return HttpResponseRedirect(url) - - -def auth_complete(request, backend, user=None, *args, **kwargs): - """Complete auth process. Return authenticated user or None.""" - if user and not user.is_authenticated(): - user = None - - if request.session.get(PIPELINE_KEY): - data = request.session.pop(PIPELINE_KEY) - kwargs = kwargs.copy() - if user: - kwargs['user'] = user - idx, xargs, xkwargs = backend.from_session_dict(data, request=request, - *args, **kwargs) - if 'backend' in xkwargs and \ - xkwargs['backend'].name == backend.AUTH_BACKEND.name: - return backend.continue_pipeline(pipeline_index=idx, - *xargs, **xkwargs) - return backend.auth_complete(user=user, request=request, *args, **kwargs) + return do_disconnect(request.strategy, request.user, association_id, + redirect_name=REDIRECT_FIELD_NAME)