django-social-auth-0.7.23/0000755000175000017500000000000012135257233015135 5ustar omabomab00000000000000django-social-auth-0.7.23/PKG-INFO0000644000175000017500000002070712135257233016240 0ustar omabomab00000000000000Metadata-Version: 1.1 Name: django-social-auth Version: 0.7.23 Summary: Django social authentication made simple. Home-page: https://github.com/omab/django-social-auth Author: Matías Aguirre Author-email: matiasaguirre@gmail.com License: BSD Description: Django Social Auth ================== Django Social Auth is an easy way to setup social authentication/authorization mechanism for Django projects. Crafted using base code from django-twitter-oauth_ and django-openid-auth_, it implements a common interface to define new authentication providers from third parties. You can view this app's documentation on `Read the Docs`_ too. .. contents:: Table of Contents Features -------- This application provides user registration and login using social site credentials. Some features are: - Registration and login with social sites using the following providers at the moment: * `Google OpenID`_ * `Google OAuth`_ * `Google OAuth2`_ * `Yahoo OpenID`_ * OpenId_ like myOpenID_ * `Twitter OAuth`_ * `Facebook OAuth`_ Some contributions added support for: * `DISQUS OAuth`_ * `LiveJournal OpenID`_ * `Orkut OAuth`_ * `Linkedin OAuth`_ * `Foursquare OAuth2`_ * `GitHub OAuth`_ * `Dropbox OAuth`_ * `Flickr OAuth`_ * `Vkontakte OAuth`_ * `MSN Live Connect OAuth2`_ * `Skyrock OAuth`_ * `Yahoo OAuth`_ * `Evernote OAuth`_ * `Mail.ru OAuth`_ * `Odnoklassniki OAuth`_ * `Mixcloud OAuth2`_ * `BitBucket OAuth`_ * `Douban OAuth`_ * `Fitbit OAuth`_ * `Instagram OAuth2`_ * `Twilio`_ * `Weibo OAuth2`_ * `Yandex OpenId`_ * `Shopify OAuth2`_ * `StockTwits OAuth2`_ * `Stackoverflow OAuth2`_ - Basic user data population and signaling to allows custom fields values from providers' responses - Multiple social account associations to a single user - Custom User model override if needed (`auth.User`_ by default) - Extensible pipeline to handle authentication/association mechanism Demo ---- There's a demo at http://social.matiasaguirre.net/. Note: It lacks some backends' support at the moment. Contact ------- Join the `django-social-auth discussion list`_ and bring any questions or suggestions that would improve this application. Also join the IRC channel ``#django-social-auth`` on Freenode server. Documentation ------------- Extensive documentation at `Read the Docs`_. Dependencies ------------ Dependencies that **must** be met to use the application: - OpenId_ support depends on python-openid_ - OAuth_ support depends on python-oauth2_ - Several backends demands application registration on their corresponding sites Installation ------------ From pypi_:: $ pip install django-social-auth or:: $ easy_install django-social-auth or clone from github_:: $ git clone git://github.com/omab/django-social-auth.git and add social_auth to PYTHONPATH:: $ export PYTHONPATH=$PYTHONPATH:$(pwd)/django-social-auth/ or:: $ cd django-social-auth $ sudo python setup.py install Copyrights and Licence ---------------------- ``django-social-auth`` is protected by BSD licence. Some bits were derived from others' work and copyrighted by: - django-twitter-oauth:: Original Copyright goes to Henrik Lied (henriklied) Code borrowed from https://github.com/henriklied/django-twitter-oauth - django-openid-auth:: django-openid-auth - OpenID integration for django.contrib.auth Copyright (C) 2007 Simon Willison Copyright (C) 2008-2010 Canonical Ltd. .. _django-twitter-oauth: https://github.com/henriklied/django-twitter-oauth .. _django-openid-auth: https://launchpad.net/django-openid-auth .. _Read the Docs: http://django-social-auth.readthedocs.org/ .. _Google OpenID: https://developers.google.com/accounts/docs/OpenID .. _Google OAuth: https://developers.google.com/accounts/docs/OAuth .. _Google OAuth2: https://developers.google.com/accounts/docs/OAuth2 .. _Yahoo OpenID: http://openid.yahoo.com/ .. _OpenId: http://openid.net/ .. _myOpenID: https://www.myopenid.com/ .. _Twitter OAuth: http://dev.twitter.com/pages/oauth_faq .. _Facebook OAuth: http://developers.facebook.com/docs/authentication/ .. _DISQUS OAuth: http://disqus.com/api/docs/auth/ .. _LiveJournal OpenID: http://www.livejournal.com/support/faqbrowse.bml?faqid=283 .. _Orkut OAuth: http://code.google.com/apis/orkut/docs/rest/developers_guide_protocol.html#Authenticating .. _Linkedin OAuth: https://www.linkedin.com/secure/developer .. _Foursquare OAuth2: https://developer.foursquare.com/docs/oauth.html .. _GitHub OAuth: http://developer.github.com/v3/oauth/ .. _Dropbox OAuth: https://www.dropbox.com/developers_beta/reference/api .. _Flickr OAuth: http://www.flickr.com/services/api/ .. _Vkontakte OAuth: http://vk.com/developers.php?oid=-1&p=%D0%90%D0%B2%D1%82%D0%BE%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2 .. _MSN Live Connect OAuth2: http://msdn.microsoft.com/en-us/library/live/hh243647.aspx .. _Skyrock OAuth: http://www.skyrock.com/developer/ .. _Yahoo OAuth: http://developer.yahoo.com/oauth/guide/oauth-auth-flow.html .. _Evernote OAuth: http://dev.evernote.com/documentation/cloud/chapters/Authentication.php .. _Mail.ru OAuth: http://api.mail.ru/docs/guides/oauth/ .. _Odnoklassniki OAuth: http://dev.odnoklassniki.ru/wiki/display/ok/The+OAuth+2.0+Protocol .. _Mixcloud OAuth2: http://www.mixcloud.com/developers/documentation/#authorization .. _BitBucket OAuth: https://confluence.atlassian.com/display/BITBUCKET/OAuth+Consumers .. _Douban OAuth: http://www.douban.com/service/apidoc/auth .. _Fitbit OAuth: https://wiki.fitbit.com/display/API/OAuth+Authentication+in+the+Fitbit+API .. _Instagram OAuth2: http://instagram.com/developer/authentication/ .. _Twilio: https://www.twilio.com/user/account/connect/apps .. _Weibo OAuth2: http://open.weibo.com/wiki/Oauth2 .. _Yandex OpenId: http://openid.yandex.ru/ .. _Shopify OAuth2: http://api.shopify.com/authentication.html .. _StockTwits OAuth2: http://stocktwits.com/developers/docs/authentication .. _auth.User: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py#L186 .. _python-openid: http://pypi.python.org/pypi/python-openid/ .. _python-oauth2: https://github.com/simplegeo/python-oauth2 .. _OAuth: http://oauth.net/ .. _pypi: http://pypi.python.org/pypi/django-social-auth/ .. _github: https://github.com/omab/django-social-auth .. _django-social-auth discussion list: https://groups.google.com/forum/?fromgroups#!forum/django-social-auth .. _Stackoverflow OAuth2: http://api.stackexchange.com/ Keywords: django,openid,oauth,social auth,application Platform: UNKNOWN Classifier: Framework :: Django Classifier: Development Status :: 4 - Beta Classifier: Topic :: Internet Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: Developers Classifier: Environment :: Web Environment Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 django-social-auth-0.7.23/django_social_auth.egg-info/0000755000175000017500000000000012135257233022444 5ustar omabomab00000000000000django-social-auth-0.7.23/django_social_auth.egg-info/not-zip-safe0000644000175000017500000000000112135257226024674 0ustar omabomab00000000000000 django-social-auth-0.7.23/django_social_auth.egg-info/requires.txt0000644000175000017500000000006012135257226025042 0ustar omabomab00000000000000Django>=1.2.5 oauth2>=1.5.167 python-openid>=2.2django-social-auth-0.7.23/django_social_auth.egg-info/PKG-INFO0000644000175000017500000002070712135257226023551 0ustar omabomab00000000000000Metadata-Version: 1.1 Name: django-social-auth Version: 0.7.23 Summary: Django social authentication made simple. Home-page: https://github.com/omab/django-social-auth Author: Matías Aguirre Author-email: matiasaguirre@gmail.com License: BSD Description: Django Social Auth ================== Django Social Auth is an easy way to setup social authentication/authorization mechanism for Django projects. Crafted using base code from django-twitter-oauth_ and django-openid-auth_, it implements a common interface to define new authentication providers from third parties. You can view this app's documentation on `Read the Docs`_ too. .. contents:: Table of Contents Features -------- This application provides user registration and login using social site credentials. Some features are: - Registration and login with social sites using the following providers at the moment: * `Google OpenID`_ * `Google OAuth`_ * `Google OAuth2`_ * `Yahoo OpenID`_ * OpenId_ like myOpenID_ * `Twitter OAuth`_ * `Facebook OAuth`_ Some contributions added support for: * `DISQUS OAuth`_ * `LiveJournal OpenID`_ * `Orkut OAuth`_ * `Linkedin OAuth`_ * `Foursquare OAuth2`_ * `GitHub OAuth`_ * `Dropbox OAuth`_ * `Flickr OAuth`_ * `Vkontakte OAuth`_ * `MSN Live Connect OAuth2`_ * `Skyrock OAuth`_ * `Yahoo OAuth`_ * `Evernote OAuth`_ * `Mail.ru OAuth`_ * `Odnoklassniki OAuth`_ * `Mixcloud OAuth2`_ * `BitBucket OAuth`_ * `Douban OAuth`_ * `Fitbit OAuth`_ * `Instagram OAuth2`_ * `Twilio`_ * `Weibo OAuth2`_ * `Yandex OpenId`_ * `Shopify OAuth2`_ * `StockTwits OAuth2`_ * `Stackoverflow OAuth2`_ - Basic user data population and signaling to allows custom fields values from providers' responses - Multiple social account associations to a single user - Custom User model override if needed (`auth.User`_ by default) - Extensible pipeline to handle authentication/association mechanism Demo ---- There's a demo at http://social.matiasaguirre.net/. Note: It lacks some backends' support at the moment. Contact ------- Join the `django-social-auth discussion list`_ and bring any questions or suggestions that would improve this application. Also join the IRC channel ``#django-social-auth`` on Freenode server. Documentation ------------- Extensive documentation at `Read the Docs`_. Dependencies ------------ Dependencies that **must** be met to use the application: - OpenId_ support depends on python-openid_ - OAuth_ support depends on python-oauth2_ - Several backends demands application registration on their corresponding sites Installation ------------ From pypi_:: $ pip install django-social-auth or:: $ easy_install django-social-auth or clone from github_:: $ git clone git://github.com/omab/django-social-auth.git and add social_auth to PYTHONPATH:: $ export PYTHONPATH=$PYTHONPATH:$(pwd)/django-social-auth/ or:: $ cd django-social-auth $ sudo python setup.py install Copyrights and Licence ---------------------- ``django-social-auth`` is protected by BSD licence. Some bits were derived from others' work and copyrighted by: - django-twitter-oauth:: Original Copyright goes to Henrik Lied (henriklied) Code borrowed from https://github.com/henriklied/django-twitter-oauth - django-openid-auth:: django-openid-auth - OpenID integration for django.contrib.auth Copyright (C) 2007 Simon Willison Copyright (C) 2008-2010 Canonical Ltd. .. _django-twitter-oauth: https://github.com/henriklied/django-twitter-oauth .. _django-openid-auth: https://launchpad.net/django-openid-auth .. _Read the Docs: http://django-social-auth.readthedocs.org/ .. _Google OpenID: https://developers.google.com/accounts/docs/OpenID .. _Google OAuth: https://developers.google.com/accounts/docs/OAuth .. _Google OAuth2: https://developers.google.com/accounts/docs/OAuth2 .. _Yahoo OpenID: http://openid.yahoo.com/ .. _OpenId: http://openid.net/ .. _myOpenID: https://www.myopenid.com/ .. _Twitter OAuth: http://dev.twitter.com/pages/oauth_faq .. _Facebook OAuth: http://developers.facebook.com/docs/authentication/ .. _DISQUS OAuth: http://disqus.com/api/docs/auth/ .. _LiveJournal OpenID: http://www.livejournal.com/support/faqbrowse.bml?faqid=283 .. _Orkut OAuth: http://code.google.com/apis/orkut/docs/rest/developers_guide_protocol.html#Authenticating .. _Linkedin OAuth: https://www.linkedin.com/secure/developer .. _Foursquare OAuth2: https://developer.foursquare.com/docs/oauth.html .. _GitHub OAuth: http://developer.github.com/v3/oauth/ .. _Dropbox OAuth: https://www.dropbox.com/developers_beta/reference/api .. _Flickr OAuth: http://www.flickr.com/services/api/ .. _Vkontakte OAuth: http://vk.com/developers.php?oid=-1&p=%D0%90%D0%B2%D1%82%D0%BE%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2 .. _MSN Live Connect OAuth2: http://msdn.microsoft.com/en-us/library/live/hh243647.aspx .. _Skyrock OAuth: http://www.skyrock.com/developer/ .. _Yahoo OAuth: http://developer.yahoo.com/oauth/guide/oauth-auth-flow.html .. _Evernote OAuth: http://dev.evernote.com/documentation/cloud/chapters/Authentication.php .. _Mail.ru OAuth: http://api.mail.ru/docs/guides/oauth/ .. _Odnoklassniki OAuth: http://dev.odnoklassniki.ru/wiki/display/ok/The+OAuth+2.0+Protocol .. _Mixcloud OAuth2: http://www.mixcloud.com/developers/documentation/#authorization .. _BitBucket OAuth: https://confluence.atlassian.com/display/BITBUCKET/OAuth+Consumers .. _Douban OAuth: http://www.douban.com/service/apidoc/auth .. _Fitbit OAuth: https://wiki.fitbit.com/display/API/OAuth+Authentication+in+the+Fitbit+API .. _Instagram OAuth2: http://instagram.com/developer/authentication/ .. _Twilio: https://www.twilio.com/user/account/connect/apps .. _Weibo OAuth2: http://open.weibo.com/wiki/Oauth2 .. _Yandex OpenId: http://openid.yandex.ru/ .. _Shopify OAuth2: http://api.shopify.com/authentication.html .. _StockTwits OAuth2: http://stocktwits.com/developers/docs/authentication .. _auth.User: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py#L186 .. _python-openid: http://pypi.python.org/pypi/python-openid/ .. _python-oauth2: https://github.com/simplegeo/python-oauth2 .. _OAuth: http://oauth.net/ .. _pypi: http://pypi.python.org/pypi/django-social-auth/ .. _github: https://github.com/omab/django-social-auth .. _django-social-auth discussion list: https://groups.google.com/forum/?fromgroups#!forum/django-social-auth .. _Stackoverflow OAuth2: http://api.stackexchange.com/ Keywords: django,openid,oauth,social auth,application Platform: UNKNOWN Classifier: Framework :: Django Classifier: Development Status :: 4 - Beta Classifier: Topic :: Internet Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: Developers Classifier: Environment :: Web Environment Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 django-social-auth-0.7.23/django_social_auth.egg-info/top_level.txt0000644000175000017500000000001412135257226025173 0ustar omabomab00000000000000social_auth django-social-auth-0.7.23/django_social_auth.egg-info/dependency_links.txt0000644000175000017500000000000112135257226026514 0ustar omabomab00000000000000 django-social-auth-0.7.23/django_social_auth.egg-info/SOURCES.txt0000644000175000017500000000676112135257226024344 0ustar omabomab00000000000000README.rst setup.py django_social_auth.egg-info/PKG-INFO django_social_auth.egg-info/SOURCES.txt django_social_auth.egg-info/dependency_links.txt django_social_auth.egg-info/not-zip-safe django_social_auth.egg-info/requires.txt django_social_auth.egg-info/top_level.txt social_auth/__init__.py social_auth/admin.py social_auth/context_processors.py social_auth/decorators.py social_auth/exceptions.py social_auth/fields.py social_auth/middleware.py social_auth/models.py social_auth/signals.py social_auth/store.py social_auth/urls.py social_auth/utils.py social_auth/views.py social_auth/backends/__init__.py social_auth/backends/browserid.py social_auth/backends/facebook.py social_auth/backends/google.py social_auth/backends/reddit.py social_auth/backends/steam.py social_auth/backends/stripe.py social_auth/backends/twitter.py social_auth/backends/utils.py social_auth/backends/yahoo.py social_auth/backends/contrib/__init__.py social_auth/backends/contrib/angel.py social_auth/backends/contrib/behance.py social_auth/backends/contrib/bitbucket.py social_auth/backends/contrib/dailymotion.py social_auth/backends/contrib/disqus.py social_auth/backends/contrib/douban.py social_auth/backends/contrib/dropbox.py social_auth/backends/contrib/evernote.py social_auth/backends/contrib/fitbit.py social_auth/backends/contrib/flickr.py social_auth/backends/contrib/foursquare.py social_auth/backends/contrib/gae.py social_auth/backends/contrib/github.py social_auth/backends/contrib/instagram.py social_auth/backends/contrib/linkedin.py social_auth/backends/contrib/live.py social_auth/backends/contrib/livejournal.py social_auth/backends/contrib/mailru.py social_auth/backends/contrib/mendeley.py social_auth/backends/contrib/mixcloud.py social_auth/backends/contrib/odnoklassniki.py social_auth/backends/contrib/orkut.py social_auth/backends/contrib/rdio.py social_auth/backends/contrib/readability.py social_auth/backends/contrib/shopify.py social_auth/backends/contrib/skyrock.py social_auth/backends/contrib/soundcloud.py social_auth/backends/contrib/stackoverflow.py social_auth/backends/contrib/stocktwits.py social_auth/backends/contrib/tripit.py social_auth/backends/contrib/tumblr.py social_auth/backends/contrib/twilio.py social_auth/backends/contrib/vkontakte.py social_auth/backends/contrib/weibo.py social_auth/backends/contrib/xing.py social_auth/backends/contrib/yahoo.py social_auth/backends/contrib/yammer.py social_auth/backends/contrib/yammer_staging.py social_auth/backends/contrib/yandex.py social_auth/backends/pipeline/__init__.py social_auth/backends/pipeline/associate.py social_auth/backends/pipeline/misc.py social_auth/backends/pipeline/social.py social_auth/backends/pipeline/user.py social_auth/db/__init__.py social_auth/db/base.py social_auth/db/django_models.py social_auth/db/mongoengine_models.py social_auth/locale/ru/LC_MESSAGES/django.mo social_auth/locale/ru/LC_MESSAGES/django.po social_auth/locale/tr/LC_MESSAGES/django.mo social_auth/locale/tr/LC_MESSAGES/django.po social_auth/management/__init__.py social_auth/management/commands/__init__.py social_auth/management/commands/clean_associations.py social_auth/management/commands/clean_nonces.py social_auth/migrations/0001_initial.py social_auth/migrations/0002_auto__add_unique_nonce_timestamp_salt_server_url__add_unique_associati.py social_auth/migrations/__init__.py social_auth/tests/__init__.py social_auth/tests/base.py social_auth/tests/client.py social_auth/tests/facebook.py social_auth/tests/google.py social_auth/tests/odnoklassniki.py social_auth/tests/twitter.pydjango-social-auth-0.7.23/social_auth/0000755000175000017500000000000012135257233017430 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/signals.py0000644000175000017500000000050512127316474021446 0ustar omabomab00000000000000from 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']) django-social-auth-0.7.23/social_auth/tests/0000755000175000017500000000000012135257233020572 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/tests/twitter.py0000644000175000017500000000611212127316474022652 0ustar omabomab00000000000000from 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 self.assertTrue(self.user) self.assertTrue(self.passwd) class TwitterTestLogin(TwitterTestCase): @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_succeful(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) django-social-auth-0.7.23/social_auth/tests/client.py0000644000175000017500000001131412131430347022415 0ustar omabomab00000000000000import 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.utils.urlopen') def login(self, user, mock_urlopen, 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] ] 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 django-social-auth-0.7.23/social_auth/tests/odnoklassniki.py0000644000175000017500000002057212127316474024026 0ustar omabomab00000000000000# -*- coding:utf-8 -*- from __future__ import unicode_literals 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): def setUp(self): self.assertTrue(hasattr(settings, 'ODNOKLASSNIKI_APP_ID'), "You need to have ODNOKLASSNIKI_APP_ID in settings to test iframe app") self.assertTrue(hasattr(settings, 'ODNOKLASSNIKI_SANDBOX_DEV_USERNAME'), "You need to have ODNOKLASSNIKI_SANDBOX_DEV_USERNAME in settings to test iframe app") self.assertTrue(hasattr(settings, 'ODNOKLASSNIKI_SANDBOX_DEV_PASSWORD'), "You need to have ODNOKLASSNIKI_SANDBOX_DEV_PASSWORD in settings to test iframe app") 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): def setUp(self): self.assertTrue(hasattr(settings, 'ODNOKLASSNIKI_OAUTH2_CLIENT_KEY'), "You need to have ODNOKLASSNIKI_OAUTH2_CLIENT_KEY in settings to test odnoklassniki OAuth") self.assertTrue(hasattr(settings, 'ODNOKLASSNIKI_TEST_USERNAME'), "You need to have ODNOKLASSNIKI_TEST_USERNAME in settings to test odnoklassniki OAuth") self.assertTrue(hasattr(settings, 'ODNOKLASSNIKI_TEST_PASSWORD'), "You need to have ODNOKLASSNIKI_TEST_PASSWORD in settings to test odnoklassniki OAuth") 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) django-social-auth-0.7.23/social_auth/tests/facebook.py0000644000175000017500000000640012127316474022721 0ustar omabomab00000000000000import re 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'): self.SERVER_NAME = Site.objects.get_current() super(FacebookTestCase, self).__init__(methodName) name = 'facebook' def setUp(self, *args, **kwargs): 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 self.assertTrue(self.user) self.assertTrue(self.passwd) REDIRECT_RE = re.compile('window.location.replace\("(.*)"\);') class FacebookTestLogin(FacebookTestCase): 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'])) django-social-auth-0.7.23/social_auth/tests/__init__.py0000644000175000017500000000063612127316474022714 0ustar omabomab00000000000000from 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 *django-social-auth-0.7.23/social_auth/tests/base.py0000644000175000017500000001432412127316474022066 0ustar omabomab00000000000000import 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 django-social-auth-0.7.23/social_auth/tests/google.py0000644000175000017500000000613012127316474022424 0ustar omabomab00000000000000import re 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 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' 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'])) django-social-auth-0.7.23/social_auth/store.py0000644000175000017500000000325612127316474021150 0ustar omabomab00000000000000"""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) django-social-auth-0.7.23/social_auth/models.py0000644000175000017500000000103112127316474021264 0ustar omabomab00000000000000"""Social auth models""" import types from django.utils.importlib import import_module from social_auth.utils import setting SOCIAL_AUTH_MODELS_MODULE = import_module(setting('SOCIAL_AUTH_MODELS', 'social_auth.db.django_models')) 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))) django-social-auth-0.7.23/social_auth/db/0000755000175000017500000000000012135257233020015 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/db/mongoengine_models.py0000644000175000017500000000614112133321477024241 0ustar omabomab00000000000000""" 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.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_MODULE, USER_MODEL_NAME = ( setting('SOCIAL_AUTH_USER_MODEL') or setting('AUTH_USER_MODEL') or '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 django-social-auth-0.7.23/social_auth/db/django_models.py0000644000175000017500000000625612127316474023211 0ustar omabomab00000000000000"""Django ORM models for Social Auth""" from django.db import models 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 UserSocialAuth._meta.get_field('user').rel.to 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 django-social-auth-0.7.23/social_auth/db/__init__.py0000644000175000017500000000000012127316474022120 0ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/db/base.py0000644000175000017500000002054112127317036021302 0ustar omabomab00000000000000"""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 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) 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" 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) django-social-auth-0.7.23/social_auth/fields.py0000644000175000017500000000357412127316474021265 0ustar omabomab00000000000000from 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 django-social-auth-0.7.23/social_auth/admin.py0000644000175000017500000000221512127316474021076 0ustar omabomab00000000000000"""Admin settings""" from social_auth.utils import setting 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 class UserSocialAuthOption(admin.ModelAdmin): """Social Auth user options""" list_display = ('id', 'user', 'provider', 'uid') search_fields = ('user__first_name', 'user__last_name', 'user__email', 'user__username') 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) django-social-auth-0.7.23/social_auth/urls.py0000644000175000017500000000216612127316474021000 0ustar omabomab00000000000000"""URLs module""" 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'^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. url(r'^associate/(?P[^/]+)/$', auth, name='socialauth_associate_begin'), url(r'^associate/complete/(?P[^/]+)/$', complete, name='socialauth_associate_complete'), # disconnection url(r'^disconnect/(?P[^/]+)/$', disconnect, name='socialauth_disconnect'), url(r'^disconnect/(?P[^/]+)/(?P[^/]+)/$', disconnect, name='socialauth_disconnect_individual'), ) django-social-auth-0.7.23/social_auth/locale/0000755000175000017500000000000012135257233020667 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/locale/ru/0000755000175000017500000000000012135257233021315 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/locale/ru/LC_MESSAGES/0000755000175000017500000000000012135257233023102 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/locale/ru/LC_MESSAGES/django.po0000644000175000017500000000255112127316474024713 0ustar omabomab00000000000000# 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 уже используется." django-social-auth-0.7.23/social_auth/locale/ru/LC_MESSAGES/django.mo0000644000175000017500000000174312127316474024712 0ustar omabomab00000000000000Dl$).!';E:bAuthentication failed: %sAuthentication process was cancelledThis %(provider)s account already in use.Unknown authentication error. Try again later.Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2012-02-17 15:25+0400 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Ошибка авторизации: %sПроцесс авторизации был прерванЭтот аккаунт %(provider)s уже используется.Непредвиденная ошибка авторизации. Попробуйте позже.django-social-auth-0.7.23/social_auth/locale/tr/0000755000175000017500000000000012135257233021314 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/locale/tr/LC_MESSAGES/0000755000175000017500000000000012135257233023101 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/locale/tr/LC_MESSAGES/django.po0000644000175000017500000000176712127316474024722 0ustar omabomab00000000000000# 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." django-social-auth-0.7.23/social_auth/locale/tr/LC_MESSAGES/django.mo0000644000175000017500000000126512127316474024710 0ustar omabomab00000000000000<\pq$)p K#l$Authentication failed: %sAuthentication process was cancelledThis %(provider)s account already in use.Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2012-05-15 18:58+0300 PO-Revision-Date: 2012-05-15 22:02+0200 Last-Translator: Cihan Okyay Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0 Kimlik denetimi başarısız: %sKimlik denetimi işlemi kapatıldıBu %(provider)s hesabı kullanımda.django-social-auth-0.7.23/social_auth/exceptions.py0000644000175000017500000000555312127316474022177 0ustar omabomab00000000000000from 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 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 u'Authentication process canceled' class AuthUnknownError(AuthException): """Unknown auth process error.""" def __unicode__(self): msg = super(AuthUnknownError, self).__unicode__() return u'An unknown error happened while authenticating %s' % msg class AuthTokenError(AuthException): """Auth token error.""" def __unicode__(self): msg = super(AuthTokenError, self).__unicode__() return 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 u'Missing needed parameter %s' % self.parameter class AuthStateMissing(AuthException): """State parameter is incorrect.""" def __unicode__(self): return u'Session value state missing.' class AuthStateForbidden(AuthException): """State parameter is incorrect.""" def __unicode__(self): return 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 u'User revoke access to the token' django-social-auth-0.7.23/social_auth/management/0000755000175000017500000000000012135257233021544 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/management/commands/0000755000175000017500000000000012135257233023345 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/management/commands/clean_associations.py0000644000175000017500000000216612127316474027571 0ustar omabomab00000000000000import 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' django-social-auth-0.7.23/social_auth/management/commands/__init__.py0000644000175000017500000000000012127316474025450 0ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/management/commands/clean_nonces.py0000644000175000017500000000106712127316474026356 0ustar omabomab00000000000000import 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' django-social-auth-0.7.23/social_auth/management/__init__.py0000644000175000017500000000000012127316474023647 0ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/utils.py0000644000175000017500000001771712127316474021163 0ustar omabomab00000000000000import 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.contrib.contenttypes.models import ContentType from django.utils.functional import SimpleLazyObject 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 def sanitize_log_data(secret, data=None, leave_characters=4): """ 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 custom_user_frozen_models(): user_model = getattr(settings, 'SOCIAL_AUTH_USER_MODEL', None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' 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 if __name__ == '__main__': import doctest doctest.testmod() django-social-auth-0.7.23/social_auth/backends/0000755000175000017500000000000012135257233021202 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/backends/steam.py0000644000175000017500000000420312127316474022670 0ustar omabomab00000000000000"""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 } django-social-auth-0.7.23/social_auth/backends/reddit.py0000644000175000017500000000456512127316474023045 0ustar omabomab00000000000000import 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 } django-social-auth-0.7.23/social_auth/backends/twitter.py0000644000175000017500000000637512127316474023275 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/stripe.py0000644000175000017500000000525312127316474023073 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/yahoo.py0000644000175000017500000000105412127316474022677 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/utils.py0000644000175000017500000000335412127316474022725 0ustar omabomab00000000000000from 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 django-social-auth-0.7.23/social_auth/backends/pipeline/0000755000175000017500000000000012135257233023007 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/backends/pipeline/user.py0000644000175000017500000001063512127317050024337 0ustar omabomab00000000000000from uuid import uuid4 from django.template.defaultfilters import slugify from social_auth.utils import setting from social_auth.models import UserSocialAuth 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 if max_length and len(value) > max_length: value = value[:max_length] 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 if max_length and len(value) > max_length: value = value[:max_length] 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() django-social-auth-0.7.23/social_auth/backends/pipeline/social.py0000644000175000017500000000504012127316474024636 0ustar omabomab00000000000000from 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} django-social-auth-0.7.23/social_auth/backends/pipeline/misc.py0000644000175000017500000000107112127316474024317 0ustar omabomab00000000000000from 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 django-social-auth-0.7.23/social_auth/backends/pipeline/associate.py0000644000175000017500000000164612127316474025347 0ustar omabomab00000000000000from 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 # Allow case-insensitive match, since real-world email address is case-insensitive try: return {'user': UserSocialAuth.get_user_by_email(email=email)} except MultipleObjectsReturned: raise AuthException(kwargs['backend'], 'Not unique email address.') except ObjectDoesNotExist: pass django-social-auth-0.7.23/social_auth/backends/pipeline/__init__.py0000644000175000017500000000050612127316474025125 0ustar omabomab00000000000000"""Django-Social-Auth Pipeline. Pipelines must return a dictionary with values that will be passed as parameter to next pipeline item. Pipelines must take **kwargs parameters to avoid failure. At some point a pipeline entry must create a UserSocialAuth instance and load it to the output if the user logged in correctly. """ django-social-auth-0.7.23/social_auth/backends/contrib/0000755000175000017500000000000012135257233022642 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/backends/contrib/twilio.py0000644000175000017500000000425512127316474024535 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/soundcloud.py0000644000175000017500000000716512127316474025410 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/tripit.py0000644000175000017500000000453012127316474024535 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/xing.py0000644000175000017500000000677412127316474024203 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/shopify.py0000644000175000017500000001017412127316474024704 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/odnoklassniki.py0000644000175000017500000002501012127316474026066 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/mixcloud.py0000644000175000017500000000261512127316474025050 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/github.py0000644000175000017500000000662612127316474024514 0ustar omabomab00000000000000""" 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 # 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""" return {'username': response.get('login'), 'email': response.get('email') or '', 'first_name': response.get('name')} 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://developer.github.com/v3/orgs/members/#response-if-requester-is-an-organization-member-and-user-is-a-member if not response.code == 204: data = None return data # Backend definition BACKENDS = { 'github': GithubAuth, } django-social-auth-0.7.23/social_auth/backends/contrib/gae.py0000644000175000017500000000365712127316474023767 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/douban.py0000644000175000017500000000774112127316474024501 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/fitbit.py0000644000175000017500000000577512127316474024517 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/evernote.py0000644000175000017500000000712712127316474025056 0ustar omabomab00000000000000""" EverNote OAuth support 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' 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, } django-social-auth-0.7.23/social_auth/backends/contrib/tumblr.py0000644000175000017500000000714612127316474024535 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/yahoo.py0000644000175000017500000000647412127316474024352 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/linkedin.py0000644000175000017500000001461312133321571025011 0ustar omabomab00000000000000""" 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'] 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} class LinkedinOAuth2Backend(LinkedinBackend): """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) 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)) 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, } django-social-auth-0.7.23/social_auth/backends/contrib/angel.py0000644000175000017500000000371612127316474024315 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/stackoverflow.py0000644000175000017500000000745312127316474026122 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/instagram.py0000644000175000017500000000405212127316474025206 0ustar omabomab00000000000000from 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, } django-social-auth-0.7.23/social_auth/backends/contrib/vkontakte.py0000644000175000017500000002352212127316474025232 0ustar omabomab00000000000000# -*- coding: utf-8 -*- """ VKontakte OpenAPI and OAuth 2.0 support. This contribution adds support for VKontakte OpenAPI and OAuth 2.0 service in the form www.vkontakte.ru. 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 # Vkontakte 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'] VKONTAKTE_API_URL = 'https://api.vkontakte.ru/method/' VKONTAKTE_SERVER_API_URL = 'http://api.vkontakte.ru/api.php' VKONTAKTE_API_VERSION = '3.0' USE_APP_AUTH = setting('VKONTAKTE_APP_AUTH', False) LOCAL_HTML = setting('VKONTAKTE_LOCAL_HTML', 'vkontakte.html') class VKontakteBackend(SocialAuthBackend): """VKontakte OpenAPI authentication backend""" name = 'vkontakte' def get_user_id(self, details, response): """Return user unique id provided by VKontakte""" return response['id'] def get_user_details(self, response): """Return user details from VKontakte 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 VKontakteAuth(BaseAuth): """VKontakte OpenAPI authorization mechanism""" AUTH_BACKEND = VKontakteBackend 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 VKontakte, 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('VKONTAKTE_APP_SECRET')).hexdigest() if hash != cookie_dict['sig'] or int(cookie_dict['expire']) < time(): raise AuthFailed('VKontakte 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): """VKontakte 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 VKontakteOAuth2Backend(OAuthBackend): """VKontakteOAuth2 authentication backend""" name = 'vkontakte-oauth2' 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 Vkontakte account""" return { 'username': response.get('screen_name'), 'email': '', 'first_name': response.get('first_name'), 'last_name': response.get('last_name') } class VKontakteOAuth2(BaseOAuth2): """Vkontakte OAuth mechanism""" AUTHORIZATION_URL = VK_AUTHORIZATION_URL ACCESS_TOKEN_URL = VK_ACCESS_TOKEN_URL AUTH_BACKEND = VKontakteOAuth2Backend 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(VKontakteOAuth2.SCOPE_VAR_NAME) or \ setting('VKONTAKTE_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 = vkontakte_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 VKontakteAppAuth(VKontakteOAuth2): """VKontakte Application Authentication support""" def auth_complete(self, *args, **kwargs): if USE_APP_AUTH: stop, app_auth = self.application_auth(*args, **kwargs) if app_auth: return app_auth if stop: return None return super(VKontakteAppAuth, 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 = vkontakte_api('getProfiles', data).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 VKontakte API""" data = {'uid': user_id} if access_token: data['access_token'] = access_token return vkontakte_api('isAppUser', data).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([self.request.REQUEST.get('api_id'), self.request.REQUEST.get('viewer_id'), USE_APP_AUTH['key']])).hexdigest() if check_key != auth_key: raise ValueError('VKontakte authentication failed: invalid ' 'auth key') user_check = USE_APP_AUTH.get('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 = {'response': self.user_profile(user_id), 'user_id': user_id} return (True, authenticate(*args, **{'auth': self, 'request': self.request, 'response': data, self.AUTH_BACKEND.name: True })) def _api_get_val_fun(name, conf): if USE_APP_AUTH: return USE_APP_AUTH.get(name) else: return setting(conf) def vkontakte_api(method, data): """Calls VKontakte OpenAPI method http://vkontakte.ru/apiclub, http://vkontakte.ru/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%20 API """ # We need to perform server-side call if no access_token if not 'access_token' in data: if not 'v' in data: data['v'] = VKONTAKTE_API_VERSION if not 'api_id' in data: data['api_id'] = _api_get_val_fun('id', 'VKONTAKTE_APP_ID') data['method'] = method data['format'] = 'json' url = VKONTAKTE_SERVER_API_URL secret = _api_get_val_fun('key', 'VKONTAKTE_APP_SECRET') param_list = sorted(list(item + '=' + data[item] for item in data)) data['sig'] = md5(''.join(param_list) + secret).hexdigest() else: url = VKONTAKTE_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 VKontakte.', exc_info=True, extra=dict(data=data)) return None # Backend definition BACKENDS = { 'vkontakte': VKontakteAuth, 'vkontakte-oauth2': VKontakteAppAuth if USE_APP_AUTH else VKontakteOAuth2 } django-social-auth-0.7.23/social_auth/backends/contrib/weibo.py0000644000175000017500000000450012127316474024324 0ustar omabomab00000000000000#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 } django-social-auth-0.7.23/social_auth/backends/contrib/mendeley.py0000644000175000017500000000410712127316474025024 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/bitbucket.py0000644000175000017500000001017112127316474025174 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/behance.py0000644000175000017500000000443012127316474024606 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/stocktwits.py0000644000175000017500000000417512127316474025445 0ustar omabomab00000000000000from 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, } django-social-auth-0.7.23/social_auth/backends/contrib/readability.py0000644000175000017500000000721512127316474025516 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/yammer.py0000644000175000017500000000624112127316474024515 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/mailru.py0000644000175000017500000000634712127316474024523 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/dropbox.py0000644000175000017500000000472312127316474024703 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/disqus.py0000644000175000017500000000441312127316474024532 0ustar omabomab00000000000000from 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, } django-social-auth-0.7.23/social_auth/backends/contrib/foursquare.py0000644000175000017500000000335012127316474025415 0ustar omabomab00000000000000from 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, } django-social-auth-0.7.23/social_auth/backends/contrib/yandex.py0000644000175000017500000001075612127316474024521 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/dailymotion.py0000644000175000017500000000535312127316474025556 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/__init__.py0000644000175000017500000000003312127316474024753 0ustar omabomab00000000000000"""Contrib auth modules""" django-social-auth-0.7.23/social_auth/backends/contrib/rdio.py0000644000175000017500000001014212127316474024153 0ustar omabomab00000000000000import 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 } django-social-auth-0.7.23/social_auth/backends/contrib/live.py0000644000175000017500000000505412127316474024163 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/orkut.py0000644000175000017500000000546612127316474024377 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/yammer_staging.py0000644000175000017500000000151612127316474026231 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/backends/contrib/flickr.py0000644000175000017500000000610612127316474024475 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/livejournal.py0000644000175000017500000000270312127316474025554 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/contrib/skyrock.py0000644000175000017500000000505412127316474024711 0ustar omabomab00000000000000""" 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, } django-social-auth-0.7.23/social_auth/backends/facebook.py0000644000175000017500000002465212127316474023342 0ustar omabomab00000000000000""" 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' 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) 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(): 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, } django-social-auth-0.7.23/social_auth/backends/__init__.py0000644000175000017500000010667612127316474023337 0ustar omabomab00000000000000""" 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.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 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): if association_id: UserSocialAuth.get_social_auth_for_user(user)\ .get(id=association_id).delete() else: UserSocialAuth.get_social_auth_for_user(user)\ .filter(provider=name)\ .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 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 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): if data.get('error'): error = self.data.get('error_description') or self.data['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()) 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") # Cache for discovered backends. BACKENDSCACHE = {} def get_backends(force_load=False): """ Entry point to the BACKENDS cache. If BACKENDSCACHE hasn't been populated, each of the modules referenced in AUTHENTICATION_BACKENDS is imported and checked for a BACKENDS definition and if enabled, added to the cache. Previously all backends were attempted to be loaded at import time of this module, which meant that backends that subclass bases found in this module would not have the chance to be loaded by the time they were added to this module's BACKENDS dict. See: https://github.com/omab/django-social-auth/issues/204 This new approach ensures that backends are allowed to subclass from bases in this module and still be picked up. A force_load boolean arg is also provided so that get_backend 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'): 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] return BACKENDSCACHE def get_backend(name, *args, **kwargs): """Returns a backend by name. Backends are stored in the BACKENDSCACHE cache dict. If not found, each of the modules referenced in AUTHENTICATION_BACKENDS is imported and checked for a BACKENDS definition. If the named backend is found in the module's BACKENDS definition, it's then stored in the cache for future access. """ try: # Cached backend which has previously been discovered. return BACKENDSCACHE[name](*args, **kwargs) except KeyError: # Force a reload of BACKENDS to ensure a missing # backend hasn't been missed. get_backends(force_load=True) try: return BACKENDSCACHE[name](*args, **kwargs) except KeyError: return None BACKENDS = { 'openid': OpenIdAuth } django-social-auth-0.7.23/social_auth/backends/google.py0000644000175000017500000002255212127316474023042 0ustar omabomab00000000000000""" 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' 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) 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 emails and email in emails: return # you're good if domains and email.split('@', 1)[1] not in domains: raise AuthFailed(backend, 'Domain not allowed') # Backend definition BACKENDS = { 'google': GoogleAuth, 'google-oauth': GoogleOAuth, 'google-oauth2': GoogleOAuth2, } django-social-auth-0.7.23/social_auth/backends/browserid.py0000644000175000017500000000475512127316474023573 0ustar omabomab00000000000000""" 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 } django-social-auth-0.7.23/social_auth/migrations/0000755000175000017500000000000012135257233021604 5ustar omabomab00000000000000django-social-auth-0.7.23/social_auth/migrations/0001_initial.py0000644000175000017500000002027512127316474024261 0ustar omabomab00000000000000# -*- 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 custom_user_frozen_models USER_MODEL = getattr(settings, 'SOCIAL_AUTH_USER_MODEL', None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' 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()) complete_apps = ['social_auth'] django-social-auth-0.7.23/social_auth/migrations/__init__.py0000644000175000017500000000000012127316474023707 0ustar omabomab00000000000000././@LongLink0000000000000000000000000000020000000000000011205 Lustar 00000000000000django-social-auth-0.7.23/social_auth/migrations/0002_auto__add_unique_nonce_timestamp_salt_server_url__add_unique_associati.pydjango-social-auth-0.7.23/social_auth/migrations/0002_auto__add_unique_nonce_timestamp_salt_server_u0000644000175000017500000001576712127316474033723 0ustar omabomab00000000000000# -*- 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 custom_user_frozen_models USER_MODEL = getattr(settings, 'SOCIAL_AUTH_USER_MODEL', None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' 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()) complete_apps = ['social_auth'] django-social-auth-0.7.23/social_auth/context_processors.py0000644000175000017500000000735112127316474023762 0ustar omabomab00000000000000from django.contrib.auth import REDIRECT_FIELD_NAME 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. 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)} def social_auth_by_type_backends(request): """Load Social Auth current user data to context. Will add a output from backends_data to context under social_auth key where each entry will be grouped by backend type (openid, oauth, oauth2). """ def context_value(): data = backends_data(request.user) data['backends'] = group_backend_by_type(data['backends']) data['not_associated'] = group_backend_by_type(data['not_associated']) data['associated'] = group_backend_by_type( data['associated'], key=lambda assoc: assoc.provider ) return data return {'social_auth': LazyDict(context_value)} def social_auth_by_name_backends(request): """Load Social Auth current user data to context. Will add a social_auth object whose attribute names are the names of each provider, e.g. social_auth.facebook would be the facebook association or None, depending on the logged in user's current associations. Providers with a hyphen have the hyphen replaced with an underscore, e.g. google-oauth2 becomes google_oauth2 when referenced in templates. """ def context_value(): keys = get_backends().keys() accounts = dict(zip(keys, [None] * len(keys))) user = request.user if hasattr(user, 'is_authenticated') and user.is_authenticated(): accounts.update((assoc.provider.replace('-', '_'), assoc) for assoc in UserSocialAuth.get_social_auth_for_user(user)) return accounts 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 } django-social-auth-0.7.23/social_auth/decorators.py0000644000175000017500000000263612127316474022162 0ustar omabomab00000000000000from 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 django-social-auth-0.7.23/social_auth/middleware.py0000644000175000017500000000465512132152170022120 0ustar omabomab00000000000000# -*- coding: utf-8 -*- from django.conf import settings from django.contrib import messages 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: messages.error(request, message, extra_tags=' '.join(tags)) except messages.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 django-social-auth-0.7.23/social_auth/__init__.py0000644000175000017500000000030112135257155021536 0ustar omabomab00000000000000""" Django-social-auth application, allows OpenId or OAuth user registration/authentication just adding a few configurations. """ version = (0, 7, 23) __version__ = '.'.join(map(str, version)) django-social-auth-0.7.23/social_auth/views.py0000644000175000017500000001710412131126210021124 0ustar omabomab00000000000000"""Views 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 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 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') @dsa_view(setting('SOCIAL_AUTH_COMPLETE_URL_NAME', 'socialauth_complete')) def auth(request, backend): """Start authentication process""" return auth_process(request, backend) @csrf_exempt @dsa_view() 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) @login_required @dsa_view() @disconnect_view 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) django-social-auth-0.7.23/setup.cfg0000644000175000017500000000007312135257233016756 0ustar omabomab00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 django-social-auth-0.7.23/README.rst0000644000175000017500000001434512127316474016637 0ustar omabomab00000000000000Django Social Auth ================== Django Social Auth is an easy way to setup social authentication/authorization mechanism for Django projects. Crafted using base code from django-twitter-oauth_ and django-openid-auth_, it implements a common interface to define new authentication providers from third parties. You can view this app's documentation on `Read the Docs`_ too. .. contents:: Table of Contents Features -------- This application provides user registration and login using social site credentials. Some features are: - Registration and login with social sites using the following providers at the moment: * `Google OpenID`_ * `Google OAuth`_ * `Google OAuth2`_ * `Yahoo OpenID`_ * OpenId_ like myOpenID_ * `Twitter OAuth`_ * `Facebook OAuth`_ Some contributions added support for: * `DISQUS OAuth`_ * `LiveJournal OpenID`_ * `Orkut OAuth`_ * `Linkedin OAuth`_ * `Foursquare OAuth2`_ * `GitHub OAuth`_ * `Dropbox OAuth`_ * `Flickr OAuth`_ * `Vkontakte OAuth`_ * `MSN Live Connect OAuth2`_ * `Skyrock OAuth`_ * `Yahoo OAuth`_ * `Evernote OAuth`_ * `Mail.ru OAuth`_ * `Odnoklassniki OAuth`_ * `Mixcloud OAuth2`_ * `BitBucket OAuth`_ * `Douban OAuth`_ * `Fitbit OAuth`_ * `Instagram OAuth2`_ * `Twilio`_ * `Weibo OAuth2`_ * `Yandex OpenId`_ * `Shopify OAuth2`_ * `StockTwits OAuth2`_ * `Stackoverflow OAuth2`_ - Basic user data population and signaling to allows custom fields values from providers' responses - Multiple social account associations to a single user - Custom User model override if needed (`auth.User`_ by default) - Extensible pipeline to handle authentication/association mechanism Demo ---- There's a demo at http://social.matiasaguirre.net/. Note: It lacks some backends' support at the moment. Contact ------- Join the `django-social-auth discussion list`_ and bring any questions or suggestions that would improve this application. Also join the IRC channel ``#django-social-auth`` on Freenode server. Documentation ------------- Extensive documentation at `Read the Docs`_. Dependencies ------------ Dependencies that **must** be met to use the application: - OpenId_ support depends on python-openid_ - OAuth_ support depends on python-oauth2_ - Several backends demands application registration on their corresponding sites Installation ------------ From pypi_:: $ pip install django-social-auth or:: $ easy_install django-social-auth or clone from github_:: $ git clone git://github.com/omab/django-social-auth.git and add social_auth to PYTHONPATH:: $ export PYTHONPATH=$PYTHONPATH:$(pwd)/django-social-auth/ or:: $ cd django-social-auth $ sudo python setup.py install Copyrights and Licence ---------------------- ``django-social-auth`` is protected by BSD licence. Some bits were derived from others' work and copyrighted by: - django-twitter-oauth:: Original Copyright goes to Henrik Lied (henriklied) Code borrowed from https://github.com/henriklied/django-twitter-oauth - django-openid-auth:: django-openid-auth - OpenID integration for django.contrib.auth Copyright (C) 2007 Simon Willison Copyright (C) 2008-2010 Canonical Ltd. .. _django-twitter-oauth: https://github.com/henriklied/django-twitter-oauth .. _django-openid-auth: https://launchpad.net/django-openid-auth .. _Read the Docs: http://django-social-auth.readthedocs.org/ .. _Google OpenID: https://developers.google.com/accounts/docs/OpenID .. _Google OAuth: https://developers.google.com/accounts/docs/OAuth .. _Google OAuth2: https://developers.google.com/accounts/docs/OAuth2 .. _Yahoo OpenID: http://openid.yahoo.com/ .. _OpenId: http://openid.net/ .. _myOpenID: https://www.myopenid.com/ .. _Twitter OAuth: http://dev.twitter.com/pages/oauth_faq .. _Facebook OAuth: http://developers.facebook.com/docs/authentication/ .. _DISQUS OAuth: http://disqus.com/api/docs/auth/ .. _LiveJournal OpenID: http://www.livejournal.com/support/faqbrowse.bml?faqid=283 .. _Orkut OAuth: http://code.google.com/apis/orkut/docs/rest/developers_guide_protocol.html#Authenticating .. _Linkedin OAuth: https://www.linkedin.com/secure/developer .. _Foursquare OAuth2: https://developer.foursquare.com/docs/oauth.html .. _GitHub OAuth: http://developer.github.com/v3/oauth/ .. _Dropbox OAuth: https://www.dropbox.com/developers_beta/reference/api .. _Flickr OAuth: http://www.flickr.com/services/api/ .. _Vkontakte OAuth: http://vk.com/developers.php?oid=-1&p=%D0%90%D0%B2%D1%82%D0%BE%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2 .. _MSN Live Connect OAuth2: http://msdn.microsoft.com/en-us/library/live/hh243647.aspx .. _Skyrock OAuth: http://www.skyrock.com/developer/ .. _Yahoo OAuth: http://developer.yahoo.com/oauth/guide/oauth-auth-flow.html .. _Evernote OAuth: http://dev.evernote.com/documentation/cloud/chapters/Authentication.php .. _Mail.ru OAuth: http://api.mail.ru/docs/guides/oauth/ .. _Odnoklassniki OAuth: http://dev.odnoklassniki.ru/wiki/display/ok/The+OAuth+2.0+Protocol .. _Mixcloud OAuth2: http://www.mixcloud.com/developers/documentation/#authorization .. _BitBucket OAuth: https://confluence.atlassian.com/display/BITBUCKET/OAuth+Consumers .. _Douban OAuth: http://www.douban.com/service/apidoc/auth .. _Fitbit OAuth: https://wiki.fitbit.com/display/API/OAuth+Authentication+in+the+Fitbit+API .. _Instagram OAuth2: http://instagram.com/developer/authentication/ .. _Twilio: https://www.twilio.com/user/account/connect/apps .. _Weibo OAuth2: http://open.weibo.com/wiki/Oauth2 .. _Yandex OpenId: http://openid.yandex.ru/ .. _Shopify OAuth2: http://api.shopify.com/authentication.html .. _StockTwits OAuth2: http://stocktwits.com/developers/docs/authentication .. _auth.User: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py#L186 .. _python-openid: http://pypi.python.org/pypi/python-openid/ .. _python-oauth2: https://github.com/simplegeo/python-oauth2 .. _OAuth: http://oauth.net/ .. _pypi: http://pypi.python.org/pypi/django-social-auth/ .. _github: https://github.com/omab/django-social-auth .. _django-social-auth discussion list: https://groups.google.com/forum/?fromgroups#!forum/django-social-auth .. _Stackoverflow OAuth2: http://api.stackexchange.com/ django-social-auth-0.7.23/setup.py0000644000175000017500000000422112133321571016641 0ustar omabomab00000000000000# -*- coding: utf-8 -*- """Setup file for easy installation""" from os.path import join, dirname from setuptools import setup version = __import__('social_auth').__version__ LONG_DESCRIPTION = """ Django Social Auth is an easy to setup social authentication/registration mechanism for Django projects. Crafted using base code from django-twitter-oauth_ and django-openid-auth_, implements a common interface to define new authentication providers from third parties. """ def long_description(): """Return long description from README.rst if it's present because it doesn't get installed.""" try: return open(join(dirname(__file__), 'README.rst')).read() except IOError: return LONG_DESCRIPTION setup(name='django-social-auth', version=version, author='Matías Aguirre', author_email='matiasaguirre@gmail.com', description='Django social authentication made simple.', license='BSD', keywords='django, openid, oauth, social auth, application', url='https://github.com/omab/django-social-auth', packages=['social_auth', 'social_auth.management', 'social_auth.management.commands', 'social_auth.backends', 'social_auth.backends.contrib', 'social_auth.backends.pipeline', 'social_auth.migrations', 'social_auth.tests', 'social_auth.db'], 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'], classifiers=['Framework :: Django', 'Development Status :: 4 - Beta', 'Topic :: Internet', 'License :: OSI Approved :: BSD License', 'Intended Audience :: Developers', 'Environment :: Web Environment', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7'], zip_safe=False)