python-social-auth-0.2.21/0000755000175500017550000000000012754357263015074 5ustar debacledebaclepython-social-auth-0.2.21/MANIFEST.in0000644000175500017550000000056012754357263016633 0ustar debacledebacleglobal-include *.py include *.txt CHANGELOG.md LICENSE README.rst recursive-include docs *.rst recursive-include social/tests *.txt graft examples recursive-exclude .tox * recursive-exclude social *.pyc recursive-exclude examples *.pyc recursive-exclude examples *.db recursive-exclude examples local_settings.py recursive-exclude examples/webpy_example/sessions * python-social-auth-0.2.21/examples/0000755000175500017550000000000012754357263016712 5ustar debacledebaclepython-social-auth-0.2.21/examples/cherrypy_example/0000755000175500017550000000000012754357263022272 5ustar debacledebaclepython-social-auth-0.2.21/examples/cherrypy_example/local_settings.py.template0000644000175500017550000000407212754357263027473 0ustar debacledebacleSOCIAL_SETTINGS = { 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth2', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', 'social.backends.upwork.UpworkOAuth', ), 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY': '', 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET': '' } python-social-auth-0.2.21/examples/cherrypy_example/templates/0000755000175500017550000000000012754357263024270 5ustar debacledebaclepython-social-auth-0.2.21/examples/cherrypy_example/templates/base.html0000644000175500017550000000102612754357263026067 0ustar debacledebacle Social {% block content %}{% endblock %} {% block scripts %}{% endblock %} python-social-auth-0.2.21/examples/cherrypy_example/templates/home.html0000644000175500017550000000630112754357263026106 0ustar debacledebacle{% extends "base.html" %} {% block content %} Google OAuth2
Google OAuth
Google OpenId
Twitter OAuth
Yahoo OpenId
Yahoo OAuth
Stripe OAuth2
Facebook OAuth2
Facebook App
Angel OAuth2
Behance OAuth2
Bitbucket OAuth
Box OAuth2
LinkedIn OAuth
Github OAuth2
Foursquare OAuth2
Instagram OAuth2
Live OAuth2
VK.com OAuth2
Dailymotion OAuth2
Disqus OAuth2
Dropbox OAuth
Evernote OAuth (sandbox mode)
Fitbit OAuth
Flickr OAuth
Soundcloud OAuth2
ThisIsMyJam OAuth1
Stocktwits OAuth2
Tripit OAuth
Twilio
Xing OAuth
Yandex OAuth2
Podio OAuth2
MineID OAuth2
Persona
{% endblock %} {% block scripts %} {% endblock %} python-social-auth-0.2.21/examples/cherrypy_example/templates/done.html0000644000175500017550000000077012754357263026107 0ustar debacledebacle{% extends "base.html" %} {% block content %}

You are logged in as {{ user.username }}!

Associated:

Associate:

{% endblock %} python-social-auth-0.2.21/examples/cherrypy_example/syncbd.py0000644000175500017550000000065412754357263024133 0ustar debacledebacleimport sys sys.path.append('../..') from sqlalchemy import create_engine import cherrypy cherrypy.config.update({ 'SOCIAL_AUTH_USER_MODEL': 'db.user.User', }) from social.apps.cherrypy_app.models import SocialBase from db import Base from db.user import User if __name__ == '__main__': engine = create_engine('sqlite:///test.db') Base.metadata.create_all(engine) SocialBase.metadata.create_all(engine) python-social-auth-0.2.21/examples/cherrypy_example/__init__.py0000644000175500017550000000412412754357263024404 0ustar debacledebacleimport sys sys.path.append('../..') import cherrypy from jinja2 import Environment, FileSystemLoader from social.apps.cherrypy_app.utils import backends from social.apps.cherrypy_app.views import CherryPyPSAViews from db.saplugin import SAEnginePlugin from db.satool import SATool from db.user import User SAEnginePlugin(cherrypy.engine, 'sqlite:///test.db').subscribe() class PSAExample(CherryPyPSAViews): @cherrypy.expose def index(self): return self.render_to('home.html') @cherrypy.expose def done(self): user = getattr(cherrypy.request, 'user', None) if user is None: raise cherrypy.HTTPRedirect('/') return self.render_to('done.html', user=user, backends=backends(user)) @cherrypy.expose def logout(self): raise cherrypy.HTTPRedirect('/') def render_to(self, tpl, **ctx): return cherrypy.tools.jinja2env.get_template(tpl).render(**ctx) def load_user(): user_id = cherrypy.session.get('user_id') if user_id: cherrypy.request.user = cherrypy.request.db.query(User).get(user_id) else: cherrypy.request.user = None def session_commit(): cherrypy.session.save() try: from local_settings import SOCIAL_SETTINGS except ImportError: print 'Define a local_settings.py using local_settings.py.template as base' SOCIAL_SETTINGS = {} if __name__ == '__main__': cherrypy.config.update({ 'server.socket_port': 8000, 'tools.sessions.on': True, 'tools.sessions.storage_type': 'ram', 'tools.db.on': True, 'tools.authenticate.on': True, 'SOCIAL_AUTH_USER_MODEL': 'db.user.User', 'SOCIAL_AUTH_LOGIN_URL': '/', 'SOCIAL_AUTH_LOGIN_REDIRECT_URL': '/done', }) cherrypy.config.update(SOCIAL_SETTINGS) cherrypy.tools.jinja2env = Environment( loader=FileSystemLoader('templates') ) cherrypy.tools.db = SATool() cherrypy.tools.authenticate = cherrypy.Tool('before_handler', load_user) cherrypy.tools.session = cherrypy.Tool('on_end_resource', session_commit) cherrypy.quickstart(PSAExample()) python-social-auth-0.2.21/examples/cherrypy_example/db/0000755000175500017550000000000012754357263022657 5ustar debacledebaclepython-social-auth-0.2.21/examples/cherrypy_example/db/user.py0000644000175500017550000000063112754357263024207 0ustar debacledebaclefrom sqlalchemy import Column, Integer, String, Boolean from db import Base class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(200)) password = Column(String(200), default='') name = Column(String(100)) email = Column(String(200)) active = Column(Boolean, default=True) def is_active(self): return self.active python-social-auth-0.2.21/examples/cherrypy_example/db/__init__.py0000644000175500017550000000012412754357263024765 0ustar debacledebaclefrom sqlalchemy.ext.declarative import declarative_base Base = declarative_base() python-social-auth-0.2.21/examples/cherrypy_example/db/satool.py0000644000175500017550000000140012754357263024525 0ustar debacledebacle# -*- coding: utf-8 -*- import cherrypy class SATool(cherrypy.Tool): def __init__(self): super(SATool, self).__init__('before_handler', self.bind_session, priority=20) def _setup(self): super(SATool, self)._setup() cherrypy.request.hooks.attach('on_end_resource', self.commit_transaction, priority=80) def bind_session(self): session = cherrypy.engine.publish('bind-session').pop() cherrypy.request.db = session def commit_transaction(self): if not hasattr(cherrypy.request, 'db'): return cherrypy.request.db = None cherrypy.engine.publish('commit-session') python-social-auth-0.2.21/examples/cherrypy_example/db/saplugin.py0000644000175500017550000000235212754357263025055 0ustar debacledebacle# -*- coding: utf-8 -*- from cherrypy.process import plugins from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker class SAEnginePlugin(plugins.SimplePlugin): def __init__(self, bus, connection_string=None): self.sa_engine = None self.connection_string = connection_string self.session = scoped_session(sessionmaker(autoflush=True, autocommit=False)) super(SAEnginePlugin, self).__init__(bus) def start(self): self.sa_engine = create_engine(self.connection_string, echo=False) self.bus.subscribe('bind-session', self.bind) self.bus.subscribe('commit-session', self.commit) def stop(self): self.bus.unsubscribe('bind-session', self.bind) self.bus.unsubscribe('commit-session', self.commit) if self.sa_engine: self.sa_engine.dispose() self.sa_engine = None def bind(self): self.session.configure(bind=self.sa_engine) return self.session def commit(self): try: self.session.commit() except: self.session.rollback() raise finally: self.session.remove() python-social-auth-0.2.21/examples/cherrypy_example/requirements.txt0000644000175500017550000000001112754357263025546 0ustar debacledebaclecherrypy python-social-auth-0.2.21/examples/tornado_example/0000755000175500017550000000000012754357263022073 5ustar debacledebaclepython-social-auth-0.2.21/examples/tornado_example/app.py0000644000175500017550000000356712754357263023240 0ustar debacledebacleimport sys sys.path.append('../..') from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web from social.apps.tornado_app.models import init_social from social.apps.tornado_app.routes import SOCIAL_AUTH_ROUTES import settings engine = create_engine('sqlite:///test.db', echo=False) session = scoped_session(sessionmaker(bind=engine)) Base = declarative_base() class MainHandler(tornado.web.RequestHandler): def get(self): self.render('templates/home.html') class DoneHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): from models import User user_id = self.get_secure_cookie('user_id') user = session.query(User).get(int(user_id)) self.render('templates/done.html', user=user) class LogoutHandler(tornado.web.RequestHandler): def get(self): self.request.redirect('/') tornado.options.parse_command_line() tornado_settings = dict((k, getattr(settings, k)) for k in dir(settings) if not k.startswith('__')) application = tornado.web.Application(SOCIAL_AUTH_ROUTES + [ (r'/', MainHandler), (r'/done/', DoneHandler), (r'/logout/', LogoutHandler), ], cookie_secret='adb528da-20bb-4386-8eaf-09f041b569e0', **tornado_settings) def main(): init_social(Base, session, tornado_settings) http_server = tornado.httpserver.HTTPServer(application) http_server.listen(8000) tornado.ioloop.IOLoop.instance().start() def syncdb(): from models import user_syncdb init_social(Base, session, tornado_settings) Base.metadata.create_all(engine) user_syncdb() if __name__ == '__main__': if len(sys.argv) > 1 and sys.argv[1] == 'syncdb': syncdb() else: main() python-social-auth-0.2.21/examples/tornado_example/models.py0000644000175500017550000000073212754357263023732 0ustar debacledebaclefrom sqlalchemy import Column, Integer, String from app import Base, engine class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(30), nullable=False) first_name = Column(String(30), nullable=True) last_name = Column(String(30), nullable=True) email = Column(String(75), nullable=False) password = Column(String(128), nullable=True) def user_syncdb(): Base.metadata.create_all(engine) python-social-auth-0.2.21/examples/tornado_example/templates/0000755000175500017550000000000012754357263024071 5ustar debacledebaclepython-social-auth-0.2.21/examples/tornado_example/templates/base.html0000644000175500017550000000101412754357263025665 0ustar debacledebacle Social {% block content %}{% end %} {% block scripts %}{% end %} python-social-auth-0.2.21/examples/tornado_example/templates/home.html0000644000175500017550000000634112754357263025713 0ustar debacledebacle{% extends "base.html" %} {% block content %} Google OAuth2
Google OAuth
Google OpenId
Twitter OAuth
Yahoo OpenId
Yahoo OAuth
Stripe OAuth2
Facebook OAuth2
Facebook App
Angel OAuth2
Behance OAuth2
Bitbucket OAuth
Box OAuth2
LinkedIn OAuth
Github OAuth2
Foursquare OAuth2
Instagram OAuth2
Live OAuth2
VK.com OAuth2
Dailymotion OAuth2
Disqus OAuth2
Dropbox OAuth
Evernote OAuth (sandbox mode)
Fitbit OAuth
Flickr OAuth
Soundcloud OAuth2
ThisIsMyJam OAuth1
Stocktwits OAuth2
Tripit OAuth
Clef OAuth2
Twilio
Xing OAuth
Yandex OAuth2
Podio OAuth2
MineID OAuth2
Persona
{% end %} {% block scripts %} {% end %} python-social-auth-0.2.21/examples/tornado_example/templates/done.html0000644000175500017550000000015212754357263025702 0ustar debacledebacle{% extends "base.html" %} {% block content %}

You are logged in as {{ user.username }}!

{% end %} python-social-auth-0.2.21/examples/tornado_example/__init__.py0000644000175500017550000000000012754357263024172 0ustar debacledebaclepython-social-auth-0.2.21/examples/tornado_example/settings.py0000644000175500017550000000406212754357263024307 0ustar debacledebacleSQLALCHEMY_DATABASE_URI = 'sqlite:///test.db' SOCIAL_AUTH_LOGIN_URL = '/' SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/done/' SOCIAL_AUTH_USER_MODEL = 'models.User' SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth2', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', 'social.backends.upwork.UpworkOAuth', ) from local_settings import * python-social-auth-0.2.21/examples/django_example/0000755000175500017550000000000012754357263021667 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_example/example/0000755000175500017550000000000012754357263023322 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_example/example/urls.py0000644000175500017550000000127712754357263024670 0ustar debacledebaclefrom django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', url(r'^$', 'example.app.views.home'), url(r'^admin/', include(admin.site.urls)), url(r'^email-sent/', 'example.app.views.validation_sent'), url(r'^login/$', 'example.app.views.home'), url(r'^logout/$', 'example.app.views.logout'), url(r'^done/$', 'example.app.views.done', name='done'), url(r'^ajax-auth/(?P[^/]+)/$', 'example.app.views.ajax_auth', name='ajax-auth'), url(r'^email/$', 'example.app.views.require_email', name='require_email'), url(r'', include('social.apps.django_app.urls', namespace='social')) ) python-social-auth-0.2.21/examples/django_example/example/wsgi.py0000644000175500017550000000215312754357263024646 0ustar debacledebacle""" WSGI config for dj project. This module contains the WSGI application used by Django's development server and any production WSGI deployments. It should expose a module-level variable named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover this application via the ``WSGI_APPLICATION`` setting. Usually you will have the standard Django WSGI application here, but it also might make sense to replace the whole Django WSGI application with a custom one that later delegates to the Django one. For example, you could introduce WSGI middleware here, or combine a Django application with an application of another framework. """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. from django.core.wsgi import get_wsgi_application application = get_wsgi_application() # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) python-social-auth-0.2.21/examples/django_example/example/templates/0000755000175500017550000000000012754357263025320 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_example/example/templates/home.html0000644000175500017550000004532112754357263027143 0ustar debacledebacle{% load backend_utils %} Python Social Auth

Python Social Auth

{% if user.is_authenticated %}
You are logged in as {{ user.username }}!
{% endif %}
{% for name, backend in available_backends|legacy_backends %} {% associated backend %} {% if association %}
{% csrf_token %} Disconnect {{ backend|backend_name }}
{% else %} {{ backend|backend_name }} {% endif %} {% endfor %} Ajax
Logout
{% if backend %} {% endif %} {% if plus_id %} {% endif %} python-social-auth-0.2.21/examples/django_example/example/__init__.py0000644000175500017550000000000012754357263025421 0ustar debacledebaclepython-social-auth-0.2.21/examples/django_example/example/settings.py0000644000175500017550000002060412754357263025536 0ustar debacledebacleimport sys from os.path import abspath, dirname, join sys.path.insert(0, '../..') DEBUG = True TEMPLATE_DEBUG = DEBUG ROOT_PATH = abspath(dirname(__file__)) ADMINS = ( # ('Your Name', 'your_email@example.com'), ) MANAGERS = ADMINS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'test.db' } } TIME_ZONE = 'America/Montevideo' LANGUAGE_CODE = 'en-us' SITE_ID = 1 USE_I18N = True USE_L10N = True USE_TZ = True MEDIA_ROOT = '' MEDIA_URL = '' STATIC_ROOT = '' STATIC_URL = '/static/' STATICFILES_DIRS = ( # Put strings here, like "/home/html/static" or "C:/www/django/static". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) SECRET_KEY = '#$5btppqih8=%ae^#&7en#kyi!vh%he9rg=ed#hm6fnw9^=umc' TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) ROOT_URLCONF = 'example.urls' # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'example.wsgi.application' TEMPLATE_DIRS = ( join(ROOT_PATH, 'templates'), ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'social.apps.django_app.default', 'example.app', ) LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' } }, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, } } SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'django.contrib.messages.context_processors.messages', 'social.apps.django_app.context_processors.backends', ) AUTHENTICATION_BACKENDS = ( 'social.backends.amazon.AmazonOAuth2', 'social.backends.angel.AngelOAuth2', 'social.backends.aol.AOLOpenId', 'social.backends.appsfuel.AppsfuelOAuth2', 'social.backends.beats.BeatsOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.belgiumeid.BelgiumEIDOpenId', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.clef.ClefOAuth2', 'social.backends.coinbase.CoinbaseOAuth2', 'social.backends.coursera.CourseraOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.deezer.DeezerOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.douban.DoubanOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.dropbox.DropboxOAuth2', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.facebook.FacebookOAuth2', 'social.backends.fedora.FedoraOpenId', 'social.backends.fitbit.FitbitOAuth2', 'social.backends.flickr.FlickrOAuth', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.github.GithubOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOpenId', 'social.backends.google.GooglePlusAuth', 'social.backends.google.GoogleOpenIdConnect', 'social.backends.instagram.InstagramOAuth2', 'social.backends.jawbone.JawboneOAuth2', 'social.backends.kakao.KakaoOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.linkedin.LinkedinOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.mailru.MailruOAuth2', 'social.backends.mendeley.MendeleyOAuth', 'social.backends.mendeley.MendeleyOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.mixcloud.MixcloudOAuth2', 'social.backends.nationbuilder.NationBuilderOAuth2', 'social.backends.odnoklassniki.OdnoklassnikiOAuth2', 'social.backends.open_id.OpenIdAuth', 'social.backends.openstreetmap.OpenStreetMapOAuth', 'social.backends.persona.PersonaAuth', 'social.backends.podio.PodioOAuth2', 'social.backends.rdio.RdioOAuth1', 'social.backends.rdio.RdioOAuth2', 'social.backends.readability.ReadabilityOAuth', 'social.backends.reddit.RedditOAuth2', 'social.backends.runkeeper.RunKeeperOAuth2', 'social.backends.sketchfab.SketchfabOAuth2', 'social.backends.skyrock.SkyrockOAuth', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.spotify.SpotifyOAuth2', 'social.backends.stackoverflow.StackoverflowOAuth2', 'social.backends.steam.SteamOpenId', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.stripe.StripeOAuth2', 'social.backends.suse.OpenSUSEOpenId', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.trello.TrelloOAuth', 'social.backends.tripit.TripItOAuth', 'social.backends.tumblr.TumblrOAuth', 'social.backends.twilio.TwilioAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.vk.VKOAuth2', 'social.backends.weibo.WeiboOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', 'social.backends.xing.XingOAuth', 'social.backends.yahoo.YahooOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.yammer.YammerOAuth2', 'social.backends.yandex.YandexOAuth2', 'social.backends.vimeo.VimeoOAuth1', 'social.backends.lastfm.LastFmAuth', 'social.backends.moves.MovesOAuth2', 'social.backends.vend.VendOAuth2', 'social.backends.email.EmailAuth', 'social.backends.username.UsernameAuth', 'django.contrib.auth.backends.ModelBackend', 'social.backends.upwork.UpworkOAuth', ) AUTH_USER_MODEL = 'app.CustomUser' LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/done/' URL_PATH = '' SOCIAL_AUTH_STRATEGY = 'social.strategies.django_strategy.DjangoStrategy' SOCIAL_AUTH_STORAGE = 'social.apps.django_app.default.models.DjangoStorage' SOCIAL_AUTH_GOOGLE_OAUTH_SCOPE = [ 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/userinfo.profile' ] # SOCIAL_AUTH_EMAIL_FORM_URL = '/signup-email' SOCIAL_AUTH_EMAIL_FORM_HTML = 'email_signup.html' SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION = 'example.app.mail.send_validation' SOCIAL_AUTH_EMAIL_VALIDATION_URL = '/email-sent/' # SOCIAL_AUTH_USERNAME_FORM_URL = '/signup-username' SOCIAL_AUTH_USERNAME_FORM_HTML = 'username_signup.html' SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'example.app.pipeline.require_email', 'social.pipeline.mail.mail_validation', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.debug.debug', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', 'social.pipeline.debug.debug' ) TEST_RUNNER = 'django.test.runner.DiscoverRunner' # SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ['first_name', 'last_name', 'email', # 'username'] try: from example.local_settings import * except ImportError: pass python-social-auth-0.2.21/examples/django_example/example/app/0000755000175500017550000000000012754357263024102 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_example/example/app/mail.py0000644000175500017550000000075612754357263025406 0ustar debacledebaclefrom django.conf import settings from django.core.mail import send_mail from django.core.urlresolvers import reverse def send_validation(strategy, backend, code): url = '{0}?verification_code={1}'.format( reverse('social:complete', args=(backend.name,)), code.code ) url = strategy.request.build_absolute_uri(url) send_mail('Validate your account', 'Validate your account {0}'.format(url), settings.EMAIL_FROM, [code.email], fail_silently=False) python-social-auth-0.2.21/examples/django_example/example/app/templatetags/0000755000175500017550000000000012754357263026574 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_example/example/app/templatetags/__init__.py0000644000175500017550000000000012754357263030673 0ustar debacledebaclepython-social-auth-0.2.21/examples/django_example/example/app/templatetags/backend_utils.py0000644000175500017550000000407612754357263031764 0ustar debacledebacleimport re from django import template from social.backends.oauth import OAuthAuth register = template.Library() name_re = re.compile(r'([^O])Auth') @register.filter def backend_name(backend): name = backend.__class__.__name__ name = name.replace('OAuth', ' OAuth') name = name.replace('OpenId', ' OpenId') name = name.replace('Sandbox', '') name = name_re.sub(r'\1 Auth', name) return name @register.filter def backend_class(backend): return backend.name.replace('-', ' ') @register.filter def icon_name(name): return { 'stackoverflow': 'stack-overflow', 'google-oauth': 'google', 'google-oauth2': 'google', 'google-openidconnect': 'google', 'yahoo-oauth': 'yahoo', 'facebook-app': 'facebook', 'email': 'envelope', 'vimeo': 'vimeo-square', 'linkedin-oauth2': 'linkedin', 'vk-oauth2': 'vk', 'live': 'windows', 'username': 'user', }.get(name, name) @register.filter def social_backends(backends): backends = [(name, backend) for name, backend in backends.items() if name not in ['username', 'email']] backends.sort(key=lambda b: b[0]) return [backends[n:n + 10] for n in range(0, len(backends), 10)] @register.filter def legacy_backends(backends): backends = [(name, backend) for name, backend in backends.items() if name in ['username', 'email']] backends.sort(key=lambda b: b[0]) return backends @register.filter def oauth_backends(backends): backends = [(name, backend) for name, backend in backends.items() if issubclass(backend, OAuthAuth)] backends.sort(key=lambda b: b[0]) return backends @register.simple_tag(takes_context=True) def associated(context, backend): user = context.get('user') context['association'] = None if user and user.is_authenticated(): try: context['association'] = user.social_auth.filter( provider=backend.name )[0] except IndexError: pass return '' python-social-auth-0.2.21/examples/django_example/example/app/decorators.py0000644000175500017550000000072112754357263026621 0ustar debacledebaclefrom functools import wraps from django.template import RequestContext from django.shortcuts import render_to_response def render_to(tpl): def decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): out = func(request, *args, **kwargs) if isinstance(out, dict): out = render_to_response(tpl, out, RequestContext(request)) return out return wrapper return decorator python-social-auth-0.2.21/examples/django_example/example/app/models.py0000644000175500017550000000023412754357263025736 0ustar debacledebacle# Define a custom User class to work with django-social-auth from django.contrib.auth.models import AbstractUser class CustomUser(AbstractUser): pass python-social-auth-0.2.21/examples/django_example/example/app/__init__.py0000644000175500017550000000000012754357263026201 0ustar debacledebaclepython-social-auth-0.2.21/examples/django_example/example/app/pipeline.py0000644000175500017550000000070412754357263026262 0ustar debacledebaclefrom django.shortcuts import redirect from social.pipeline.partial import partial @partial def require_email(strategy, details, user=None, is_new=False, *args, **kwargs): if kwargs.get('ajax') or user and user.email: return elif is_new and not details.get('email'): email = strategy.request_data().get('email') if email: details['email'] = email else: return redirect('require_email') python-social-auth-0.2.21/examples/django_example/example/app/views.py0000644000175500017550000000422212754357263025611 0ustar debacledebacleimport json from django.conf import settings from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import redirect from django.contrib.auth.decorators import login_required from django.contrib.auth import logout as auth_logout, login from social.backends.oauth import BaseOAuth1, BaseOAuth2 from social.backends.google import GooglePlusAuth from social.backends.utils import load_backends from social.apps.django_app.utils import psa from example.app.decorators import render_to def logout(request): """Logs out user""" auth_logout(request) return redirect('/') def context(**extra): return dict({ 'plus_id': getattr(settings, 'SOCIAL_AUTH_GOOGLE_PLUS_KEY', None), 'plus_scope': ' '.join(GooglePlusAuth.DEFAULT_SCOPE), 'available_backends': load_backends(settings.AUTHENTICATION_BACKENDS) }, **extra) @render_to('home.html') def home(request): """Home view, displays login mechanism""" if request.user.is_authenticated(): return redirect('done') return context() @login_required @render_to('home.html') def done(request): """Login complete view, displays user data""" return context() @render_to('home.html') def validation_sent(request): return context( validation_sent=True, email=request.session.get('email_validation_address') ) @render_to('home.html') def require_email(request): backend = request.session['partial_pipeline']['backend'] return context(email_required=True, backend=backend) @psa('social:complete') def ajax_auth(request, backend): if isinstance(request.backend, BaseOAuth1): token = { 'oauth_token': request.REQUEST.get('access_token'), 'oauth_token_secret': request.REQUEST.get('access_token_secret'), } elif isinstance(request.backend, BaseOAuth2): token = request.REQUEST.get('access_token') else: raise HttpResponseBadRequest('Wrong backend type') user = request.backend.do_auth(token, ajax=True) login(request, user) data = {'id': user.id, 'username': user.username} return HttpResponse(json.dumps(data), mimetype='application/json') python-social-auth-0.2.21/examples/django_example/requirements.txt0000644000175500017550000000003712754357263025153 0ustar debacledebacledjango>=1.4 python-social-auth python-social-auth-0.2.21/examples/django_example/manage.py0000755000175500017550000000037012754357263023474 0ustar debacledebacle#!/usr/bin/env python import os import sys if __name__ == '__main__': os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) python-social-auth-0.2.21/examples/webpy_example/0000755000175500017550000000000012754357263021553 5ustar debacledebaclepython-social-auth-0.2.21/examples/webpy_example/app.py0000644000175500017550000000653512754357263022716 0ustar debacledebacleimport sys sys.path.append('../..') import web from web.contrib.template import render_jinja from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from social.utils import setting_name from social.apps.webpy_app.utils import psa, backends from social.apps.webpy_app import app as social_app import local_settings web.config.debug = False web.config[setting_name('USER_MODEL')] = 'models.User' web.config[setting_name('AUTHENTICATION_BACKENDS')] = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth2', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', 'social.backends.upwork.UpworkOAuth', ) web.config[setting_name('LOGIN_REDIRECT_URL')] = '/done/' urls = ( '^/$', 'main', '^/done/$', 'done', '', social_app.app_social ) render = render_jinja('templates/') class main(object): def GET(self): return render.home() class done(social_app.BaseViewClass): def GET(self): user = self.get_current_user() return render.done(user=user, backends=backends(user)) engine = create_engine('sqlite:///test.db', echo=True) def load_sqla(handler): web.ctx.orm = scoped_session(sessionmaker(bind=engine)) try: return handler() except web.HTTPError: web.ctx.orm.commit() raise except: web.ctx.orm.rollback() raise finally: web.ctx.orm.commit() # web.ctx.orm.expunge_all() Session = sessionmaker(bind=engine) Session.configure(bind=engine) app = web.application(urls, locals()) app.add_processor(load_sqla) session = web.session.Session(app, web.session.DiskStore('sessions')) web.db_session = Session() web.web_session = session if __name__ == "__main__": app.run() python-social-auth-0.2.21/examples/webpy_example/models.py0000644000175500017550000000101512754357263023405 0ustar debacledebaclefrom sqlalchemy import Column, Integer, String, Boolean from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(200)) password = Column(String(200), default='') name = Column(String(100)) email = Column(String(200)) active = Column(Boolean, default=True) def is_active(self): return self.active def is_authenticated(self): return True python-social-auth-0.2.21/examples/webpy_example/templates/0000755000175500017550000000000012754357263023551 5ustar debacledebaclepython-social-auth-0.2.21/examples/webpy_example/templates/base.html0000644000175500017550000000102612754357263025350 0ustar debacledebacle Social {% block content %}{% endblock %} {% block scripts %}{% endblock %} python-social-auth-0.2.21/examples/webpy_example/templates/home.html0000644000175500017550000000642212754357263025373 0ustar debacledebacle{% extends "base.html" %} {% block content %} Google OAuth2
Google OAuth
Google OpenId
Twitter OAuth
Yahoo OpenId
Yahoo OAuth
Stripe OAuth2
Facebook OAuth2
Facebook App
Angel OAuth2
Behance OAuth2
Bitbucket OAuth
Box OAuth2
LinkedIn OAuth
Github OAuth2
Foursquare OAuth2
Instagram OAuth2
Live OAuth2
VK.com OAuth2
Dailymotion OAuth2
Disqus OAuth2
Dropbox OAuth
Evernote OAuth (sandbox mode)
Fitbit OAuth
Flickr OAuth
Soundcloud OAuth2
ThisIsMyJamm OAuth1
Stocktwits OAuth2
Tripit OAuth
Clef OAuth2
Twilio
Xing OAuth
Yandex OAuth2
Podio OAuth2
MineID OAuth2
Persona
{% endblock %} {% block scripts %} {% endblock %} python-social-auth-0.2.21/examples/webpy_example/templates/done.html0000644000175500017550000000077112754357263025371 0ustar debacledebacle{% extends "base.html" %} {% block content %} Logged in as {{ user.username }}!

Associated:

{% for assoc in backends["associated"] %}
{{ assoc.provider }}
{% endfor %}

Associate:

{% endblock %} python-social-auth-0.2.21/examples/webpy_example/__init__.py0000644000175500017550000000000012754357263023652 0ustar debacledebaclepython-social-auth-0.2.21/examples/webpy_example/requirements.txt0000644000175500017550000000005412754357263025036 0ustar debacledebacleJinja2==2.6 web.py==0.37 python-social-auth python-social-auth-0.2.21/examples/webpy_example/migrate.py0000644000175500017550000000032012754357263023550 0ustar debacledebaclefrom app import engine from models import Base from social.apps.webpy_app.models import SocialBase if __name__ == '__main__': Base.metadata.create_all(engine) SocialBase.metadata.create_all(engine) python-social-auth-0.2.21/examples/pyramid_example/0000755000175500017550000000000012754357263022072 5ustar debacledebaclepython-social-auth-0.2.21/examples/pyramid_example/development.ini0000644000175500017550000000255012754357263025117 0ustar debacledebacle### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:example pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = true pyramid.debug_routematch = true pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/test.db # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 8000 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, example, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_example] level = DEBUG handlers = qualname = example [logger_sqlalchemy] level = INFO handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s python-social-auth-0.2.21/examples/pyramid_example/MANIFEST.in0000644000175500017550000000020212754357263023622 0ustar debacledebacleinclude *.txt *.ini *.cfg *.rst recursive-include example *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml python-social-auth-0.2.21/examples/pyramid_example/README.txt0000644000175500017550000000034312754357263023570 0ustar debacledebacleexample README ============== Getting Started --------------- - cd - $VENV/bin/python setup.py develop - $VENV/bin/initialize_example_db development.ini - $VENV/bin/pserve development.ini python-social-auth-0.2.21/examples/pyramid_example/setup.cfg0000644000175500017550000000075112754357263023716 0ustar debacledebacle[nosetests] match=^test nocapture=1 cover-package=example with-coverage=1 cover-erase=1 [compile_catalog] directory = example/locale domain = example statistics = true [extract_messages] add_comments = TRANSLATORS: output_file = example/locale/example.pot width = 80 [init_catalog] domain = example input_file = example/locale/example.pot output_dir = example/locale [update_catalog] domain = example input_file = example/locale/example.pot output_dir = example/locale previous = true python-social-auth-0.2.21/examples/pyramid_example/example/0000755000175500017550000000000012754357263023525 5ustar debacledebaclepython-social-auth-0.2.21/examples/pyramid_example/example/models.py0000644000175500017550000000114012754357263025356 0ustar debacledebaclefrom sqlalchemy import Column, Integer, String, Boolean from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(200)) email = Column(String(200)) password = Column(String(200), default='') name = Column(String(100)) active = Column(Boolean, default=True) python-social-auth-0.2.21/examples/pyramid_example/example/local_settings.py.template0000644000175500017550000000471612754357263030733 0ustar debacledebacleSOCIAL_AUTH_KEYS = { 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY': '', 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET': '', 'SOCIAL_AUTH_TWITTER_KEY': '', 'SOCIAL_AUTH_TWITTER_SECRET': '', 'SOCIAL_AUTH_STRIPE_KEY': '', 'SOCIAL_AUTH_STRIPE_SECRET': '', 'SOCIAL_AUTH_STRIPE_SCOPE': [], 'SOCIAL_AUTH_FACEBOOK_KEY': '', 'SOCIAL_AUTH_FACEBOOK_SECRET': '', 'SOCIAL_AUTH_FACEBOOK_APP_KEY': '', 'SOCIAL_AUTH_FACEBOOK_APP_SECRET': '', 'SOCIAL_AUTH_FACEBOOK_APP_NAMESPACE': '', 'SOCIAL_AUTH_YAHOO_OAUTH_KEY': '', 'SOCIAL_AUTH_YAHOO_OAUTH_SECRET': '', 'SOCIAL_AUTH_ANGEL_KEY': '', 'SOCIAL_AUTH_ANGEL_SECRET': '', 'SOCIAL_AUTH_BEHANCE_KEY': '', 'SOCIAL_AUTH_BEHANCE_SECRET': '', 'SOCIAL_AUTH_BEHANCE_SCOPE': [], 'SOCIAL_AUTH_BITBUCKET_KEY': '', 'SOCIAL_AUTH_BITBUCKET_SECRET': '', 'SOCIAL_AUTH_LINKEDIN_KEY': '', 'SOCIAL_AUTH_LINKEDIN_SECRET': '', 'SOCIAL_AUTH_LINKEDIN_SCOPE': [], 'SOCIAL_AUTH_GITHUB_KEY': '', 'SOCIAL_AUTH_GITHUB_SECRET': '', 'SOCIAL_AUTH_FOURSQUARE_KEY': '', 'SOCIAL_AUTH_FOURSQUARE_SECRET': '', 'SOCIAL_AUTH_INSTAGRAM_KEY': '', 'SOCIAL_AUTH_INSTAGRAM_SECRET': '', 'SOCIAL_AUTH_LIVE_KEY': '', 'SOCIAL_AUTH_LIVE_SECRET': '', 'SOCIAL_AUTH_VKONTAKTE_OAUTH2_KEY': '', 'SOCIAL_AUTH_VKONTAKTE_OAUTH2_SECRET': '', 'SOCIAL_AUTH_DAILYMOTION_KEY': '', 'SOCIAL_AUTH_DAILYMOTION_SECRET': '', 'SOCIAL_AUTH_DISQUS_KEY': '', 'SOCIAL_AUTH_DISQUS_SECRET': '', 'SOCIAL_AUTH_DROPBOX_KEY': '', 'SOCIAL_AUTH_DROPBOX_SECRET': '', 'SOCIAL_AUTH_EVERNOTE_SANDBOX_KEY': '', 'SOCIAL_AUTH_EVERNOTE_SANDBOX_SECRET': '', 'SOCIAL_AUTH_FITBIT_KEY': '', 'SOCIAL_AUTH_FITBIT_SECRET': '', 'SOCIAL_AUTH_FLICKR_KEY': '', 'SOCIAL_AUTH_FLICKR_SECRET': '', 'SOCIAL_AUTH_SOUNDCLOUD_KEY': '', 'SOCIAL_AUTH_SOUNDCLOUD_SECRET': '', 'SOCIAL_AUTH_STOCKTWITS_KEY': '', 'SOCIAL_AUTH_STOCKTWITS_SECRET': '', 'SOCIAL_AUTH_TRIPIT_KEY': '', 'SOCIAL_AUTH_TRIPIT_SECRET': '', 'SOCIAL_AUTH_TWILIO_KEY': '', 'SOCIAL_AUTH_TWILIO_SECRET': '', 'SOCIAL_AUTH_XING_KEY': '', 'SOCIAL_AUTH_XING_SECRET': '', 'SOCIAL_AUTH_YANDEX_OAUTH2_KEY': '', 'SOCIAL_AUTH_YANDEX_OAUTH2_SECRET': '', 'SOCIAL_AUTH_YANDEX_OAUTH2_API_URL': '', 'SOCIAL_AUTH_REDDIT_KEY': '', 'SOCIAL_AUTH_REDDIT_SECRET': '', 'SOCIAL_AUTH_REDDIT_AUTH_EXTRA_ARGUMENTS': {}, } def includeme(config): config.registry.settings.update(SOCIAL_AUTH_KEYS) python-social-auth-0.2.21/examples/pyramid_example/example/templates/0000755000175500017550000000000012754357263025523 5ustar debacledebaclepython-social-auth-0.2.21/examples/pyramid_example/example/templates/done.pt0000644000175500017550000000151512754357263027017 0ustar debacledebacle Social Auth Pyramid Example

You are logged in as ${request.user.username}!

Associated:

Associate:

python-social-auth-0.2.21/examples/pyramid_example/example/templates/home.pt0000644000175500017550000001243312754357263027023 0ustar debacledebacle Social Auth Pyramid Example Google OAuth2
Google OAuth
Google OpenId
Twitter OAuth
Yahoo OpenId
Yahoo OAuth
Stripe OAuth2
Facebook OAuth2
Facebook App
Angel OAuth2
Behance OAuth2
Bitbucket OAuth
Box OAuth2
LinkedIn OAuth
Github OAuth2
Foursquare OAuth2
Instagram OAuth2
Live OAuth2
VK.com OAuth2
Dailymotion OAuth2
Disqus OAuth2
Dropbox OAuth
Evernote OAuth (sandbox mode)
Fitbit OAuth
Flickr OAuth
Soundcloud OAuth2
ThisIsMyJam OAuth1
Stocktwits OAuth2
Tripit OAuth
Clef OAuth2
Twilio
Xing OAuth
Yandex OAuth2
Podio OAuth2
Reddit OAuth2
Persona
python-social-auth-0.2.21/examples/pyramid_example/example/__init__.py0000644000175500017550000000234012754357263025635 0ustar debacledebacleimport sys sys.path.append('../..') from pyramid.config import Configurator from pyramid.session import UnencryptedCookieSessionFactoryConfig from sqlalchemy import engine_from_config from social.apps.pyramid_app.models import init_social from .models import DBSession, Base def main(global_config, **settings): """This function returns a Pyramid WSGI application.""" engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine session_factory = UnencryptedCookieSessionFactoryConfig('thisisasecret') config = Configurator(settings=settings, session_factory=session_factory, autocommit=True) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_request_method('example.auth.get_user', 'user', reify=True) config.add_route('home', '/') config.add_route('done', '/done') config.include('example.settings') config.include('example.local_settings') config.include('social.apps.pyramid_app') init_social(config, Base, DBSession) config.scan() config.scan('social.apps.pyramid_app') return config.make_wsgi_app() python-social-auth-0.2.21/examples/pyramid_example/example/settings.py0000644000175500017550000000463612754357263025750 0ustar debacledebacleSOCIAL_AUTH_SETTINGS = { 'SOCIAL_AUTH_LOGIN_URL': '/', 'SOCIAL_AUTH_LOGIN_REDIRECT_URL': '/done', 'SOCIAL_AUTH_USER_MODEL': 'example.models.User', 'SOCIAL_AUTH_LOGIN_FUNCTION': 'example.auth.login_user', 'SOCIAL_AUTH_LOGGEDIN_FUNCTION': 'example.auth.login_required', 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( 'social.backends.twitter.TwitterOAuth', 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth2', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.twilio.TwilioAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', 'social.backends.upwork.UpworkOAuth', ) } def includeme(config): config.registry.settings.update(SOCIAL_AUTH_SETTINGS) python-social-auth-0.2.21/examples/pyramid_example/example/scripts/0000755000175500017550000000000012754357263025214 5ustar debacledebaclepython-social-auth-0.2.21/examples/pyramid_example/example/scripts/__init__.py0000644000175500017550000000001212754357263027316 0ustar debacledebacle# package python-social-auth-0.2.21/examples/pyramid_example/example/scripts/initializedb.py0000644000175500017550000000171012754357263030234 0ustar debacledebacleimport os import sys sys.path.append('../..') from sqlalchemy import engine_from_config from pyramid.paster import get_appsettings, setup_logging from pyramid.scripts.common import parse_vars from social.apps.pyramid_app.models import init_social from example.models import DBSession, Base from example.settings import SOCIAL_AUTH_SETTINGS def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s [var=value]\n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) < 2: usage(argv) config_uri = argv[1] options = parse_vars(argv[2:]) setup_logging(config_uri) settings = get_appsettings(config_uri, options=options) init_social(SOCIAL_AUTH_SETTINGS, Base, DBSession) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) if __name__ == '__main__': main() python-social-auth-0.2.21/examples/pyramid_example/example/auth.py0000644000175500017550000000133112754357263025036 0ustar debacledebaclefrom pyramid.events import subscriber, BeforeRender from social.apps.pyramid_app.utils import backends from example.models import DBSession, User def login_user(backend, user, user_social_auth): backend.strategy.session_set('user_id', user.id) def login_required(request): return getattr(request, 'user', None) is not None def get_user(request): user_id = request.session.get('user_id') if user_id: user = DBSession.query(User)\ .filter(User.id == user_id)\ .first() else: user = None return user @subscriber(BeforeRender) def add_social(event): request = event['request'] event['social'] = backends(request, request.user) python-social-auth-0.2.21/examples/pyramid_example/example/views.py0000644000175500017550000000034712754357263025240 0ustar debacledebaclefrom pyramid.view import view_config @view_config(route_name='home', renderer='templates/home.pt') def home(request): return {} @view_config(route_name='done', renderer='templates/done.pt') def done(request): return {} python-social-auth-0.2.21/examples/pyramid_example/example/tests.py0000644000175500017550000000273312754357263025246 0ustar debacledebacleimport unittest import transaction from pyramid import testing from .models import DBSession class TestMyViewSuccessCondition(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine engine = create_engine('sqlite://') from .models import ( Base, MyModel, ) DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = MyModel(name='one', value=55) DBSession.add(model) def tearDown(self): DBSession.remove() testing.tearDown() def test_passing_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'example') class TestMyViewFailureCondition(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine engine = create_engine('sqlite://') from .models import ( Base, MyModel, ) DBSession.configure(bind=engine) def tearDown(self): DBSession.remove() testing.tearDown() def test_failing_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info.status_int, 500) python-social-auth-0.2.21/examples/pyramid_example/production.ini0000644000175500017550000000225012754357263024760 0ustar debacledebacle### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:example pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/test.db [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, example, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_example] level = WARN handlers = qualname = example [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s python-social-auth-0.2.21/examples/pyramid_example/setup.py0000644000175500017550000000225612754357263023611 0ustar debacledebacleimport os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, 'README.txt')) as f: README = f.read() with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ 'pyramid', 'SQLAlchemy', 'transaction', 'pyramid_tm', 'pyramid_debugtoolbar', 'zope.sqlalchemy', 'waitress', 'pyramid_chameleon', ] setup(name='example', version='0.0', description='example', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web wsgi bfg pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='example', install_requires=requires, entry_points="""\ [paste.app_factory] main = example:main [console_scripts] initialize_example_db = example.scripts.initializedb:main """, ) python-social-auth-0.2.21/examples/pyramid_example/CHANGES.txt0000644000175500017550000000003412754357263023700 0ustar debacledebacle0.0 --- - Initial version python-social-auth-0.2.21/examples/pyramid_example/requirements.txt0000644000175500017550000000003312754357263025352 0ustar debacledebaclepython-social-auth pyramid python-social-auth-0.2.21/examples/django_me_example/0000755000175500017550000000000012754357263022350 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_me_example/example/0000755000175500017550000000000012754357263024003 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_me_example/example/urls.py0000644000175500017550000000127712754357263025351 0ustar debacledebaclefrom django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', url(r'^$', 'example.app.views.home'), url(r'^admin/', include(admin.site.urls)), url(r'^email-sent/', 'example.app.views.validation_sent'), url(r'^login/$', 'example.app.views.home'), url(r'^logout/$', 'example.app.views.logout'), url(r'^done/$', 'example.app.views.done', name='done'), url(r'^ajax-auth/(?P[^/]+)/$', 'example.app.views.ajax_auth', name='ajax-auth'), url(r'^email/$', 'example.app.views.require_email', name='require_email'), url(r'', include('social.apps.django_app.urls', namespace='social')) ) python-social-auth-0.2.21/examples/django_me_example/example/wsgi.py0000644000175500017550000000215312754357263025327 0ustar debacledebacle""" WSGI config for dj project. This module contains the WSGI application used by Django's development server and any production WSGI deployments. It should expose a module-level variable named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover this application via the ``WSGI_APPLICATION`` setting. Usually you will have the standard Django WSGI application here, but it also might make sense to replace the whole Django WSGI application with a custom one that later delegates to the Django one. For example, you could introduce WSGI middleware here, or combine a Django application with an application of another framework. """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. from django.core.wsgi import get_wsgi_application application = get_wsgi_application() # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) python-social-auth-0.2.21/examples/django_me_example/example/templates/0000755000175500017550000000000012754357263026001 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_me_example/example/templates/home.html0000644000175500017550000004341112754357263027622 0ustar debacledebacle{% load backend_utils %} Python Social Auth

Python Social Auth

{% if user.is_authenticated %}
You are logged in as {{ user.username }}!
{% endif %}
{% for name, backend in available_backends|legacy_backends %} {% associated backend %} {% if association %}
{% csrf_token %} Disconnect {{ backend|backend_name }}
{% else %} {{ backend|backend_name }} {% endif %} {% endfor %} Ajax
{% if backend %} {% endif %} {% if plus_id %}
{% csrf_token %}
{% endif %} python-social-auth-0.2.21/examples/django_me_example/example/__init__.py0000644000175500017550000000000012754357263026102 0ustar debacledebaclepython-social-auth-0.2.21/examples/django_me_example/example/settings.py0000644000175500017550000001642512754357263026225 0ustar debacledebacleimport sys from os.path import abspath, dirname, join import mongoengine sys.path.insert(0, '../..') DEBUG = True TEMPLATE_DEBUG = DEBUG ROOT_PATH = abspath(dirname(__file__)) ADMINS = ( # ('Your Name', 'your_email@example.com'), ) MANAGERS = ADMINS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.dummy', } } TIME_ZONE = 'America/Montevideo' LANGUAGE_CODE = 'en-us' SITE_ID = 1 USE_I18N = True USE_L10N = True USE_TZ = True MEDIA_ROOT = '' MEDIA_URL = '' STATIC_ROOT = '' STATIC_URL = '/static/' STATICFILES_DIRS = ( # Put strings here, like "/home/html/static" or "C:/www/django/static". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) SECRET_KEY = '#$5btppqih8=%ae^#&7en#kyi!vh%he9rg=ed#hm6fnw9^=umc' TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) ROOT_URLCONF = 'example.urls' # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'example.wsgi.application' TEMPLATE_DIRS = ( join(ROOT_PATH, 'templates'), ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'mongoengine.django.mongo_auth', 'social.apps.django_app.me', 'example.app', ) LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' } }, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, } } TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'django.contrib.messages.context_processors.messages', 'social.apps.django_app.context_processors.backends', ) AUTH_USER_MODEL = 'mongo_auth.MongoUser' MONGOENGINE_USER_DOCUMENT = 'mongoengine.django.auth.User' SESSION_ENGINE = 'mongoengine.django.sessions' SESSION_SERIALIZER = 'mongoengine.django.sessions.BSONSerializer' mongoengine.connect('psa', host='mongodb://localhost/psa') # MONGOENGINE_USER_DOCUMENT = 'example.app.models.User' # SOCIAL_AUTH_USER_MODEL = 'example.app.models.User' AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.linkedin.LinkedinOAuth2', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth2', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.twilio.TwilioAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.douban.DoubanOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.mixcloud.MixcloudOAuth2', 'social.backends.rdio.RdioOAuth1', 'social.backends.rdio.RdioOAuth2', 'social.backends.yammer.YammerOAuth2', 'social.backends.stackoverflow.StackoverflowOAuth2', 'social.backends.readability.ReadabilityOAuth', 'social.backends.sketchfab.SketchfabOAuth2', 'social.backends.skyrock.SkyrockOAuth', 'social.backends.tumblr.TumblrOAuth', 'social.backends.reddit.RedditOAuth2', 'social.backends.steam.SteamOpenId', 'social.backends.podio.PodioOAuth2', 'social.backends.amazon.AmazonOAuth2', 'social.backends.email.EmailAuth', 'social.backends.username.UsernameAuth', 'social.backends.wunderlist.WunderlistOAuth2', 'social.backends.upwork.UpworkOAuth', 'mongoengine.django.auth.MongoEngineBackend', 'django.contrib.auth.backends.ModelBackend', ) LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/done/' URL_PATH = '' SOCIAL_AUTH_STRATEGY = 'social.strategies.django_strategy.DjangoStrategy' SOCIAL_AUTH_STORAGE = 'social.apps.django_app.me.models.DjangoStorage' SOCIAL_AUTH_GOOGLE_OAUTH_SCOPE = [ 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/userinfo.profile' ] # SOCIAL_AUTH_EMAIL_FORM_URL = '/signup-email' SOCIAL_AUTH_EMAIL_FORM_HTML = 'email_signup.html' SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION = 'example.app.mail.send_validation' SOCIAL_AUTH_EMAIL_VALIDATION_URL = '/email-sent/' # SOCIAL_AUTH_USERNAME_FORM_URL = '/signup-username' SOCIAL_AUTH_USERNAME_FORM_HTML = 'username_signup.html' SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'example.app.pipeline.require_email', 'social.pipeline.mail.mail_validation', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details' ) TEST_RUNNER = 'django.test.runner.DiscoverRunner' try: from example.local_settings import * except ImportError: pass python-social-auth-0.2.21/examples/django_me_example/example/app/0000755000175500017550000000000012754357263024563 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_me_example/example/app/mail.py0000644000175500017550000000075612754357263026067 0ustar debacledebaclefrom django.conf import settings from django.core.mail import send_mail from django.core.urlresolvers import reverse def send_validation(strategy, backend, code): url = '{0}?verification_code={1}'.format( reverse('social:complete', args=(backend.name,)), code.code ) url = strategy.request.build_absolute_uri(url) send_mail('Validate your account', 'Validate your account {0}'.format(url), settings.EMAIL_FROM, [code.email], fail_silently=False) python-social-auth-0.2.21/examples/django_me_example/example/app/templatetags/0000755000175500017550000000000012754357263027255 5ustar debacledebaclepython-social-auth-0.2.21/examples/django_me_example/example/app/templatetags/__init__.py0000644000175500017550000000000012754357263031354 0ustar debacledebaclepython-social-auth-0.2.21/examples/django_me_example/example/app/templatetags/backend_utils.py0000644000175500017550000000421312754357263032436 0ustar debacledebacleimport re from django import template from social.backends.oauth import OAuthAuth from social.apps.django_app.me.models import UserSocialAuth register = template.Library() name_re = re.compile(r'([^O])Auth') @register.filter def backend_name(backend): name = backend.__class__.__name__ name = name.replace('OAuth', ' OAuth') name = name.replace('OpenId', ' OpenId') name = name.replace('Sandbox', '') name = name_re.sub(r'\1 Auth', name) return name @register.filter def backend_class(backend): return backend.name.replace('-', ' ') @register.filter def icon_name(name): return { 'stackoverflow': 'stack-overflow', 'google-oauth': 'google', 'google-oauth2': 'google', 'google-openidconnect': 'google', 'yahoo-oauth': 'yahoo', 'facebook-app': 'facebook', 'email': 'envelope', 'vimeo': 'vimeo-square', 'linkedin-oauth2': 'linkedin', 'vk-oauth2': 'vk', 'live': 'windows', 'username': 'user', }.get(name, name) @register.filter def social_backends(backends): backends = [(name, backend) for name, backend in backends.items() if name not in ['username', 'email']] backends.sort(key=lambda b: b[0]) return [backends[n:n + 10] for n in range(0, len(backends), 10)] @register.filter def legacy_backends(backends): backends = [(name, backend) for name, backend in backends.items() if name in ['username', 'email']] backends.sort(key=lambda b: b[0]) return backends @register.filter def oauth_backends(backends): backends = [(name, backend) for name, backend in backends.items() if issubclass(backend, OAuthAuth)] backends.sort(key=lambda b: b[0]) return backends @register.simple_tag(takes_context=True) def associated(context, backend): user = context.get('user') context['association'] = None if user and user.is_authenticated(): try: context['association'] = UserSocialAuth.objects.filter( user=user, provider=backend.name )[0] except IndexError: pass return '' python-social-auth-0.2.21/examples/django_me_example/example/app/decorators.py0000644000175500017550000000072112754357263027302 0ustar debacledebaclefrom functools import wraps from django.template import RequestContext from django.shortcuts import render_to_response def render_to(tpl): def decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): out = func(request, *args, **kwargs) if isinstance(out, dict): out = render_to_response(tpl, out, RequestContext(request)) return out return wrapper return decorator python-social-auth-0.2.21/examples/django_me_example/example/app/models.py0000644000175500017550000000000012754357263026406 0ustar debacledebaclepython-social-auth-0.2.21/examples/django_me_example/example/app/__init__.py0000644000175500017550000000000012754357263026662 0ustar debacledebaclepython-social-auth-0.2.21/examples/django_me_example/example/app/pipeline.py0000644000175500017550000000070412754357263026743 0ustar debacledebaclefrom django.shortcuts import redirect from social.pipeline.partial import partial @partial def require_email(strategy, details, user=None, is_new=False, *args, **kwargs): if kwargs.get('ajax') or user and user.email: return elif is_new and not details.get('email'): email = strategy.request_data().get('email') if email: details['email'] = email else: return redirect('require_email') python-social-auth-0.2.21/examples/django_me_example/example/app/views.py0000644000175500017550000000422212754357263026272 0ustar debacledebacleimport json from django.conf import settings from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import redirect from django.contrib.auth.decorators import login_required from django.contrib.auth import logout as auth_logout, login from social.backends.oauth import BaseOAuth1, BaseOAuth2 from social.backends.google import GooglePlusAuth from social.backends.utils import load_backends from social.apps.django_app.utils import psa from example.app.decorators import render_to def logout(request): """Logs out user""" auth_logout(request) return redirect('/') def context(**extra): return dict({ 'plus_id': getattr(settings, 'SOCIAL_AUTH_GOOGLE_PLUS_KEY', None), 'plus_scope': ' '.join(GooglePlusAuth.DEFAULT_SCOPE), 'available_backends': load_backends(settings.AUTHENTICATION_BACKENDS) }, **extra) @render_to('home.html') def home(request): """Home view, displays login mechanism""" if request.user.is_authenticated(): return redirect('done') return context() @login_required @render_to('home.html') def done(request): """Login complete view, displays user data""" return context() @render_to('home.html') def validation_sent(request): return context( validation_sent=True, email=request.session.get('email_validation_address') ) @render_to('home.html') def require_email(request): backend = request.session['partial_pipeline']['backend'] return context(email_required=True, backend=backend) @psa('social:complete') def ajax_auth(request, backend): if isinstance(request.backend, BaseOAuth1): token = { 'oauth_token': request.REQUEST.get('access_token'), 'oauth_token_secret': request.REQUEST.get('access_token_secret'), } elif isinstance(request.backend, BaseOAuth2): token = request.REQUEST.get('access_token') else: raise HttpResponseBadRequest('Wrong backend type') user = request.backend.do_auth(token, ajax=True) login(request, user) data = {'id': user.id, 'username': user.username} return HttpResponse(json.dumps(data), mimetype='application/json') python-social-auth-0.2.21/examples/django_me_example/requirements.txt0000644000175500017550000000006712754357263025637 0ustar debacledebacledjango>=1.4,<1.8 mongoengine>=0.8.6 python-social-auth python-social-auth-0.2.21/examples/django_me_example/manage.py0000755000175500017550000000037012754357263024155 0ustar debacledebacle#!/usr/bin/env python import os import sys if __name__ == '__main__': os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) python-social-auth-0.2.21/examples/flask_me_example/0000755000175500017550000000000012754357263022206 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_me_example/routes/0000755000175500017550000000000012754357263023527 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_me_example/routes/__init__.py0000644000175500017550000000012212754357263025633 0ustar debacledebaclefrom flask_me_example.routes import main from social.apps.flask_app import routes python-social-auth-0.2.21/examples/flask_me_example/routes/main.py0000644000175500017550000000061212754357263025024 0ustar debacledebaclefrom flask import render_template, redirect from flask_login import login_required, logout_user from flask_me_example import app @app.route('/') def main(): return render_template('home.html') @app.route('/done/') @login_required def done(): return render_template('done.html') @app.route('/logout') def logout(): """Logout view""" logout_user() return redirect('/') python-social-auth-0.2.21/examples/flask_me_example/templates/0000755000175500017550000000000012754357263024204 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_me_example/templates/base.html0000644000175500017550000000102612754357263026003 0ustar debacledebacle Social {% block content %}{% endblock %} {% block scripts %}{% endblock %} python-social-auth-0.2.21/examples/flask_me_example/templates/home.html0000644000175500017550000001071612754357263026027 0ustar debacledebacle{% extends "base.html" %} {% block content %} Google OAuth2
Google OAuth
Google OpenId
Twitter OAuth
Yahoo OpenId
Yahoo OAuth
Stripe OAuth2
Facebook OAuth2
Facebook App
Angel OAuth2
Behance OAuth2
Bitbucket OAuth
Box OAuth2
LinkedIn OAuth
Github OAuth2
Foursquare OAuth2
Instagram OAuth2
Live OAuth2
VK.com OAuth2
Dailymotion OAuth2
Disqus OAuth2
Dropbox OAuth
Evernote OAuth (sandbox mode)
Fitbit OAuth
Flickr OAuth
Soundcloud OAuth2
LastFm
ThisIsMyJam OAuth1
Stocktwits OAuth2
Tripit OAuth
Clef OAuth2
Twilio
Xing OAuth
Yandex OAuth2
Podio OAuth2
Persona
{% endblock %} {% block scripts %} {% endblock %} python-social-auth-0.2.21/examples/flask_me_example/templates/done.html0000644000175500017550000000107312754357263026020 0ustar debacledebacle{% extends "base.html" %} {% block content %}

You are logged in as {{ user.username }}!

Associated:

{% for assoc in backends.associated %}
{{ assoc.provider }}
{% endfor %}

Associate:

    {% for name in backends.not_associated %}
  • {{ name }}
  • {% endfor %}
{% endblock %} python-social-auth-0.2.21/examples/flask_me_example/__init__.py0000644000175500017550000000230512754357263024317 0ustar debacledebacleimport sys from flask import Flask, g from flask_login import LoginManager, current_user from flask.ext.mongoengine import MongoEngine sys.path.append('../..') from social.apps.flask_app.routes import social_auth from social.apps.flask_app.me.models import init_social from social.apps.flask_app.template_filters import backends # App app = Flask(__name__) app.config.from_object('flask_me_example.settings') app.debug = True try: app.config.from_object('flask_me_example.local_settings') except ImportError: pass # DB db = MongoEngine(app) app.register_blueprint(social_auth) init_social(app, db) login_manager = LoginManager() login_manager.login_view = 'main' login_manager.login_message = '' login_manager.init_app(app) from flask_me_example import models from flask_me_example import routes @login_manager.user_loader def load_user(userid): try: return models.user.User.objects.get(id=userid) except (TypeError, ValueError): pass @app.before_request def global_user(): g.user = current_user @app.context_processor def inject_user(): try: return {'user': g.user} except AttributeError: return {'user': None} app.context_processor(backends) python-social-auth-0.2.21/examples/flask_me_example/settings.py0000644000175500017550000000442612754357263024426 0ustar debacledebaclefrom flask_me_example import app app.debug = True SECRET_KEY = 'random-secret-key' SESSION_COOKIE_NAME = 'psa_session' DEBUG = False MONGODB_SETTINGS = {'DB': 'psa_db'} DEBUG_TB_INTERCEPT_REDIRECTS = False SESSION_PROTECTION = 'strong' SOCIAL_AUTH_LOGIN_URL = '/' SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/done/' SOCIAL_AUTH_USER_MODEL = 'flask_me_example.models.user.User' SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth2', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.lastfm.LastFmAuth', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', 'social.backends.upwork.UpworkOAuth', ) python-social-auth-0.2.21/examples/flask_me_example/models/0000755000175500017550000000000012754357263023471 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_me_example/models/user.py0000644000175500017550000000064712754357263025030 0ustar debacledebaclefrom mongoengine import StringField, EmailField, BooleanField from flask_login import UserMixin from flask_me_example import db class User(db.Document, UserMixin): username = StringField(max_length=200) password = StringField(max_length=200, default='') name = StringField(max_length=100) email = EmailField() active = BooleanField(default=True) def is_active(self): return self.active python-social-auth-0.2.21/examples/flask_me_example/models/__init__.py0000644000175500017550000000012512754357263025600 0ustar debacledebaclefrom flask_me_example.models import user from social.apps.flask_app.me import models python-social-auth-0.2.21/examples/flask_me_example/requirements.txt0000644000175500017550000000012512754357263025470 0ustar debacledebacleFlask Flask-Login Flask-Script Werkzeug Jinja2 mongoengine==0.8.4 python-social-auth python-social-auth-0.2.21/examples/flask_me_example/manage.py0000755000175500017550000000054112754357263024013 0ustar debacledebacle#!/usr/bin/env python import sys from flask.ext.script import Server, Manager, Shell sys.path.append('..') from flask_me_example import app, db manager = Manager(app) manager.add_command('runserver', Server()) manager.add_command('shell', Shell(make_context=lambda: { 'app': app, 'db': db })) if __name__ == '__main__': manager.run() python-social-auth-0.2.21/examples/flask_example/0000755000175500017550000000000012754357263021525 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_example/routes/0000755000175500017550000000000012754357263023046 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_example/routes/__init__.py0000644000175500017550000000011712754357263025156 0ustar debacledebaclefrom flask_example.routes import main from social.apps.flask_app import routes python-social-auth-0.2.21/examples/flask_example/routes/main.py0000644000175500017550000000060712754357263024347 0ustar debacledebaclefrom flask import render_template, redirect from flask_login import login_required, logout_user from flask_example import app @app.route('/') def main(): return render_template('home.html') @login_required @app.route('/done/') def done(): return render_template('done.html') @app.route('/logout') def logout(): """Logout view""" logout_user() return redirect('/') python-social-auth-0.2.21/examples/flask_example/templates/0000755000175500017550000000000012754357263023523 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_example/templates/base.html0000644000175500017550000000102612754357263025322 0ustar debacledebacle Social {% block content %}{% endblock %} {% block scripts %}{% endblock %} python-social-auth-0.2.21/examples/flask_example/templates/home.html0000644000175500017550000001072512754357263025346 0ustar debacledebacle{% extends "base.html" %} {% block content %} Google OAuth2
Google OAuth
Google OpenId
Twitter OAuth
Yahoo OpenId
Yahoo OAuth
Stripe OAuth2
Facebook OAuth2
Facebook App
Angel OAuth2
Behance OAuth2
Bitbucket OAuth
Box OAuth2
LinkedIn OAuth
Github OAuth2
Foursquare OAuth2
Instagram OAuth2
Live OAuth2
VK.com OAuth2
Dailymotion OAuth2
Disqus OAuth2
Dropbox OAuth
Evernote OAuth (sandbox mode)
Fitbit OAuth
Flickr OAuth
Soundcloud OAuth2
ThisIsMyJam OAuth1
Stocktwits OAuth2
Tripit OAuth
Clef OAuth2
Twilio
Xing OAuth
Yandex OAuth2
Podio OAuth2
MineID OAuth2
Persona
{% endblock %} {% block scripts %} {% endblock %} python-social-auth-0.2.21/examples/flask_example/templates/done.html0000644000175500017550000000107312754357263025337 0ustar debacledebacle{% extends "base.html" %} {% block content %}

You are logged in as {{ user.username }}!

Associated:

{% for assoc in backends.associated %}
{{ assoc.provider }}
{% endfor %}

Associate:

    {% for name in backends.not_associated %}
  • {{ name }}
  • {% endfor %}
{% endblock %} python-social-auth-0.2.21/examples/flask_example/__init__.py0000755000175500017550000000313312754357263023641 0ustar debacledebacleimport sys from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from flask import Flask, g from flask_login import LoginManager, current_user sys.path.append('../..') from social.apps.flask_app.routes import social_auth from social.apps.flask_app.template_filters import backends from social.apps.flask_app.default.models import init_social # App app = Flask(__name__) app.config.from_object('flask_example.settings') try: app.config.from_object('flask_example.local_settings') except ImportError: pass # DB engine = create_engine(app.config['SQLALCHEMY_DATABASE_URI']) Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) db_session = scoped_session(Session) app.register_blueprint(social_auth) init_social(app, db_session) login_manager = LoginManager() login_manager.login_view = 'main' login_manager.login_message = '' login_manager.init_app(app) from flask_example import models from flask_example import routes @login_manager.user_loader def load_user(userid): try: return models.user.User.query.get(int(userid)) except (TypeError, ValueError): pass @app.before_request def global_user(): # evaluate proxy value g.user = current_user._get_current_object() @app.teardown_appcontext def commit_on_success(error=None): if error is None: db_session.commit() else: db_session.rollback() db_session.remove() @app.context_processor def inject_user(): try: return {'user': g.user} except AttributeError: return {'user': None} app.context_processor(backends) python-social-auth-0.2.21/examples/flask_example/settings.py0000644000175500017550000000440312754357263023740 0ustar debacledebaclefrom os.path import dirname, abspath SECRET_KEY = 'random-secret-key' SESSION_COOKIE_NAME = 'psa_session' DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite:////%s/test.db' % dirname(abspath(__file__)) DEBUG_TB_INTERCEPT_REDIRECTS = False SESSION_PROTECTION = 'strong' SOCIAL_AUTH_LOGIN_URL = '/' SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/done/' SOCIAL_AUTH_USER_MODEL = 'flask_example.models.user.User' SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth2', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', 'social.backends.upwork.UpworkOAuth', ) python-social-auth-0.2.21/examples/flask_example/models/0000755000175500017550000000000012754357263023010 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_example/models/user.py0000644000175500017550000000112512754357263024337 0ustar debacledebaclefrom sqlalchemy import Column, String, Integer, Boolean from sqlalchemy.ext.declarative import declarative_base from flask_login import UserMixin from flask_example import db_session Base = declarative_base() Base.query = db_session.query_property() class User(Base, UserMixin): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(200)) password = Column(String(200), default='') name = Column(String(100)) email = Column(String(200)) active = Column(Boolean, default=True) def is_active(self): return self.active python-social-auth-0.2.21/examples/flask_example/models/__init__.py0000644000175500017550000000012712754357263025121 0ustar debacledebaclefrom flask_example.models import user from social.apps.flask_app.default import models python-social-auth-0.2.21/examples/flask_example/requirements.txt0000644000175500017550000000007012754357263025006 0ustar debacledebacleFlask Flask-Login Flask-Script Werkzeug pysqlite Jinja2 python-social-auth-0.2.21/examples/flask_example/manage.py0000755000175500017550000000112612754357263023332 0ustar debacledebacle#!/usr/bin/env python import sys from flask.ext.script import Server, Manager, Shell sys.path.append('..') from flask_example import app, db_session, engine manager = Manager(app) manager.add_command('runserver', Server()) manager.add_command('shell', Shell(make_context=lambda: { 'app': app, 'db_session': db_session })) @manager.command def syncdb(): from flask_example.models import user from social.apps.flask_app.default import models user.Base.metadata.create_all(engine) models.PSABase.metadata.create_all(engine) if __name__ == '__main__': manager.run() python-social-auth-0.2.21/examples/flask_peewee_example/0000755000175500017550000000000012754357263023057 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_peewee_example/routes/0000755000175500017550000000000012754357263024400 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_peewee_example/routes/__init__.py0000644000175500017550000000011712754357263026510 0ustar debacledebaclefrom flask_example.routes import main from social.apps.flask_app import routes python-social-auth-0.2.21/examples/flask_peewee_example/routes/main.py0000644000175500017550000000061312754357263025676 0ustar debacledebaclefrom flask import render_template, redirect from flask.ext.login import login_required, logout_user from flask_example import app @app.route('/') def main(): return render_template('home.html') @login_required @app.route('/done/') def done(): return render_template('done.html') @app.route('/logout') def logout(): """Logout view""" logout_user() return redirect('/') python-social-auth-0.2.21/examples/flask_peewee_example/templates/0000755000175500017550000000000012754357263025055 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_peewee_example/templates/base.html0000644000175500017550000000102612754357263026654 0ustar debacledebacle Social {% block content %}{% endblock %} {% block scripts %}{% endblock %} python-social-auth-0.2.21/examples/flask_peewee_example/templates/home.html0000644000175500017550000001072512754357263026700 0ustar debacledebacle{% extends "base.html" %} {% block content %} Google OAuth2
Google OAuth
Google OpenId
Twitter OAuth
Yahoo OpenId
Yahoo OAuth
Stripe OAuth2
Facebook OAuth2
Facebook App
Angel OAuth2
Behance OAuth2
Bitbucket OAuth
Box OAuth2
LinkedIn OAuth
Github OAuth2
Foursquare OAuth2
Instagram OAuth2
Live OAuth2
VK.com OAuth2
Dailymotion OAuth2
Disqus OAuth2
Dropbox OAuth
Evernote OAuth (sandbox mode)
Fitbit OAuth
Flickr OAuth
Soundcloud OAuth2
ThisIsMyJam OAuth1
Stocktwits OAuth2
Tripit OAuth
Clef OAuth2
Twilio
Xing OAuth
Yandex OAuth2
Podio OAuth2
MineID OAuth2
Persona
{% endblock %} {% block scripts %} {% endblock %} python-social-auth-0.2.21/examples/flask_peewee_example/templates/done.html0000644000175500017550000000107312754357263026671 0ustar debacledebacle{% extends "base.html" %} {% block content %}

You are logged in as {{ user.username }}!

Associated:

{% for assoc in backends.associated %}
{{ assoc.provider }}
{% endfor %}

Associate:

    {% for name in backends.not_associated %}
  • {{ name }}
  • {% endfor %}
{% endblock %} python-social-auth-0.2.21/examples/flask_peewee_example/__init__.py0000644000175500017550000000237112754357263025173 0ustar debacledebacleimport sys from flask import Flask, g from flask.ext import login sys.path.append('../..') from social.apps.flask_app.routes import social_auth from social.apps.flask_app.template_filters import backends from social.apps.flask_app.peewee.models import * from peewee import * # App app = Flask(__name__) app.config.from_object('flask_example.settings') try: app.config.from_object('flask_example.local_settings') except ImportError: pass from models.user import database_proxy, User # DB database = SqliteDatabase('test.db') database_proxy.initialize(database) app.register_blueprint(social_auth) init_social(app, database) login_manager = login.LoginManager() login_manager.login_view = 'main' login_manager.login_message = '' login_manager.init_app(app) from flask_example import models from flask_example import routes @login_manager.user_loader def load_user(userid): try: us = User.get(User.id == userid) return us except User.DoesNotExist: pass @app.before_request def global_user(): g.user = login.current_user._get_current_object() @app.context_processor def inject_user(): try: return {'user': g.user} except AttributeError: return {'user': None} app.context_processor(backends) python-social-auth-0.2.21/examples/flask_peewee_example/settings.py0000644000175500017550000000432312754357263025273 0ustar debacledebaclefrom os.path import dirname, abspath SECRET_KEY = 'random-secret-key' SESSION_COOKIE_NAME = 'psa_session' DEBUG = True DEBUG_TB_INTERCEPT_REDIRECTS = False SESSION_PROTECTION = 'strong' SOCIAL_AUTH_STORAGE = 'social.apps.flask_app.peewee.models.FlaskStorage' SOCIAL_AUTH_LOGIN_URL = '/' SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/done/' SOCIAL_AUTH_USER_MODEL = 'flask_example.models.user.User' SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth2', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', ) python-social-auth-0.2.21/examples/flask_peewee_example/models/0000755000175500017550000000000012754357263024342 5ustar debacledebaclepython-social-auth-0.2.21/examples/flask_peewee_example/models/user.py0000644000175500017550000000134312754357263025673 0ustar debacledebaclefrom peewee import * from datetime import datetime from flask.ext.login import UserMixin database_proxy = Proxy() # model definitions -- the standard "pattern" is to define a base model class # that specifies which database to use. then, any subclasses will automatically # use the correct storage. class BaseModel(Model): class Meta: database = database_proxy # the user model specifies its fields (or columns) declaratively, like django class User(BaseModel, UserMixin): username = CharField(unique=True) password = CharField(null=True) email = CharField(null=True) active = BooleanField(default=True) join_date = DateTimeField(default=datetime.now) class Meta: order_by = ('username',) python-social-auth-0.2.21/examples/flask_peewee_example/models/__init__.py0000644000175500017550000000027012754357263026452 0ustar debacledebaclefrom flask_example.models import user from social.apps.flask_app.peewee import models # create a peewee database instance -- our models will use this database to # persist information python-social-auth-0.2.21/examples/flask_peewee_example/requirements.txt0000644000175500017550000000007712754357263026347 0ustar debacledebaclePeewee Flask Flask-Login Flask-Script Werkzeug pysqlite Jinja2 python-social-auth-0.2.21/examples/flask_peewee_example/manage.py0000644000175500017550000000113612754357263024662 0ustar debacledebacle#!/usr/bin/env python import sys from flask.ext.script import Server, Manager, Shell sys.path.append('..') from flask_example import app, database manager = Manager(app) manager.add_command('runserver', Server()) manager.add_command('shell', Shell(make_context=lambda: { 'app': app })) @manager.command def syncdb(): from flask_example.models.user import User from social.apps.flask_app.peewee.models import FlaskStorage database.create_tables([User, FlaskStorage.user, FlaskStorage.nonce, FlaskStorage.association, FlaskStorage.code]) if __name__ == '__main__': manager.run() python-social-auth-0.2.21/CHANGELOG.md0000644000175500017550000030747012754357263016720 0ustar debacledebacle# Change Log ## [Unreleased](https://github.com/omab/python-social-auth/tree/HEAD) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.21...HEAD) ## [v0.2.21](https://github.com/omab/python-social-auth/tree/v0.2.21) (2016-08-15) **Closed issues:** - Django Migrations Broken [\#991](https://github.com/omab/python-social-auth/issues/991) **Merged pull requests:** - Fixed Django Migrations [\#993](https://github.com/omab/python-social-auth/pull/993) ([clintonb](https://github.com/clintonb)) - Rewrited pipeline.rst [\#992](https://github.com/omab/python-social-auth/pull/992) ([an0o0nym](https://github.com/an0o0nym)) - fix typo "Piepeline" -\> "Pipeline" [\#990](https://github.com/omab/python-social-auth/pull/990) ([das-g](https://github.com/das-g)) - Fixed Django \< 1.8 broken compatibility [\#986](https://github.com/omab/python-social-auth/pull/986) ([seroy](https://github.com/seroy)) ## [v0.2.20](https://github.com/omab/python-social-auth/tree/v0.2.20) (2016-08-12) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.19...v0.2.20) **Closed issues:** - On production /complete/facebook just times out with a Gateway Timeout [\#972](https://github.com/omab/python-social-auth/issues/972) - Support namespace via : [\#971](https://github.com/omab/python-social-auth/issues/971) - Django Association model missing index [\#967](https://github.com/omab/python-social-auth/issues/967) - VK auth using access token failed. Unable to retrieve email address. [\#943](https://github.com/omab/python-social-auth/issues/943) - ImportError: No module named django\_app [\#935](https://github.com/omab/python-social-auth/issues/935) - ImportError: No module named 'example.local\_settings' with pyramid\_example [\#919](https://github.com/omab/python-social-auth/issues/919) - "'User' object is not callable." issue. [\#895](https://github.com/omab/python-social-auth/issues/895) - Support for the peewee ORM in storage. [\#877](https://github.com/omab/python-social-auth/issues/877) - Meetup.com OAuth2 [\#677](https://github.com/omab/python-social-auth/issues/677) **Merged pull requests:** - fix comment word [\#983](https://github.com/omab/python-social-auth/pull/983) ([alexpantyukhin](https://github.com/alexpantyukhin)) - Added exception handling for user creation race condition in Django [\#975](https://github.com/omab/python-social-auth/pull/975) ([carsongee](https://github.com/carsongee)) - Update facebook api version to v2.7 [\#973](https://github.com/omab/python-social-auth/pull/973) ([c-bata](https://github.com/c-bata)) - Added index to Django Association model [\#969](https://github.com/omab/python-social-auth/pull/969) ([clintonb](https://github.com/clintonb)) - Corrected migration dependency [\#968](https://github.com/omab/python-social-auth/pull/968) ([clintonb](https://github.com/clintonb)) - Removed dep method get\_all\_field\_names method from Django 1.8+ [\#966](https://github.com/omab/python-social-auth/pull/966) ([zsiddique](https://github.com/zsiddique)) - Multiple hosts in redirect sanitaion. [\#965](https://github.com/omab/python-social-auth/pull/965) ([moorchegue](https://github.com/moorchegue)) - "else" scenario in Pyramid html func was causing an exception every time. [\#964](https://github.com/omab/python-social-auth/pull/964) ([moorchegue](https://github.com/moorchegue)) - Allow POST requests for auth method so OpenID forms could use it that way [\#963](https://github.com/omab/python-social-auth/pull/963) ([moorchegue](https://github.com/moorchegue)) - Add redirect\_uri to yammer docs [\#960](https://github.com/omab/python-social-auth/pull/960) ([m3brown](https://github.com/m3brown)) - Fix for flask/SQLAlchemy: commit on save \(but not when using Pyramid\) [\#957](https://github.com/omab/python-social-auth/pull/957) ([aoghina](https://github.com/aoghina)) - Switch from flask.ext.login to flask\_login [\#951](https://github.com/omab/python-social-auth/pull/951) ([EdwardBetts](https://github.com/EdwardBetts)) - username max\_length can be None [\#950](https://github.com/omab/python-social-auth/pull/950) ([EdwardBetts](https://github.com/EdwardBetts)) - Upgrade facebook backend api to latest version \(v2.6\) [\#941](https://github.com/omab/python-social-auth/pull/941) ([stphivos](https://github.com/stphivos)) - Line support added [\#937](https://github.com/omab/python-social-auth/pull/937) ([polyn0m](https://github.com/polyn0m)) - django migration should respect SOCIAL\_AUTH\_USER\_MODEL setting [\#936](https://github.com/omab/python-social-auth/pull/936) ([max-arnold](https://github.com/max-arnold)) - fix first and last name recovery [\#934](https://github.com/omab/python-social-auth/pull/934) ([PhilipGarnero](https://github.com/PhilipGarnero)) - fixes empty uid in coursera backend [\#933](https://github.com/omab/python-social-auth/pull/933) ([CrowbarKZ](https://github.com/CrowbarKZ)) - add support peewee for flask \#877 [\#932](https://github.com/omab/python-social-auth/pull/932) ([alexpantyukhin](https://github.com/alexpantyukhin)) - Fixed typo [\#928](https://github.com/omab/python-social-auth/pull/928) ([arogachev](https://github.com/arogachev)) - Fix mixed-content error of loading http over https scheme after disconnection from social account [\#924](https://github.com/omab/python-social-auth/pull/924) ([andela-kerinoso](https://github.com/andela-kerinoso)) - Add back-end for Edmodo [\#921](https://github.com/omab/python-social-auth/pull/921) ([browniebroke](https://github.com/browniebroke)) - Add Django AppConfig Label of "social\_auth" for migrations [\#916](https://github.com/omab/python-social-auth/pull/916) ([cclay](https://github.com/cclay)) - Update vk.rst [\#907](https://github.com/omab/python-social-auth/pull/907) ([slushkovsky](https://github.com/slushkovsky)) - VULNERABILITY - BaseStrategy.validate\_email\(\) doesn't actually check email address [\#900](https://github.com/omab/python-social-auth/pull/900) ([scottp-dpaw](https://github.com/scottp-dpaw)) - Removed broken link in use cases docs fixing \#860 [\#886](https://github.com/omab/python-social-auth/pull/886) ([RobinStephenson](https://github.com/RobinStephenson)) - Fixes bug where partial pipelines from abandoned login attempts will be resumed … [\#882](https://github.com/omab/python-social-auth/pull/882) ([SeanHayes](https://github.com/SeanHayes)) - Revise battlenet endpoint to return account ID and battletag [\#799](https://github.com/omab/python-social-auth/pull/799) ([ckcollab](https://github.com/ckcollab)) - Fixed 401 client redirect error for reddit backend [\#772](https://github.com/omab/python-social-auth/pull/772) ([opaqe](https://github.com/opaqe)) ## [v0.2.19](https://github.com/omab/python-social-auth/tree/v0.2.19) (2016-04-29) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.18...v0.2.19) **Closed issues:** - \[Flask\] Not Logged in After Redirect [\#913](https://github.com/omab/python-social-auth/issues/913) - Django: type\(social\_user.extra\_data\) == unicode [\#898](https://github.com/omab/python-social-auth/issues/898) - Email is empty in login with Facebook [\#889](https://github.com/omab/python-social-auth/issues/889) **Merged pull requests:** - Storing token\_type in extra\_data field when using OAuth 2.0 [\#912](https://github.com/omab/python-social-auth/pull/912) ([clintonb](https://github.com/clintonb)) - Updates to OpenIdConnectAuth [\#911](https://github.com/omab/python-social-auth/pull/911) ([clintonb](https://github.com/clintonb)) - Corrected default value of JSONField [\#908](https://github.com/omab/python-social-auth/pull/908) ([clintonb](https://github.com/clintonb)) ## [v0.2.18](https://github.com/omab/python-social-auth/tree/v0.2.18) (2016-04-20) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.17...v0.2.18) ## [v0.2.17](https://github.com/omab/python-social-auth/tree/v0.2.17) (2016-04-20) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.16...v0.2.17) **Merged pull requests:** - ADDED: upwork backend [\#904](https://github.com/omab/python-social-auth/pull/904) ([shepilov-vladislav](https://github.com/shepilov-vladislav)) - Add Sketchfab OAuth2 backend [\#901](https://github.com/omab/python-social-auth/pull/901) ([sylvinus](https://github.com/sylvinus)) - django 1.8+ compat to ensure to\_python is always called when accessing result from db.. [\#897](https://github.com/omab/python-social-auth/pull/897) ([sbussetti](https://github.com/sbussetti)) ## [v0.2.16](https://github.com/omab/python-social-auth/tree/v0.2.16) (2016-04-13) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.15...v0.2.16) ## [v0.2.15](https://github.com/omab/python-social-auth/tree/v0.2.15) (2016-04-13) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.14...v0.2.15) **Closed issues:** - Warning with dependency six [\#885](https://github.com/omab/python-social-auth/issues/885) - Password Reset Emails don't come if Authenticated via Python Social Auth [\#881](https://github.com/omab/python-social-auth/issues/881) - I followed the documentation, but it didn't work for me. Would you please let me know where my PIPELINE is wrong? [\#867](https://github.com/omab/python-social-auth/issues/867) - Is this AttributeError caused by facebook settings or python-social-auth? [\#865](https://github.com/omab/python-social-auth/issues/865) - Google: Backend not found [\#862](https://github.com/omab/python-social-auth/issues/862) - Django 1.9.2 ImportError: No module named 'social.apps.django\_app' [\#861](https://github.com/omab/python-social-auth/issues/861) - Microsoft live oauth sign up/sign in issue [\#837](https://github.com/omab/python-social-auth/issues/837) - Redirect url always ends with /\#\_=\_ [\#833](https://github.com/omab/python-social-auth/issues/833) - Google Sign in problem [\#826](https://github.com/omab/python-social-auth/issues/826) - Fitbit oauth2 [\#733](https://github.com/omab/python-social-auth/issues/733) **Merged pull requests:** - Add weixin public number oauth backend. [\#899](https://github.com/omab/python-social-auth/pull/899) ([duoduo369](https://github.com/duoduo369)) - Add support for Untappd as an OAuth v2 backend [\#894](https://github.com/omab/python-social-auth/pull/894) ([svvitale](https://github.com/svvitale)) - add coding oauth [\#892](https://github.com/omab/python-social-auth/pull/892) ([joway](https://github.com/joway)) - Add a backend for Classlink. [\#890](https://github.com/omab/python-social-auth/pull/890) ([antinescience](https://github.com/antinescience)) - Pass response to AuthCancel exception [\#883](https://github.com/omab/python-social-auth/pull/883) ([st4lk](https://github.com/st4lk)) - modifed wrong key names in pocket.py [\#878](https://github.com/omab/python-social-auth/pull/878) ([EunJung-Seo](https://github.com/EunJung-Seo)) - Fix typos [\#869](https://github.com/omab/python-social-auth/pull/869) ([Chronial](https://github.com/Chronial)) - Do not instantiate Logger directly [\#864](https://github.com/omab/python-social-auth/pull/864) ([browniebroke](https://github.com/browniebroke)) - Fix xgettext warning due to unknown encoding [\#856](https://github.com/omab/python-social-auth/pull/856) ([federicobond](https://github.com/federicobond)) - Update base.py [\#852](https://github.com/omab/python-social-auth/pull/852) ([hellvix](https://github.com/hellvix)) - Fix misspelled backend name [\#847](https://github.com/omab/python-social-auth/pull/847) ([victorgutemberg](https://github.com/victorgutemberg)) - Add some tests for Spotify backend + add a backend for Deezer music service [\#845](https://github.com/omab/python-social-auth/pull/845) ([khamaileon](https://github.com/khamaileon)) - \[Fix\] update odnoklasniki docs to new domain ok [\#836](https://github.com/omab/python-social-auth/pull/836) ([vanadium23](https://github.com/vanadium23)) - add github enterprise docs on how to specify the API URL [\#834](https://github.com/omab/python-social-auth/pull/834) ([iserko](https://github.com/iserko)) - Added optional 'include\_email' query param for Twitter backend. [\#829](https://github.com/omab/python-social-auth/pull/829) ([halfstrik](https://github.com/halfstrik)) - Fix ImportError: cannot import name ‘urlencode’ in Python3 [\#828](https://github.com/omab/python-social-auth/pull/828) ([mishbahr](https://github.com/mishbahr)) - Fix wrong evaluation of boolean kwargs [\#824](https://github.com/omab/python-social-auth/pull/824) ([falknes](https://github.com/falknes)) - SAML: raise AuthMissingParameter if idp param missing [\#821](https://github.com/omab/python-social-auth/pull/821) ([omarkhan](https://github.com/omarkhan)) - added support for ArcGIS OAuth2 [\#820](https://github.com/omab/python-social-auth/pull/820) ([aspcanada](https://github.com/aspcanada)) - BaseOAuth2: Store access token in response if it does not exist [\#816](https://github.com/omab/python-social-auth/pull/816) ([kchange](https://github.com/kchange)) - Minor backend fixes [\#815](https://github.com/omab/python-social-auth/pull/815) ([mback2k](https://github.com/mback2k)) - Fix Django 1.10 Deprecation Warning "SubfieldBase has been deprecated." [\#813](https://github.com/omab/python-social-auth/pull/813) ([contracode](https://github.com/contracode)) - Fix typo: "attacht he" -\> "attach the" [\#808](https://github.com/omab/python-social-auth/pull/808) ([smholloway](https://github.com/smholloway)) - Azure AD updates [\#807](https://github.com/omab/python-social-auth/pull/807) ([vinhub](https://github.com/vinhub)) - Remove unused response arg from user\_data method of yandex backend [\#784](https://github.com/omab/python-social-auth/pull/784) ([SrgyPetrov](https://github.com/SrgyPetrov)) - Support all kind of data type \(like uuid\) of User.id on Pyramid [\#769](https://github.com/omab/python-social-auth/pull/769) ([cjltsod](https://github.com/cjltsod)) ## [v0.2.14](https://github.com/omab/python-social-auth/tree/v0.2.14) (2016-01-25) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.13...v0.2.14) **Closed issues:** - Error "imported before its application was loaded" [\#809](https://github.com/omab/python-social-auth/issues/809) - Django 1.9.0 Deprecation Warning [\#804](https://github.com/omab/python-social-auth/issues/804) - Migration error on update 0.2.6 -\> 0.2.7 [\#761](https://github.com/omab/python-social-auth/issues/761) - Backends: user\_data vs extra\_data? [\#759](https://github.com/omab/python-social-auth/issues/759) - example/django\_example twitter error [\#742](https://github.com/omab/python-social-auth/issues/742) - Object of type map has no length [\#633](https://github.com/omab/python-social-auth/issues/633) **Merged pull requests:** - Add support for Drip Email Marketing Site [\#810](https://github.com/omab/python-social-auth/pull/810) ([buddylindsey](https://github.com/buddylindsey)) - Fix Django 1.10 deprecation warnings [\#806](https://github.com/omab/python-social-auth/pull/806) ([yprez](https://github.com/yprez)) - bugs in social\_user and associate\_by\_email return values [\#800](https://github.com/omab/python-social-auth/pull/800) ([falcon1kr](https://github.com/falcon1kr)) - Changed instagram backend to new authorization routes [\#797](https://github.com/omab/python-social-auth/pull/797) ([clybob](https://github.com/clybob)) - Update settings.rst [\#793](https://github.com/omab/python-social-auth/pull/793) ([skolsuper](https://github.com/skolsuper)) - Add naver.com OAuth2 backend [\#789](https://github.com/omab/python-social-auth/pull/789) ([se0kjun](https://github.com/se0kjun)) - Formatter fixes for SAML to support Py2.6 [\#783](https://github.com/omab/python-social-auth/pull/783) ([matburt](https://github.com/matburt)) - Add pinterest backend [\#774](https://github.com/omab/python-social-auth/pull/774) ([scailer](https://github.com/scailer)) - Fix typo [\#768](https://github.com/omab/python-social-auth/pull/768) ([mprunell](https://github.com/mprunell)) - Fixes a few grammar issues in the docs [\#764](https://github.com/omab/python-social-auth/pull/764) ([kevinharvey](https://github.com/kevinharvey)) - use qq openid as username [\#763](https://github.com/omab/python-social-auth/pull/763) ([lneoe](https://github.com/lneoe)) - Fix a few typos in backends [\#760](https://github.com/omab/python-social-auth/pull/760) ([pzrq](https://github.com/pzrq)) - Fix vk backend [\#757](https://github.com/omab/python-social-auth/pull/757) ([truetug](https://github.com/truetug)) - Fix odnoklassniki backend [\#756](https://github.com/omab/python-social-auth/pull/756) ([truetug](https://github.com/truetug)) - Store all tokens when tokens are refreshed [\#753](https://github.com/omab/python-social-auth/pull/753) ([mvschaik](https://github.com/mvschaik)) - Added support for NGPVAN ActionID OpenID [\#750](https://github.com/omab/python-social-auth/pull/750) ([nickcatal](https://github.com/nickcatal)) - Python 3 support for facebook-app backend [\#749](https://github.com/omab/python-social-auth/pull/749) ([jhmaddox](https://github.com/jhmaddox)) - Save extra\_data on login [\#748](https://github.com/omab/python-social-auth/pull/748) ([mvschaik](https://github.com/mvschaik)) - Update URLs to match new site and remove OAuth comment. [\#744](https://github.com/omab/python-social-auth/pull/744) ([lukos](https://github.com/lukos)) - Fitbit OAuth 2.0 support [\#743](https://github.com/omab/python-social-auth/pull/743) ([robbiet480](https://github.com/robbiet480)) - added AuthUnreachableProvider exception to documentation [\#729](https://github.com/omab/python-social-auth/pull/729) ([Qlio](https://github.com/Qlio)) - Add REDIRECT\_STATE = False [\#725](https://github.com/omab/python-social-auth/pull/725) ([webjunkie](https://github.com/webjunkie)) - Tuple in pipeline's documentation should be ended with coma [\#712](https://github.com/omab/python-social-auth/pull/712) ([JerzySpendel](https://github.com/JerzySpendel)) - Fix redirect\_uri issue with tornado reversed url [\#674](https://github.com/omab/python-social-auth/pull/674) ([mvschaik](https://github.com/mvschaik)) ## [v0.2.13](https://github.com/omab/python-social-auth/tree/v0.2.13) (2015-09-25) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.12...v0.2.13) **Closed issues:** - Signup by OAuth access\_token example question [\#737](https://github.com/omab/python-social-auth/issues/737) - Connecting to a "django oAuth toolkit" based oAuth provider [\#727](https://github.com/omab/python-social-auth/issues/727) - Exception Value: 'module' object has no attribute 'FacebookOauth2' [\#722](https://github.com/omab/python-social-auth/issues/722) - Google OAuth2 - stopped working, now getting JSONDecodeError for token response [\#718](https://github.com/omab/python-social-auth/issues/718) - Is there a conflict with django-debug-toolbar? [\#714](https://github.com/omab/python-social-auth/issues/714) - FORM\_HTML and Legacy Auth [\#705](https://github.com/omab/python-social-auth/issues/705) - Authentication process canceled with Spotify auth \(invalid\_client\) [\#703](https://github.com/omab/python-social-auth/issues/703) - \[Question\] How to tell if a user was created or existing [\#701](https://github.com/omab/python-social-auth/issues/701) - Make an abstract verstion of django's UserSocialAuth's model so it can be extended [\#698](https://github.com/omab/python-social-auth/issues/698) - Problem porting from django-social-auth to python-social-auth [\#682](https://github.com/omab/python-social-auth/issues/682) - django\_app/default: Migration 0003\_alter\_email\_max\_length wrong for Django 1.7 [\#622](https://github.com/omab/python-social-auth/issues/622) **Merged pull requests:** - VK API workflow fix if error happens on vk-side [\#736](https://github.com/omab/python-social-auth/pull/736) ([alrusdi](https://github.com/alrusdi)) - Added justgiving.com OAuth2 backend [\#728](https://github.com/omab/python-social-auth/pull/728) ([mwillmott](https://github.com/mwillmott)) - Fix typo in pipeline doc [\#720](https://github.com/omab/python-social-auth/pull/720) ([Andygmb](https://github.com/Andygmb)) - Update facebook.rst [\#717](https://github.com/omab/python-social-auth/pull/717) ([zergu](https://github.com/zergu)) - Support Pyramid Authentication Policies [\#710](https://github.com/omab/python-social-auth/pull/710) ([cjltsod](https://github.com/cjltsod)) - Fix typo [\#709](https://github.com/omab/python-social-auth/pull/709) ([ajoyoommen](https://github.com/ajoyoommen)) - Fix 'QueryDict' object has no attribute 'dicts' [\#707](https://github.com/omab/python-social-auth/pull/707) ([webjunkie](https://github.com/webjunkie)) - Add support for Uber OAuth2 - Uber API v1 [\#706](https://github.com/omab/python-social-auth/pull/706) ([henocdz](https://github.com/henocdz)) - Fix \#703 invalid\_client error with Spotify backend [\#704](https://github.com/omab/python-social-auth/pull/704) ([khamaileon](https://github.com/khamaileon)) - additional "how it fits together" documentation [\#700](https://github.com/omab/python-social-auth/pull/700) ([ccurvey](https://github.com/ccurvey)) - Make an abstract verstion of django's UserSocialAuth's model so it can be extended \(fixes \#698\) [\#699](https://github.com/omab/python-social-auth/pull/699) ([troygrosfield](https://github.com/troygrosfield)) - flask\_me\_example fix [\#696](https://github.com/omab/python-social-auth/pull/696) ([jameslittle](https://github.com/jameslittle)) - removed @app.teardown\_request since it is called before @app.teardown… [\#690](https://github.com/omab/python-social-auth/pull/690) ([asimcan](https://github.com/asimcan)) - Remove debug printing from BaseOAuth2 backend [\#689](https://github.com/omab/python-social-auth/pull/689) ([gcheshkov](https://github.com/gcheshkov)) - support for goclio.eu service [\#686](https://github.com/omab/python-social-auth/pull/686) ([jneves](https://github.com/jneves)) - text -\> content solves "is not JSON serializable" [\#685](https://github.com/omab/python-social-auth/pull/685) ([JordanReiter](https://github.com/JordanReiter)) - Close \#622 by explicitly setting email length \(compatibility with Django 1.7\) [\#684](https://github.com/omab/python-social-auth/pull/684) ([frankier](https://github.com/frankier)) - Add orbi backend [\#683](https://github.com/omab/python-social-auth/pull/683) ([jeyraof](https://github.com/jeyraof)) - Fix Clef backend [\#681](https://github.com/omab/python-social-auth/pull/681) ([jessepollak](https://github.com/jessepollak)) - Meetup.com OAuth2 provider [\#678](https://github.com/omab/python-social-auth/pull/678) ([bluszcz](https://github.com/bluszcz)) - echosign OAuth2 backend [\#676](https://github.com/omab/python-social-auth/pull/676) ([paxapy](https://github.com/paxapy)) ## [v0.2.12](https://github.com/omab/python-social-auth/tree/v0.2.12) (2015-07-10) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.11...v0.2.12) **Closed issues:** - Pipeline `user\_details` not changing empty and protected user fields [\#671](https://github.com/omab/python-social-auth/issues/671) - Instagram: Missing needed parameter state [\#643](https://github.com/omab/python-social-auth/issues/643) - Could not find required distribution python-social-auth [\#638](https://github.com/omab/python-social-auth/issues/638) - Installing python-social-auth as a dependecie for mailman3 with buildout fails [\#623](https://github.com/omab/python-social-auth/issues/623) **Merged pull requests:** - Improve docs on SOCIAL\_AUTH\_NEW\_USER\_REDIRECT\_URL [\#673](https://github.com/omab/python-social-auth/pull/673) ([eshellman](https://github.com/eshellman)) - PR fix `user\_details` pipeline issue [\#672](https://github.com/omab/python-social-auth/pull/672) ([maxsocl](https://github.com/maxsocl)) - Fix cookie handling for tornado [\#667](https://github.com/omab/python-social-auth/pull/667) ([mvschaik](https://github.com/mvschaik)) - added support for Github Enterprise [\#662](https://github.com/omab/python-social-auth/pull/662) ([iserko](https://github.com/iserko)) - Withings Backend [\#658](https://github.com/omab/python-social-auth/pull/658) ([tomasgarzon](https://github.com/tomasgarzon)) - Use official python-saml 2.1.3 release, remove now-unsupported setting [\#657](https://github.com/omab/python-social-auth/pull/657) ([bradenmacdonald](https://github.com/bradenmacdonald)) - Fix wrong placement of changelog commits in 76a27b2 [\#656](https://github.com/omab/python-social-auth/pull/656) ([bradenmacdonald](https://github.com/bradenmacdonald)) - Python3 fixes for Tornado [\#649](https://github.com/omab/python-social-auth/pull/649) ([mvschaik](https://github.com/mvschaik)) - Keep the egg-info directory in the sdist [\#635](https://github.com/omab/python-social-auth/pull/635) ([abompard](https://github.com/abompard)) ## [v0.2.11](https://github.com/omab/python-social-auth/tree/v0.2.11) (2015-06-24) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.10...v0.2.11) **Merged pull requests:** - Added an OAuth2 backend for Bitbucket [\#653](https://github.com/omab/python-social-auth/pull/653) ([mark-adams](https://github.com/mark-adams)) - Updated Bitbucket backends to use newer v2.0 APIs [\#652](https://github.com/omab/python-social-auth/pull/652) ([mark-adams](https://github.com/mark-adams)) - SAML support [\#616](https://github.com/omab/python-social-auth/pull/616) ([bradenmacdonald](https://github.com/bradenmacdonald)) ## [v0.2.10](https://github.com/omab/python-social-auth/tree/v0.2.10) (2015-05-30) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.9...v0.2.10) **Closed issues:** - "UserSocialAuth.user" must be a "MyUser" instance [\#631](https://github.com/omab/python-social-auth/issues/631) - ImportError: No module named packages.urllib3.poolmanager [\#617](https://github.com/omab/python-social-auth/issues/617) - AuthStateMissing: Session value state missing on web.py example integration [\#611](https://github.com/omab/python-social-auth/issues/611) - return pipeline data when doing oauth association [\#610](https://github.com/omab/python-social-auth/issues/610) - Reverse with trailing slash in django urls is broken since 0.2.4 to 0.2.7 [\#609](https://github.com/omab/python-social-auth/issues/609) **Merged pull requests:** - Resubmitting pull request to add Azure Active Directory support [\#632](https://github.com/omab/python-social-auth/pull/632) ([vinhub](https://github.com/vinhub)) - Fixes missing packages.urllib3.poolmanager \(fixes \#617\) [\#626](https://github.com/omab/python-social-auth/pull/626) ([marekjalovec](https://github.com/marekjalovec)) - fix Fitbit OAuth 1 authorization URL [\#625](https://github.com/omab/python-social-auth/pull/625) ([blurrcat](https://github.com/blurrcat)) - add weixin backends [\#621](https://github.com/omab/python-social-auth/pull/621) ([duoduo369](https://github.com/duoduo369)) - Add a DigitalOcean backend. [\#619](https://github.com/omab/python-social-auth/pull/619) ([andrewsomething](https://github.com/andrewsomething)) ## [v0.2.9](https://github.com/omab/python-social-auth/tree/v0.2.9) (2015-05-07) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.8...v0.2.9) ## [v0.2.8](https://github.com/omab/python-social-auth/tree/v0.2.8) (2015-05-07) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.7...v0.2.8) **Closed issues:** - Can't get a Google OAuth2 refresh\_token [\#607](https://github.com/omab/python-social-auth/issues/607) - Get the current logged user in the template [\#605](https://github.com/omab/python-social-auth/issues/605) - Two diferent user profiles [\#604](https://github.com/omab/python-social-auth/issues/604) - Login with Amazon TLS requests [\#603](https://github.com/omab/python-social-auth/issues/603) - Release apps.py for apps [\#601](https://github.com/omab/python-social-auth/issues/601) - migrations [\#600](https://github.com/omab/python-social-auth/issues/600) - Authentication failed: Can't connect to HTTPS URL because the SSL module is not available. [\#598](https://github.com/omab/python-social-auth/issues/598) - ConnectionError at /complete/steam You have not defined a default connection [\#597](https://github.com/omab/python-social-auth/issues/597) - uncompleted extra\_data for access\_token, code, and expires in Google+ [\#596](https://github.com/omab/python-social-auth/issues/596) - Token error: Missing unauthorized token [\#589](https://github.com/omab/python-social-auth/issues/589) - Email validation needs an email parameter \(docs\) [\#577](https://github.com/omab/python-social-auth/issues/577) - Login pipeline trying to create new user when user exists [\#562](https://github.com/omab/python-social-auth/issues/562) **Merged pull requests:** - Just add Moves App to the list of providers on README [\#606](https://github.com/omab/python-social-auth/pull/606) ([avibrazil](https://github.com/avibrazil)) - ChangeTip Backend [\#599](https://github.com/omab/python-social-auth/pull/599) ([gorillamania](https://github.com/gorillamania)) ## [v0.2.7](https://github.com/omab/python-social-auth/tree/v0.2.7) (2015-04-19) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.6...v0.2.7) **Closed issues:** - CLEAN\_USERNAME\_REGEX error [\#594](https://github.com/omab/python-social-auth/issues/594) - JSONDecodeError at /complete/facebook [\#592](https://github.com/omab/python-social-auth/issues/592) **Merged pull requests:** - Fix the final\_username may be empty and will skip the loop. [\#595](https://github.com/omab/python-social-auth/pull/595) ([littlezz](https://github.com/littlezz)) - Alter email max length for Django app [\#593](https://github.com/omab/python-social-auth/pull/593) ([JonesChi](https://github.com/JonesChi)) - Append trailing slash in Django [\#591](https://github.com/omab/python-social-auth/pull/591) ([chripede](https://github.com/chripede)) ## [v0.2.6](https://github.com/omab/python-social-auth/tree/v0.2.6) (2015-04-14) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.5...v0.2.6) **Closed issues:** - pypi package version 0.2.5 is missing requirements.txt from tests [\#590](https://github.com/omab/python-social-auth/issues/590) - TypeError: object of type 'map' has no len\(\) [\#588](https://github.com/omab/python-social-auth/issues/588) - please support weixin auth [\#481](https://github.com/omab/python-social-auth/issues/481) - How to take the user's address on facebook? This has already been implemented? [\#470](https://github.com/omab/python-social-auth/issues/470) - django social auth get wrong access\_token from google oauth2 [\#467](https://github.com/omab/python-social-auth/issues/467) - Reddit OAuth2 401 Client Error Unauthorized [\#440](https://github.com/omab/python-social-auth/issues/440) - twitter login: 401 Client Error: Authorization Required [\#400](https://github.com/omab/python-social-auth/issues/400) - remove incomplete partial pipeline data from session [\#325](https://github.com/omab/python-social-auth/issues/325) ## [v0.2.5](https://github.com/omab/python-social-auth/tree/v0.2.5) (2015-04-13) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.4...v0.2.5) **Closed issues:** - Setting user.is\_active to false at end of pipeline logs out user [\#586](https://github.com/omab/python-social-auth/issues/586) ## [v0.2.4](https://github.com/omab/python-social-auth/tree/v0.2.4) (2015-04-12) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.3...v0.2.4) **Closed issues:** - djangopackages.com still referes to the old project \(django-social-auth\) [\#585](https://github.com/omab/python-social-auth/issues/585) - warnings in Django 1.8 [\#584](https://github.com/omab/python-social-auth/issues/584) - Problems with upgrading Django Packages to python-social-auth [\#582](https://github.com/omab/python-social-auth/issues/582) - Django 1.9 Warnings [\#581](https://github.com/omab/python-social-auth/issues/581) - django 1.8, ImportError: No module named social\_auth.context\_processors [\#579](https://github.com/omab/python-social-auth/issues/579) - sdist tarball is missing some files and dirs [\#578](https://github.com/omab/python-social-auth/issues/578) - Using twitter backend with mongoengine [\#576](https://github.com/omab/python-social-auth/issues/576) - Issues while using Custom User model [\#575](https://github.com/omab/python-social-auth/issues/575) - Throw a more helpful exception when oauth\_consumer\_key is missing for OAuth1 [\#574](https://github.com/omab/python-social-auth/issues/574) - sqlalchemy\_orm: ImportError: No module named transaction [\#572](https://github.com/omab/python-social-auth/issues/572) - New version ? [\#571](https://github.com/omab/python-social-auth/issues/571) - logout without disconnect [\#568](https://github.com/omab/python-social-auth/issues/568) - SSL issue with google oauth2 [\#566](https://github.com/omab/python-social-auth/issues/566) - next parameter containing get parameters [\#565](https://github.com/omab/python-social-auth/issues/565) - get\(\) returned more than one UserSocialAuth -- it returned 2! [\#553](https://github.com/omab/python-social-auth/issues/553) - RemovedInDjango19Warning [\#551](https://github.com/omab/python-social-auth/issues/551) - Development/debug option to stub backend while developing [\#546](https://github.com/omab/python-social-auth/issues/546) - weibo access\_token ajax auth fail [\#532](https://github.com/omab/python-social-auth/issues/532) - Change PyJWT dependency version in setup.py from PyJWT==0.4.1 to PyJWT\>=0.4.1 [\#531](https://github.com/omab/python-social-auth/issues/531) - Behance authentication [\#530](https://github.com/omab/python-social-auth/issues/530) - upstream sent too big header while reading response header from upstream [\#527](https://github.com/omab/python-social-auth/issues/527) - Fails to work with Django 1.8 [\#526](https://github.com/omab/python-social-auth/issues/526) - AttributeError in VKOAuth2 [\#525](https://github.com/omab/python-social-auth/issues/525) - Login user with Email address instead of Username [\#513](https://github.com/omab/python-social-auth/issues/513) - Actually Log Exceptions in SocialAuthExceptionMiddleware [\#507](https://github.com/omab/python-social-auth/issues/507) - Don't require trailing slashes [\#505](https://github.com/omab/python-social-auth/issues/505) - django example [\#504](https://github.com/omab/python-social-auth/issues/504) - complete/mendeley-oauth2 not successful [\#501](https://github.com/omab/python-social-auth/issues/501) - Unable to refresh google oauth2 token after update python social auth to 0.2.1 [\#485](https://github.com/omab/python-social-auth/issues/485) - revoke\_token\_params & revoke\_token\_headers are missing for GooglePlusAuth [\#484](https://github.com/omab/python-social-auth/issues/484) - Microsoft Live Oauth2 Error [\#483](https://github.com/omab/python-social-auth/issues/483) - Support Facebook Graph API 2.2 [\#480](https://github.com/omab/python-social-auth/issues/480) - Spotify setting names are incorrect. [\#475](https://github.com/omab/python-social-auth/issues/475) - Django adds migration [\#474](https://github.com/omab/python-social-auth/issues/474) - SOCIAL\_AUTH\_LINKEDIN\_FIELD\_OAUTH2\_SELECTORS Not being used to populate user creation backend [\#466](https://github.com/omab/python-social-auth/issues/466) - Yahoo OAuth 2? [\#463](https://github.com/omab/python-social-auth/issues/463) - Docs for SOCIAL\_AUTH\_PROTECTED\_USER\_FIELDS misleading [\#459](https://github.com/omab/python-social-auth/issues/459) - Gracefully handle AuthExceptions [\#458](https://github.com/omab/python-social-auth/issues/458) - why context processor replace hyphen by underscore in google-oauth2 ? [\#457](https://github.com/omab/python-social-auth/issues/457) - On linkedin,github login: AttributeError at http://llovebaimuda.herokuapp.com:8000/complete/github/ 'GithubBackend' object has no attribute 'auth\_allowed' [\#442](https://github.com/omab/python-social-auth/issues/442) - GET /disconnect/\/ HTTP/1.0" 405 [\#438](https://github.com/omab/python-social-auth/issues/438) - Facebook api change [\#424](https://github.com/omab/python-social-auth/issues/424) - Import error: no module named google\_auth [\#423](https://github.com/omab/python-social-auth/issues/423) - Django: Google+ disconnect does not actually disconnect [\#394](https://github.com/omab/python-social-auth/issues/394) - How to save user to db without 'request' in register\_by\_access\_token\(request, backend\) function? [\#393](https://github.com/omab/python-social-auth/issues/393) - Support Paste style configuration [\#392](https://github.com/omab/python-social-auth/issues/392) - Google OAuth2 gives 400 error, FB 500 error [\#364](https://github.com/omab/python-social-auth/issues/364) - Django - Google Authentication - Create Account [\#362](https://github.com/omab/python-social-auth/issues/362) - Make Migrations Backward-Compatible with South [\#353](https://github.com/omab/python-social-auth/issues/353) - Github access\_token never stored [\#344](https://github.com/omab/python-social-auth/issues/344) - How to extends django orm mixins [\#343](https://github.com/omab/python-social-auth/issues/343) - a few issues [\#333](https://github.com/omab/python-social-auth/issues/333) - south migration for django app? [\#331](https://github.com/omab/python-social-auth/issues/331) - Cannot log out from GooglePlus Auth. Homepage keeps calling its GooglePlus callback [\#316](https://github.com/omab/python-social-auth/issues/316) - change log [\#313](https://github.com/omab/python-social-auth/issues/313) - Return 503 instead of raise 500 error when auth provider not accessible [\#304](https://github.com/omab/python-social-auth/issues/304) - Facebook SOCIAL\_AUTH\_FACEBOOK\_SCOPE not working as expected [\#294](https://github.com/omab/python-social-auth/issues/294) - Add Django 1.7 migrations [\#270](https://github.com/omab/python-social-auth/issues/270) - IntegrityError at /social/complete/facebook/ duplicate key value violates unique constraint "userprofile\_user\_email\_key" [\#208](https://github.com/omab/python-social-auth/issues/208) **Merged pull requests:** - Build a wheel, and upload with twine [\#583](https://github.com/omab/python-social-auth/pull/583) ([mattrobenolt](https://github.com/mattrobenolt)) - Allow inactive users to login [\#580](https://github.com/omab/python-social-auth/pull/580) ([LucasRoesler](https://github.com/LucasRoesler)) - Update LICENSE [\#573](https://github.com/omab/python-social-auth/pull/573) ([yasoob](https://github.com/yasoob)) ## [v0.2.3](https://github.com/omab/python-social-auth/tree/v0.2.3) (2015-03-31) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.2...v0.2.3) **Closed issues:** - get\_username as a classmethod collides with the standard django implementation and other packages [\#564](https://github.com/omab/python-social-auth/issues/564) - Make it easier to disable social\_details pipeline step [\#555](https://github.com/omab/python-social-auth/issues/555) - Not compatible with requests-oauthlib 0.3.0 [\#545](https://github.com/omab/python-social-auth/issues/545) - how to remove "redirect\_state" params? \( Kakao OAuth2 Error \) [\#538](https://github.com/omab/python-social-auth/issues/538) - async interface for models in tornado [\#535](https://github.com/omab/python-social-auth/issues/535) - `social.strategies.django\_strategy` work with django 1.7.4 "This QueryDict instance is immutable" [\#528](https://github.com/omab/python-social-auth/issues/528) - Update PyPI [\#523](https://github.com/omab/python-social-auth/issues/523) - Missing migration [\#516](https://github.com/omab/python-social-auth/issues/516) - Not getting correct GoogleOath2 details when signing up by OAuth access token [\#499](https://github.com/omab/python-social-auth/issues/499) - Jawbone backend problem, AuthCanceled exception. [\#497](https://github.com/omab/python-social-auth/issues/497) - StravaOAuth - Strava authentication backend not working. [\#455](https://github.com/omab/python-social-auth/issues/455) **Merged pull requests:** - Added NaszaKlasa OAuth2 support [\#570](https://github.com/omab/python-social-auth/pull/570) ([hoffmannkrzysztof](https://github.com/hoffmannkrzysztof)) - Add revoke token ability to strava [\#569](https://github.com/omab/python-social-auth/pull/569) ([buddylindsey](https://github.com/buddylindsey)) - set redirect\_state to false for live oauth2 [\#563](https://github.com/omab/python-social-auth/pull/563) ([wj1918](https://github.com/wj1918)) - Khan academy backend user\_id is required to use any further requests [\#561](https://github.com/omab/python-social-auth/pull/561) ([aniav](https://github.com/aniav)) - Rednose and config [\#560](https://github.com/omab/python-social-auth/pull/560) ([jeromelefeuvre](https://github.com/jeromelefeuvre)) - Add missing migration for Django app [\#558](https://github.com/omab/python-social-auth/pull/558) ([andreipetre](https://github.com/andreipetre)) - Require PyJWT\>=1.0.0,\<2.0.0 [\#557](https://github.com/omab/python-social-auth/pull/557) ([jpadilla](https://github.com/jpadilla)) - Start pipeline with default details arg [\#556](https://github.com/omab/python-social-auth/pull/556) ([johtso](https://github.com/johtso)) - Add `python\_chameleon` to setup [\#554](https://github.com/omab/python-social-auth/pull/554) ([jeromelefeuvre](https://github.com/jeromelefeuvre)) - update for django 1.9 [\#550](https://github.com/omab/python-social-auth/pull/550) ([DanielJDufour](https://github.com/DanielJDufour)) - Added support for Vend [\#549](https://github.com/omab/python-social-auth/pull/549) ([matthowland](https://github.com/matthowland)) - Increase min request-oauthlib version to 0.3.1 [\#548](https://github.com/omab/python-social-auth/pull/548) ([johtso](https://github.com/johtso)) - Add wunderlist backend to the list [\#547](https://github.com/omab/python-social-auth/pull/547) ([bogdal](https://github.com/bogdal)) - Typo in index.html [\#544](https://github.com/omab/python-social-auth/pull/544) ([flesser](https://github.com/flesser)) - Wunderlist oauth2 backend [\#543](https://github.com/omab/python-social-auth/pull/543) ([bogdal](https://github.com/bogdal)) - Add backend for EVE Online Single Sign-On \(OAuth2\) [\#541](https://github.com/omab/python-social-auth/pull/541) ([flesser](https://github.com/flesser)) - Add extra info on Google+ Sign-In doc [\#540](https://github.com/omab/python-social-auth/pull/540) ([Menda](https://github.com/Menda)) - fix issue \#538 : disable redirect\_state on KakaoOAuth2 [\#539](https://github.com/omab/python-social-auth/pull/539) ([dobestan](https://github.com/dobestan)) - Update google.rst [\#537](https://github.com/omab/python-social-auth/pull/537) ([tclancy](https://github.com/tclancy)) - Added Yahoo OAuth2 support [\#536](https://github.com/omab/python-social-auth/pull/536) ([hassek](https://github.com/hassek)) - Fix Issue \#532 [\#533](https://github.com/omab/python-social-auth/pull/533) ([littlezz](https://github.com/littlezz)) ## [v0.2.2](https://github.com/omab/python-social-auth/tree/v0.2.2) (2015-02-23) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.1...v0.2.2) **Closed issues:** - Problem with REQUEST in Django 1.7 [\#508](https://github.com/omab/python-social-auth/issues/508) - Unique constraint on nonce missing [\#490](https://github.com/omab/python-social-auth/issues/490) - AuthStateMissing [\#462](https://github.com/omab/python-social-auth/issues/462) - \.\_\_proxy\_\_ object at 0x7feb8a56f5f8\> is not JSON serializable [\#460](https://github.com/omab/python-social-auth/issues/460) - Cannot import name migrations [\#456](https://github.com/omab/python-social-auth/issues/456) - Bugs in tornado\_strategy.py [\#445](https://github.com/omab/python-social-auth/issues/445) - Cannot login with social account anymore after migration from DSA \(but association is OK\) [\#444](https://github.com/omab/python-social-auth/issues/444) - Mode debug [\#443](https://github.com/omab/python-social-auth/issues/443) - On linkedin,github login: AttributeError at http://llovebaimuda.herokuapp.com:8000/complete/github/ 'GithubBackend' object has no attribute 'auth\_allowed' [\#441](https://github.com/omab/python-social-auth/issues/441) - Can not migrate database with django 1.7 [\#439](https://github.com/omab/python-social-auth/issues/439) - AuthAlreadyAssociated at /complete/google-oauth2/ [\#437](https://github.com/omab/python-social-auth/issues/437) - Python social auth redirect to LOGIN\_ERROR [\#435](https://github.com/omab/python-social-auth/issues/435) - Can't register user when using email as username [\#434](https://github.com/omab/python-social-auth/issues/434) - Problems connecting Google OAUTH2 [\#433](https://github.com/omab/python-social-auth/issues/433) - Can't get refresh\_token from google-oauth2 response [\#431](https://github.com/omab/python-social-auth/issues/431) - UserMixin.tokens naming [\#430](https://github.com/omab/python-social-auth/issues/430) - Django 1.7 Type Object 'Migration' has no Attribute 'models' [\#427](https://github.com/omab/python-social-auth/issues/427) - Django 1.7 warning - You have unapplied migrations [\#426](https://github.com/omab/python-social-auth/issues/426) - Should the Django app's auth view be cacheable? [\#425](https://github.com/omab/python-social-auth/issues/425) - changelog 0.2.1 only vs. releases on github 0.2.0 only [\#421](https://github.com/omab/python-social-auth/issues/421) - Don't know how to redirect to right redirect\_uri. Use gunicorn + nginx + django1.7 [\#420](https://github.com/omab/python-social-auth/issues/420) - Request object not passed in pipeline [\#419](https://github.com/omab/python-social-auth/issues/419) - How to save the user data.? [\#418](https://github.com/omab/python-social-auth/issues/418) - GitHub doesn't select Primary Email [\#413](https://github.com/omab/python-social-auth/issues/413) - Unwanted and forced use of Google+ API for signin [\#406](https://github.com/omab/python-social-auth/issues/406) - Renaming social url namespace [\#399](https://github.com/omab/python-social-auth/issues/399) - How to overwrite redirect\_uri? [\#383](https://github.com/omab/python-social-auth/issues/383) - GoogleOauth2 hangs mod\_wsgi after multiple logins. [\#377](https://github.com/omab/python-social-auth/issues/377) - Facebook /login/facebook-app/ printing None [\#376](https://github.com/omab/python-social-auth/issues/376) - How to test a custom python-social-auth pipeline? [\#352](https://github.com/omab/python-social-auth/issues/352) - Cannot figure out how to associate multiple auth providers [\#340](https://github.com/omab/python-social-auth/issues/340) - No user param in partial pipeline function [\#323](https://github.com/omab/python-social-auth/issues/323) **Merged pull requests:** - Fix example of pyramid [\#529](https://github.com/omab/python-social-auth/pull/529) ([narusemotoki](https://github.com/narusemotoki)) - fix python3 handling of openid backend on sqlalchemy storage [\#524](https://github.com/omab/python-social-auth/pull/524) ([ghost](https://github.com/ghost)) - Don't use "import" in example method paths docs to avoid confusion [\#521](https://github.com/omab/python-social-auth/pull/521) ([lamby](https://github.com/lamby)) - Add dribbble backend. [\#519](https://github.com/omab/python-social-auth/pull/519) ([tell-k](https://github.com/tell-k)) - Fixed issue: GET dictionary is immutable. [\#518](https://github.com/omab/python-social-auth/pull/518) ([baroale](https://github.com/baroale)) - Include username in Reddit extra\_data [\#517](https://github.com/omab/python-social-auth/pull/517) ([chris-martin](https://github.com/chris-martin)) - \[facebook-oauth2\] Verifying Graph API Calls with appsecret\_proof [\#515](https://github.com/omab/python-social-auth/pull/515) ([eagafonov](https://github.com/eagafonov)) - Add Zotero Backend [\#514](https://github.com/omab/python-social-auth/pull/514) ([cdeblois](https://github.com/cdeblois)) - add qiita backend [\#512](https://github.com/omab/python-social-auth/pull/512) ([tell-k](https://github.com/tell-k)) - Fix: Issue \#508 [\#511](https://github.com/omab/python-social-auth/pull/511) ([baroale](https://github.com/baroale)) - Fix Google documentation [\#510](https://github.com/omab/python-social-auth/pull/510) ([Menda](https://github.com/Menda)) - Updated PyJWT Dependency [\#509](https://github.com/omab/python-social-auth/pull/509) ([clintonb](https://github.com/clintonb)) - Ensure email is not None [\#503](https://github.com/omab/python-social-auth/pull/503) ([ianw](https://github.com/ianw)) - Pull Request for \#501 [\#502](https://github.com/omab/python-social-auth/pull/502) ([cdeblois](https://github.com/cdeblois)) - Add support for Launchpad OpenId [\#500](https://github.com/omab/python-social-auth/pull/500) ([ianw](https://github.com/ianw)) - Jawbone authentification fix [\#498](https://github.com/omab/python-social-auth/pull/498) ([rivf](https://github.com/rivf)) - Coursera backend [\#496](https://github.com/omab/python-social-auth/pull/496) ([adambabik](https://github.com/adambabik)) - Added nonce unique constraint [\#491](https://github.com/omab/python-social-auth/pull/491) ([candlejack297](https://github.com/candlejack297)) - Store Spotify's refresh\_token. [\#482](https://github.com/omab/python-social-auth/pull/482) ([ctbarna](https://github.com/ctbarna)) - Slack improvements [\#479](https://github.com/omab/python-social-auth/pull/479) ([gorillamania](https://github.com/gorillamania)) - Fixed extra\_data field in django 1.7 initial migration [\#476](https://github.com/omab/python-social-auth/pull/476) ([bendavis78](https://github.com/bendavis78)) - YahooOAuth failed to get primary email if multiple email found in the profile. [\#473](https://github.com/omab/python-social-auth/pull/473) ([wj1918](https://github.com/wj1918)) - Update base.py [\#472](https://github.com/omab/python-social-auth/pull/472) ([travoltino](https://github.com/travoltino)) - Slack backend [\#471](https://github.com/omab/python-social-auth/pull/471) ([gorillamania](https://github.com/gorillamania)) - Update GitHub documentation [\#469](https://github.com/omab/python-social-auth/pull/469) ([alexmuller](https://github.com/alexmuller)) - Fix \#460: Call force\_text on \_URL settings to support reverse\_lazy with default session serializer [\#468](https://github.com/omab/python-social-auth/pull/468) ([frankier](https://github.com/frankier)) - Update Django instructions to fix South migrations [\#454](https://github.com/omab/python-social-auth/pull/454) ([drpancake](https://github.com/drpancake)) - Added backend for professionali.ru [\#452](https://github.com/omab/python-social-auth/pull/452) ([kblw](https://github.com/kblw)) - Removed Orkut backend [\#450](https://github.com/omab/python-social-auth/pull/450) ([lukasklein](https://github.com/lukasklein)) - Allow the pipeline to change the redirect url. [\#449](https://github.com/omab/python-social-auth/pull/449) ([tim-schilling](https://github.com/tim-schilling)) - Added support for Django's User.EMAIL\_FIELD. [\#447](https://github.com/omab/python-social-auth/pull/447) ([SeanHayes](https://github.com/SeanHayes)) - Khan Academy backend [\#446](https://github.com/omab/python-social-auth/pull/446) ([aniav](https://github.com/aniav)) - Fix typo for AUTH\_USER\_MODEL [\#432](https://github.com/omab/python-social-auth/pull/432) ([jlynn](https://github.com/jlynn)) - Update base.py , removing unncessary code after refactoring [\#429](https://github.com/omab/python-social-auth/pull/429) ([aparij](https://github.com/aparij)) - use correct tense for `to meet' [\#428](https://github.com/omab/python-social-auth/pull/428) ([mgalgs](https://github.com/mgalgs)) - Fix custom user model migrations for Django 1.7 [\#422](https://github.com/omab/python-social-auth/pull/422) ([jlynn](https://github.com/jlynn)) - Fix migration issue on python 3 [\#417](https://github.com/omab/python-social-auth/pull/417) ([EnTeQuAk](https://github.com/EnTeQuAk)) - Fix does not match the number of arguments \(for vk and ok backend\) [\#415](https://github.com/omab/python-social-auth/pull/415) ([silentsokolov](https://github.com/silentsokolov)) - Salesforce OAuth2 support [\#412](https://github.com/omab/python-social-auth/pull/412) ([postrational](https://github.com/postrational)) - Thedrow patch 1 [\#411](https://github.com/omab/python-social-auth/pull/411) ([omab](https://github.com/omab)) - Added Python 3.4 and PyPy to the build matrix. [\#410](https://github.com/omab/python-social-auth/pull/410) ([thedrow](https://github.com/thedrow)) - Added Django 1.7 App Config [\#409](https://github.com/omab/python-social-auth/pull/409) ([micahhausler](https://github.com/micahhausler)) - Django admin enhancements [\#408](https://github.com/omab/python-social-auth/pull/408) ([micahhausler](https://github.com/micahhausler)) - Use new GoogleOAuth2 Spec [\#407](https://github.com/omab/python-social-auth/pull/407) ([jaitaiwan](https://github.com/jaitaiwan)) - \[flask\_example\_app\]: Incorrect import path for db model [\#405](https://github.com/omab/python-social-auth/pull/405) ([labeneator](https://github.com/labeneator)) - Add Kakao link and detailed address for description. [\#403](https://github.com/omab/python-social-auth/pull/403) ([jeyraof](https://github.com/jeyraof)) - Added some legal stuff [\#402](https://github.com/omab/python-social-auth/pull/402) ([dzerrenner](https://github.com/dzerrenner)) - Recreate migration with Django 1.7 final and re-PEP8. [\#401](https://github.com/omab/python-social-auth/pull/401) ([akx](https://github.com/akx)) - master add SCOPE\_SEPARATOR to DisqusOAuth2 [\#398](https://github.com/omab/python-social-auth/pull/398) ([vero4karu](https://github.com/vero4karu)) - added a backend for Battle.net Oauth2 auth [\#397](https://github.com/omab/python-social-auth/pull/397) ([dzerrenner](https://github.com/dzerrenner)) - Update documentation with info on upgrading from 0.1-0.2 with migrations [\#395](https://github.com/omab/python-social-auth/pull/395) ([timsavage](https://github.com/timsavage)) - Allow more Trello settings [\#389](https://github.com/omab/python-social-auth/pull/389) ([sk7](https://github.com/sk7)) - Updated to use latest api wrapper [\#386](https://github.com/omab/python-social-auth/pull/386) ([dhendo](https://github.com/dhendo)) - updated the docs to add migrations for 1.7 while updated a constant so the warning message does not appear when running command line [\#382](https://github.com/omab/python-social-auth/pull/382) ([masterfung](https://github.com/masterfung)) - Jawbone needs params instead of data as requests [\#380](https://github.com/omab/python-social-auth/pull/380) ([amolkher](https://github.com/amolkher)) - Don't overwrite clean\_kwargs with kwargs [\#332](https://github.com/omab/python-social-auth/pull/332) ([cambridgemike](https://github.com/cambridgemike)) - Reinstated get\_user\_id override [\#314](https://github.com/omab/python-social-auth/pull/314) ([dhendo](https://github.com/dhendo)) - Update django\_orm.py [\#312](https://github.com/omab/python-social-auth/pull/312) ([synotna](https://github.com/synotna)) ## [v0.2.1](https://github.com/omab/python-social-auth/tree/v0.2.1) (2014-09-11) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.2.0...v0.2.1) ## [v0.2.0](https://github.com/omab/python-social-auth/tree/v0.2.0) (2014-09-11) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.26...v0.2.0) **Closed issues:** - cannot import name strategy [\#370](https://github.com/omab/python-social-auth/issues/370) - Request object has no attribute 'backend' in SocialAuthExceptionMiddleware [\#369](https://github.com/omab/python-social-auth/issues/369) - Shopify Backend [\#368](https://github.com/omab/python-social-auth/issues/368) - State parameter incorrectly missing for some backends [\#367](https://github.com/omab/python-social-auth/issues/367) - /signup/email/ instead of /login/email/ [\#366](https://github.com/omab/python-social-auth/issues/366) - Improve pipeline documentation [\#361](https://github.com/omab/python-social-auth/issues/361) - request: opposite default behavior for SOCIAL\_AUTH\_SESSION\_EXPIRATION [\#356](https://github.com/omab/python-social-auth/issues/356) - after installing I have got an error: ImportError: No module named defaultqrcode [\#355](https://github.com/omab/python-social-auth/issues/355) - SocialAuthExceptionMiddleware raises AttributeError [\#350](https://github.com/omab/python-social-auth/issues/350) - Python 3 support [\#349](https://github.com/omab/python-social-auth/issues/349) - can I set redirect\_uri use weibo backend? [\#345](https://github.com/omab/python-social-auth/issues/345) - Toronodo-Facebook oauth [\#342](https://github.com/omab/python-social-auth/issues/342) - Security issue with Twitter backend - state parameter [\#338](https://github.com/omab/python-social-auth/issues/338) - \ [\#330](https://github.com/omab/python-social-auth/issues/330) - Github support for checking if a user is part of a team [\#329](https://github.com/omab/python-social-auth/issues/329) - Github OAuth2 backend fails with 404 when retrieving access token [\#327](https://github.com/omab/python-social-auth/issues/327) - django admin User social auth search broken [\#322](https://github.com/omab/python-social-auth/issues/322) - Script to migrate django sessions to python social auth [\#320](https://github.com/omab/python-social-auth/issues/320) - Error with facebook login [\#315](https://github.com/omab/python-social-auth/issues/315) - NotImplementedError [\#310](https://github.com/omab/python-social-auth/issues/310) - No module named 'social\_auth' on social/utils.py [\#306](https://github.com/omab/python-social-auth/issues/306) - What is the correct way to use get tokens with GooglePlus? [\#305](https://github.com/omab/python-social-auth/issues/305) - custom LOGIN\_REDIRECT\_URL per backend [\#301](https://github.com/omab/python-social-auth/issues/301) - Instagram has changed user data format [\#296](https://github.com/omab/python-social-auth/issues/296) - Getting "cannot import name psa" error [\#295](https://github.com/omab/python-social-auth/issues/295) - Broken partial auth with Django and 0.1.24 [\#291](https://github.com/omab/python-social-auth/issues/291) - Google+ Sign-in problem [\#285](https://github.com/omab/python-social-auth/issues/285) - AuthStateMissing: Session value state missing [\#279](https://github.com/omab/python-social-auth/issues/279) - Always returns me detail: "Invalid token" [\#268](https://github.com/omab/python-social-auth/issues/268) - On facebook login: AttributeError at /complete/facebook/ 'NoneType' object has no attribute 'expiration\_datetime' [\#190](https://github.com/omab/python-social-auth/issues/190) **Merged pull requests:** - Adds backend for MineID.org [\#379](https://github.com/omab/python-social-auth/pull/379) ([caioariede](https://github.com/caioariede)) - Fix typo [\#372](https://github.com/omab/python-social-auth/pull/372) ([gipi](https://github.com/gipi)) - Updated OpenId Connect Test Mixin [\#371](https://github.com/omab/python-social-auth/pull/371) ([clintonb](https://github.com/clintonb)) - Small grammatical edit [\#363](https://github.com/omab/python-social-auth/pull/363) ([x0xMaximus](https://github.com/x0xMaximus)) - Fix repository links in thanks document. [\#359](https://github.com/omab/python-social-auth/pull/359) ([martey](https://github.com/martey)) - changed default behavior of SESSION\_EXPIRATION setting [\#358](https://github.com/omab/python-social-auth/pull/358) ([gameguy43](https://github.com/gameguy43)) - added goclio oauth2 backend [\#357](https://github.com/omab/python-social-auth/pull/357) ([rosscdh](https://github.com/rosscdh)) - Add pushbullet backends [\#351](https://github.com/omab/python-social-auth/pull/351) ([ralmn](https://github.com/ralmn)) - Added Open ID Connect base backend [\#348](https://github.com/omab/python-social-auth/pull/348) ([clintonb](https://github.com/clintonb)) - numeric index for format [\#347](https://github.com/omab/python-social-auth/pull/347) ([jprobst21](https://github.com/jprobst21)) - Update vk.rst [\#341](https://github.com/omab/python-social-auth/pull/341) ([darthwade](https://github.com/darthwade)) - Django \<1.7 Migration Support [\#339](https://github.com/omab/python-social-auth/pull/339) ([mhluongo](https://github.com/mhluongo)) - Strava name population fixes [\#336](https://github.com/omab/python-social-auth/pull/336) ([lamby](https://github.com/lamby)) - Correct Strava scoping/permissions example. [\#335](https://github.com/omab/python-social-auth/pull/335) ([lamby](https://github.com/lamby)) - Clean up language in social/tests/README.rst [\#334](https://github.com/omab/python-social-auth/pull/334) ([chris-martin](https://github.com/chris-martin)) - Fixed \#327 -- Changed access token method on backend. [\#328](https://github.com/omab/python-social-auth/pull/328) ([slurms](https://github.com/slurms)) - Minor doc updates [\#326](https://github.com/omab/python-social-auth/pull/326) ([seizethedave](https://github.com/seizethedave)) - fix for AssertionError in pyramid [\#319](https://github.com/omab/python-social-auth/pull/319) ([marinewater](https://github.com/marinewater)) - Added Django 1.7 migrations [\#318](https://github.com/omab/python-social-auth/pull/318) ([ondrowan](https://github.com/ondrowan)) - reddit sometimes responds with "429 Too Many Requests" seemingly randomly [\#317](https://github.com/omab/python-social-auth/pull/317) ([davidhubbard](https://github.com/davidhubbard)) - Update link to Django example in documentation. [\#311](https://github.com/omab/python-social-auth/pull/311) ([martey](https://github.com/martey)) - Add note about access\_type in docs [\#308](https://github.com/omab/python-social-auth/pull/308) ([romanlevin](https://github.com/romanlevin)) - The Moves app backend [\#307](https://github.com/omab/python-social-auth/pull/307) ([avibrazil](https://github.com/avibrazil)) - QQ backend [\#302](https://github.com/omab/python-social-auth/pull/302) ([omab](https://github.com/omab)) - \[documentation\] text should not go into code block [\#299](https://github.com/omab/python-social-auth/pull/299) ([GabLeRoux](https://github.com/GabLeRoux)) - Vkotnakte [\#298](https://github.com/omab/python-social-auth/pull/298) ([freydev](https://github.com/freydev)) - Update docker backend with Docker Hub endpoints [\#293](https://github.com/omab/python-social-auth/pull/293) ([jlhawn](https://github.com/jlhawn)) ## [v0.1.26](https://github.com/omab/python-social-auth/tree/v0.1.26) (2014-06-07) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.25...v0.1.26) **Closed issues:** - Google OAuth2 broken since 0.1.24 [\#292](https://github.com/omab/python-social-auth/issues/292) ## [v0.1.25](https://github.com/omab/python-social-auth/tree/v0.1.25) (2014-06-07) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.24...v0.1.25) **Closed issues:** - LinkedIn-OAuth2 refresh\_token doesn't work [\#289](https://github.com/omab/python-social-auth/issues/289) - Process exceptions even when DEBUG = True [\#287](https://github.com/omab/python-social-auth/issues/287) - python-openid does not support py3k. Alternatives? [\#282](https://github.com/omab/python-social-auth/issues/282) - Twitter OAuth using access\_token [\#272](https://github.com/omab/python-social-auth/issues/272) **Merged pull requests:** - Rdio API methods use POST [\#288](https://github.com/omab/python-social-auth/pull/288) ([dasevilla](https://github.com/dasevilla)) - Fixed Django 1.7 admin [\#286](https://github.com/omab/python-social-auth/pull/286) ([godshall](https://github.com/godshall)) - avoid updating default settings [\#281](https://github.com/omab/python-social-auth/pull/281) ([l-hedgehog](https://github.com/l-hedgehog)) ## [v0.1.24](https://github.com/omab/python-social-auth/tree/v0.1.24) (2014-05-18) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.23...v0.1.24) **Closed issues:** - Facebook2OAuth2 not setting 'client\_id' parameter when redirecting to login [\#280](https://github.com/omab/python-social-auth/issues/280) - Wrong version on pip [\#277](https://github.com/omab/python-social-auth/issues/277) - SOCIAL\_AUTH\_NEW\_USER\_REDIRECT\_URL strange behaviour [\#276](https://github.com/omab/python-social-auth/issues/276) - Feature request: Ability to encrypt access tokens [\#274](https://github.com/omab/python-social-auth/issues/274) - Google is deprecating some OAuth scopes [\#273](https://github.com/omab/python-social-auth/issues/273) - 'RegexURLResolver' object has no attribute '\_urlconf\_module' [\#269](https://github.com/omab/python-social-auth/issues/269) - I am facing a 500: Internal Server Error after clicking any link [\#266](https://github.com/omab/python-social-auth/issues/266) - EXTRA\_DATA for VK [\#263](https://github.com/omab/python-social-auth/issues/263) - Amazon Docs Out of Date? [\#260](https://github.com/omab/python-social-auth/issues/260) - Strava Integration OAuth Redirect Issue [\#259](https://github.com/omab/python-social-auth/issues/259) - Add the ability to customize AX\_SCHEMA\_ATTRS [\#258](https://github.com/omab/python-social-auth/issues/258) - g.user may be Proxy! \(flask bug\) [\#257](https://github.com/omab/python-social-auth/issues/257) - Error running syncdb with MySQL utf8mb4 charset [\#255](https://github.com/omab/python-social-auth/issues/255) - Use of Django Internal Property [\#254](https://github.com/omab/python-social-auth/issues/254) - Persona auth failing to authenticate when using a custom user model \[Django\] [\#253](https://github.com/omab/python-social-auth/issues/253) - Facebook: "Invalid App ID: None" [\#252](https://github.com/omab/python-social-auth/issues/252) - "vk-openapi" backend error [\#250](https://github.com/omab/python-social-auth/issues/250) - request hanging after social authentication [\#248](https://github.com/omab/python-social-auth/issues/248) - Unable To Redirect User After Facebook Authentication [\#247](https://github.com/omab/python-social-auth/issues/247) - social.exceptions.AuthStateMissing [\#244](https://github.com/omab/python-social-auth/issues/244) - Facebook Re-authentication [\#243](https://github.com/omab/python-social-auth/issues/243) - SOCIAL\_AUTH\_DEFAULT\_USERNAME [\#241](https://github.com/omab/python-social-auth/issues/241) - Refactor first, last and full name population [\#240](https://github.com/omab/python-social-auth/issues/240) - Authication using acees token works for facebook but not twitter and VK [\#238](https://github.com/omab/python-social-auth/issues/238) - MendeleyOAuth2 does not require REDIRECT\_STATE [\#234](https://github.com/omab/python-social-auth/issues/234) - Autnticate/Create user from acces\_token [\#233](https://github.com/omab/python-social-auth/issues/233) - Problem using @partial with GoogleOpenId in Django 1.6 and python 3.3 [\#231](https://github.com/omab/python-social-auth/issues/231) - Unicode error with UTF-8 string in next [\#229](https://github.com/omab/python-social-auth/issues/229) - Error with "Enhanced redirection security" in Microsoft account \(Live backend\). [\#218](https://github.com/omab/python-social-auth/issues/218) **Merged pull requests:** - Implementing Spotify and Beats OAuth implementations. [\#283](https://github.com/omab/python-social-auth/pull/283) ([ryankicks](https://github.com/ryankicks)) - Add MapMyFitness [\#278](https://github.com/omab/python-social-auth/pull/278) ([JasonSanford](https://github.com/JasonSanford)) - from http API to https API [\#275](https://github.com/omab/python-social-auth/pull/275) ([swmerko](https://github.com/swmerko)) - Replace references to python-oauth2 with references to requests-oauthlib [\#271](https://github.com/omab/python-social-auth/pull/271) ([malept](https://github.com/malept)) - get email on login via VK [\#267](https://github.com/omab/python-social-auth/pull/267) ([Smamaxs](https://github.com/Smamaxs)) - Change the authorization url for the xing api [\#265](https://github.com/omab/python-social-auth/pull/265) ([hujiko](https://github.com/hujiko)) - Support for Facebook Open Graph 2.0 [\#264](https://github.com/omab/python-social-auth/pull/264) ([dryan](https://github.com/dryan)) - Added LoginRadius backend. [\#262](https://github.com/omab/python-social-auth/pull/262) ([grepme](https://github.com/grepme)) - Add Kakao backend [\#261](https://github.com/omab/python-social-auth/pull/261) ([momamene](https://github.com/momamene)) - Using https as required by the API [\#256](https://github.com/omab/python-social-auth/pull/256) ([gmist](https://github.com/gmist)) - User model fields accessors clashes issue solved [\#251](https://github.com/omab/python-social-auth/pull/251) ([wumzi](https://github.com/wumzi)) - linkedin now requires redirect uris to be verified: https://developer.li... [\#246](https://github.com/omab/python-social-auth/pull/246) ([dblado](https://github.com/dblado)) - Add Twitch backend [\#245](https://github.com/omab/python-social-auth/pull/245) ([hannseman](https://github.com/hannseman)) - Handle properly refusing when entering via twitter [\#242](https://github.com/omab/python-social-auth/pull/242) ([Chern](https://github.com/Chern)) - Fix small spelling mistake. [\#239](https://github.com/omab/python-social-auth/pull/239) ([cdepillabout](https://github.com/cdepillabout)) - Add support for Vimeo OAuth 2 as part of Vimeo API v3 [\#237](https://github.com/omab/python-social-auth/pull/237) ([jjshabs](https://github.com/jjshabs)) - Update settings.rst [\#236](https://github.com/omab/python-social-auth/pull/236) ([krishangupta](https://github.com/krishangupta)) - Incorrect syntax given in the documention [\#235](https://github.com/omab/python-social-auth/pull/235) ([mdamien](https://github.com/mdamien)) - login with bitbucket account, error when any verified email is set [\#230](https://github.com/omab/python-social-auth/pull/230) ([pekoslaw](https://github.com/pekoslaw)) ## [v0.1.23](https://github.com/omab/python-social-auth/tree/v0.1.23) (2014-03-26) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.22...v0.1.23) **Closed issues:** - Handling AuthAlreadyAssociated [\#226](https://github.com/omab/python-social-auth/issues/226) - AuthFailed at /complete/dropbox-oauth2/ [\#225](https://github.com/omab/python-social-auth/issues/225) - Github says 404 when I want to use it [\#224](https://github.com/omab/python-social-auth/issues/224) - Dropbox OAuth2 backend not found [\#223](https://github.com/omab/python-social-auth/issues/223) - EmailAuth pipeline - saving password [\#222](https://github.com/omab/python-social-auth/issues/222) - SocialAuthExceptionMiddleware is not thread safe. [\#221](https://github.com/omab/python-social-auth/issues/221) - `AuthStateMissing` and `HTTPError` being raised [\#220](https://github.com/omab/python-social-auth/issues/220) - Saving to session and access after pipeline [\#219](https://github.com/omab/python-social-auth/issues/219) - Migrating from Django social auth [\#214](https://github.com/omab/python-social-auth/issues/214) - No module named apps.django\_app.default.models in custom pipeline [\#213](https://github.com/omab/python-social-auth/issues/213) - complete login with facebook app [\#212](https://github.com/omab/python-social-auth/issues/212) - Use Django messages in SocialAuthExceptionMiddleware even for anonymous users [\#210](https://github.com/omab/python-social-auth/issues/210) - HTTPError at /complete/facebook/ when trying to connect to Facebook. [\#207](https://github.com/omab/python-social-auth/issues/207) - Steam backend not using stateless mode [\#200](https://github.com/omab/python-social-auth/issues/200) - Got UnicodeEncodeError when redirection parameter &next=/apage/contains/inτερnαtιοnal/characters/ [\#191](https://github.com/omab/python-social-auth/issues/191) - PIP install to virtual environment fails [\#177](https://github.com/omab/python-social-auth/issues/177) - AUTHENTICATION\_BACKENDS [\#131](https://github.com/omab/python-social-auth/issues/131) **Merged pull requests:** - Added backend for Last.Fm. [\#232](https://github.com/omab/python-social-auth/pull/232) ([eriklavander](https://github.com/eriklavander)) - Added Docker.io backend [\#228](https://github.com/omab/python-social-auth/pull/228) ([fermayo](https://github.com/fermayo)) - OpenStreetMap: no img element if user has no avatar [\#227](https://github.com/omab/python-social-auth/pull/227) ([yohanboniface](https://github.com/yohanboniface)) - Added support for strava [\#217](https://github.com/omab/python-social-auth/pull/217) ([abunsen](https://github.com/abunsen)) - Removes flask dependency from webpy\_app [\#216](https://github.com/omab/python-social-auth/pull/216) ([w0rm](https://github.com/w0rm)) - Added backend for Ubuntu \(One\). [\#215](https://github.com/omab/python-social-auth/pull/215) ([schwuk](https://github.com/schwuk)) - Fixed Django \< 1.4 support in context processors. [\#211](https://github.com/omab/python-social-auth/pull/211) ([bmispelon](https://github.com/bmispelon)) - Add some missing test dependencies for `social.apps.django\_app.default.tests` [\#209](https://github.com/omab/python-social-auth/pull/209) ([pzrq](https://github.com/pzrq)) ## [v0.1.22](https://github.com/omab/python-social-auth/tree/v0.1.22) (2014-03-01) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.21...v0.1.22) **Closed issues:** - Email confirmation is broken for SQLAlchemy storage and webpy\_app [\#204](https://github.com/omab/python-social-auth/issues/204) - Associate by mail doesn't return is\_new flag [\#201](https://github.com/omab/python-social-auth/issues/201) - Coinbase backend defaults to 'balance' but the complete calls user\_data with looks for /users api path [\#199](https://github.com/omab/python-social-auth/issues/199) - Partial pipeline doesn't restore user model [\#198](https://github.com/omab/python-social-auth/issues/198) - mongoengine should support USERNAME\_FIELD ? [\#197](https://github.com/omab/python-social-auth/issues/197) - Action of do\_complete is not managing exceptions thrown during strategy.complete [\#196](https://github.com/omab/python-social-auth/issues/196) - Using Django-facebook side by side [\#195](https://github.com/omab/python-social-auth/issues/195) - Saving user as inactive in a pipeline, causes redirect to login error [\#194](https://github.com/omab/python-social-auth/issues/194) - case-sensetive ?next= parameter dont work [\#193](https://github.com/omab/python-social-auth/issues/193) - TypeError: can only concatenate list \(not "str"\) to list [\#186](https://github.com/omab/python-social-auth/issues/186) - Post-authentication redirects: are they still supported? [\#182](https://github.com/omab/python-social-auth/issues/182) - LinkedIn HTTPError: 401 Client Error: Unauthorized [\#181](https://github.com/omab/python-social-auth/issues/181) - How to register user by access\_token [\#180](https://github.com/omab/python-social-auth/issues/180) - Session value state missing [\#166](https://github.com/omab/python-social-auth/issues/166) - Unavailable facebook raises unexpected ConnectionError [\#155](https://github.com/omab/python-social-auth/issues/155) - Exceptions not noted in logs [\#154](https://github.com/omab/python-social-auth/issues/154) - Internal Server Error: /complete/facebook/ -\> raise KeyError [\#153](https://github.com/omab/python-social-auth/issues/153) - Migrating server [\#128](https://github.com/omab/python-social-auth/issues/128) - django example: trying to get only the email auth work for now... [\#118](https://github.com/omab/python-social-auth/issues/118) - Can we choose to set the login url escaped ? [\#115](https://github.com/omab/python-social-auth/issues/115) - Incorporating rauth? [\#3](https://github.com/omab/python-social-auth/issues/3) **Merged pull requests:** - Fixes broken email confirmation for SQLAlchemy storage and webpy\_app [\#205](https://github.com/omab/python-social-auth/pull/205) ([w0rm](https://github.com/w0rm)) - Update mendeley.py [\#203](https://github.com/omab/python-social-auth/pull/203) ([sbassi](https://github.com/sbassi)) - Removed commit marker [\#192](https://github.com/omab/python-social-auth/pull/192) ([dkingman](https://github.com/dkingman)) - Add Clef backend [\#189](https://github.com/omab/python-social-auth/pull/189) ([tklovett](https://github.com/tklovett)) - Fixed a typo. [\#188](https://github.com/omab/python-social-auth/pull/188) ([ykalchevskiy](https://github.com/ykalchevskiy)) - Add a Bitdeli Badge to README [\#185](https://github.com/omab/python-social-auth/pull/185) ([bitdeli-chef](https://github.com/bitdeli-chef)) - added information for FIELDS\_STORED\_IN\_SESSION [\#184](https://github.com/omab/python-social-auth/pull/184) ([joelewis](https://github.com/joelewis)) - updated live connection for better support [\#183](https://github.com/omab/python-social-auth/pull/183) ([hassek](https://github.com/hassek)) ## [v0.1.21](https://github.com/omab/python-social-auth/tree/v0.1.21) (2014-02-05) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.20...v0.1.21) **Closed issues:** - User association by email should be case insensitive [\#179](https://github.com/omab/python-social-auth/issues/179) - ImproperlyConfigured: Module "social.apps.django\_app.utils" does not define a "BackendWrapper" authentication backend [\#175](https://github.com/omab/python-social-auth/issues/175) - Django usernames more then 30 charracters, via setting variable [\#174](https://github.com/omab/python-social-auth/issues/174) - Dropbox Lack of Encoding Causes Connection Failures [\#173](https://github.com/omab/python-social-auth/issues/173) - On new Tumblr login: AttributeError: 'NoneType' object has no attribute 'expiration\_datetime' [\#172](https://github.com/omab/python-social-auth/issues/172) - Tornado example not working ? [\#171](https://github.com/omab/python-social-auth/issues/171) - Unicode-object must be encoded before hashing [\#168](https://github.com/omab/python-social-auth/issues/168) - Accessing access\_token ? [\#167](https://github.com/omab/python-social-auth/issues/167) - suggestion: please change the username column in auth\_user from "name" to "domain" for weibo backend [\#164](https://github.com/omab/python-social-auth/issues/164) - Invalid openid.mode: '\' [\#163](https://github.com/omab/python-social-auth/issues/163) - get\_user\_id refers to details [\#136](https://github.com/omab/python-social-auth/issues/136) **Merged pull requests:** - Add version parameter to foursquare backend [\#176](https://github.com/omab/python-social-auth/pull/176) ([michisu](https://github.com/michisu)) - Added PixelPin to list of providers [\#170](https://github.com/omab/python-social-auth/pull/170) ([lukos](https://github.com/lukos)) - Added new PixelPin provider. [\#169](https://github.com/omab/python-social-auth/pull/169) ([lukos](https://github.com/lukos)) - Serializer changed. [\#165](https://github.com/omab/python-social-auth/pull/165) ([omgbbqhaxx](https://github.com/omgbbqhaxx)) ## [v0.1.20](https://github.com/omab/python-social-auth/tree/v0.1.20) (2014-01-17) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.19...v0.1.20) **Closed issues:** - docs and examples not included in pypi tarball [\#162](https://github.com/omab/python-social-auth/issues/162) - Unable to retrieve any extra\_data from LinkedIn backend [\#161](https://github.com/omab/python-social-auth/issues/161) - Twitter backend error with Python 3.3 [\#139](https://github.com/omab/python-social-auth/issues/139) ## [v0.1.19](https://github.com/omab/python-social-auth/tree/v0.1.19) (2014-01-16) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.18...v0.1.19) ## [v0.1.18](https://github.com/omab/python-social-auth/tree/v0.1.18) (2014-01-16) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.17...v0.1.18) **Closed issues:** - GooglePlusAuth backend do not store 'access\_token' on extra\_data \(psa v0.1.17\) [\#157](https://github.com/omab/python-social-auth/issues/157) - partial pipeline "example.app.pipeline.require\_email" for django does not work [\#152](https://github.com/omab/python-social-auth/issues/152) - Other dependencies missing [\#151](https://github.com/omab/python-social-auth/issues/151) - Force https redirect\_uri causes Exception when loading strategy [\#148](https://github.com/omab/python-social-auth/issues/148) - ValueError: too many values to unpack [\#146](https://github.com/omab/python-social-auth/issues/146) - AuthCanceled: Authentication process canceled Error [\#144](https://github.com/omab/python-social-auth/issues/144) - django nonce salt field is too short [\#141](https://github.com/omab/python-social-auth/issues/141) - Missing dependencies in readme [\#140](https://github.com/omab/python-social-auth/issues/140) - Incorrect Client Credentials via GitHub [\#138](https://github.com/omab/python-social-auth/issues/138) - Could I redirect complete page to original login page? [\#137](https://github.com/omab/python-social-auth/issues/137) - User-friendly backend names [\#132](https://github.com/omab/python-social-auth/issues/132) - Yahoo backend handle key error [\#125](https://github.com/omab/python-social-auth/issues/125) - Use constant time comparison function [\#122](https://github.com/omab/python-social-auth/issues/122) - Inquiry: Why is social.tests.backends not part of the package? [\#119](https://github.com/omab/python-social-auth/issues/119) - How to get Facebook username during Social authentication [\#117](https://github.com/omab/python-social-auth/issues/117) - How to get backend instance [\#114](https://github.com/omab/python-social-auth/issues/114) - Connecting multiple social auths from same provider [\#112](https://github.com/omab/python-social-auth/issues/112) - Linkedin JSAPI and exchanging Client-side Bearer Token for OAuth 1.0a token [\#111](https://github.com/omab/python-social-auth/issues/111) - get\_strategy\(\) got multiple values for keyword argument 'request' [\#110](https://github.com/omab/python-social-auth/issues/110) - Twitter OAuth ValueError [\#107](https://github.com/omab/python-social-auth/issues/107) - Facebook scope not set anymore [\#106](https://github.com/omab/python-social-auth/issues/106) - Namespacing for python-social-auth [\#103](https://github.com/omab/python-social-auth/issues/103) - Additional backend API calls after user authorization [\#102](https://github.com/omab/python-social-auth/issues/102) - Linkedin OAuth not working [\#101](https://github.com/omab/python-social-auth/issues/101) - Make extending SOCIAL\_AUTH\_PIPELINE easier [\#99](https://github.com/omab/python-social-auth/issues/99) - Authentication problem with Weibo backend when integrate with Django application [\#98](https://github.com/omab/python-social-auth/issues/98) - Odnoklassniki - PARAM\_API\_KEY : No application key [\#97](https://github.com/omab/python-social-auth/issues/97) - Per backend FORCE\_EMAIL\_VALIDATION is not respected [\#95](https://github.com/omab/python-social-auth/issues/95) - Migrating from django\_social\_auth [\#94](https://github.com/omab/python-social-auth/issues/94) - UnicodeError in mailru backend [\#91](https://github.com/omab/python-social-auth/issues/91) - setting OPENID\_PAPE\_MAX\_AUTH\_AGE equal to zero doesn't force reauthentication [\#89](https://github.com/omab/python-social-auth/issues/89) - added associate\_by\_email to pipeline but still adding new account when i login with a social account [\#84](https://github.com/omab/python-social-auth/issues/84) - LinkedIn OAuth2 bad request. [\#58](https://github.com/omab/python-social-auth/issues/58) **Merged pull requests:** - AUTHORIZATION\_URL changed to https [\#160](https://github.com/omab/python-social-auth/pull/160) ([harshiljain](https://github.com/harshiljain)) - GooglePlusAuth backend do not store 'access\_token' on extra\_data \(psa v0.1.17\) [\#159](https://github.com/omab/python-social-auth/pull/159) ([jgsogo](https://github.com/jgsogo)) - Solves some revoke\_token related errors \(BaseOAuth1 and FacebookOAuth2\) [\#158](https://github.com/omab/python-social-auth/pull/158) ([jgsogo](https://github.com/jgsogo)) - odnoklassniki backend iframe app fix [\#156](https://github.com/omab/python-social-auth/pull/156) ([maxtepkeev](https://github.com/maxtepkeev)) - Update Flask integration to most recent version [\#150](https://github.com/omab/python-social-auth/pull/150) ([xen](https://github.com/xen)) - Fixed issue with redirect\_uri with https [\#149](https://github.com/omab/python-social-auth/pull/149) ([roberto-robles](https://github.com/roberto-robles)) - add docs for backend Taobao [\#147](https://github.com/omab/python-social-auth/pull/147) ([jcouyang](https://github.com/jcouyang)) - Add support for \(淘宝\)Taobao OAuth2 [\#145](https://github.com/omab/python-social-auth/pull/145) ([jcouyang](https://github.com/jcouyang)) - Add Dropbox OAuth2 Support [\#143](https://github.com/omab/python-social-auth/pull/143) ([coddingtonbear](https://github.com/coddingtonbear)) - increasing length of salt field for django apps, fixes \#141 [\#142](https://github.com/omab/python-social-auth/pull/142) ([eknuth](https://github.com/eknuth)) - Add support for OpenStreetMap OAuth [\#135](https://github.com/omab/python-social-auth/pull/135) ([Xmypblu](https://github.com/Xmypblu)) - Support for MongoEngine authentication using Custom User Model [\#134](https://github.com/omab/python-social-auth/pull/134) ([ncortot](https://github.com/ncortot)) - Update reddit.py - comment was referencing Github. [\#133](https://github.com/omab/python-social-auth/pull/133) ([gorillamania](https://github.com/gorillamania)) - Tiny typo fix [\#130](https://github.com/omab/python-social-auth/pull/130) ([parlarjb](https://github.com/parlarjb)) - fix session expiration in vk backend [\#129](https://github.com/omab/python-social-auth/pull/129) ([maxtepkeev](https://github.com/maxtepkeev)) - Added support for named URLs and URL translation using the django built-... [\#127](https://github.com/omab/python-social-auth/pull/127) ([hekevintran](https://github.com/hekevintran)) - Updated pipeline example to include externalized auth. [\#126](https://github.com/omab/python-social-auth/pull/126) ([bimsapi](https://github.com/bimsapi)) - Removed non-ascii character from author string [\#123](https://github.com/omab/python-social-auth/pull/123) ([monkut](https://github.com/monkut)) - Add test backends to the package. [\#121](https://github.com/omab/python-social-auth/pull/121) ([hansl](https://github.com/hansl)) - Missing trailing slash on complete url [\#120](https://github.com/omab/python-social-auth/pull/120) ([gorghoa](https://github.com/gorghoa)) - getpocket.com backend [\#116](https://github.com/omab/python-social-auth/pull/116) ([stephenmcd](https://github.com/stephenmcd)) - fix uid in coinbase oauth [\#109](https://github.com/omab/python-social-auth/pull/109) ([FloorLamp](https://github.com/FloorLamp)) - Add Coinbase OAuth2 [\#105](https://github.com/omab/python-social-auth/pull/105) ([FloorLamp](https://github.com/FloorLamp)) - Update weibo.py [\#100](https://github.com/omab/python-social-auth/pull/100) ([josseph](https://github.com/josseph)) - Make vk-app backend to retrieve additional user data in respect to the \*\_EXTRA\_DATA setting [\#96](https://github.com/omab/python-social-auth/pull/96) ([maxtepkeev](https://github.com/maxtepkeev)) - Refresh the docs on http://psa.matiasaguirre.net/docs/ [\#93](https://github.com/omab/python-social-auth/pull/93) ([sahilgupta](https://github.com/sahilgupta)) - Allow for server side flow for Google+ [\#92](https://github.com/omab/python-social-auth/pull/92) ([assiotis](https://github.com/assiotis)) - Fitbit uid [\#90](https://github.com/omab/python-social-auth/pull/90) ([juanriaza](https://github.com/juanriaza)) ## [v0.1.17](https://github.com/omab/python-social-auth/tree/v0.1.17) (2013-11-13) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.16...v0.1.17) **Closed issues:** - Problem with weibo backend [\#85](https://github.com/omab/python-social-auth/issues/85) - Steam auth 401 Client Error: Unauthorized [\#82](https://github.com/omab/python-social-auth/issues/82) - Unit Test Django Client Login? [\#81](https://github.com/omab/python-social-auth/issues/81) - Facebook profile picture [\#80](https://github.com/omab/python-social-auth/issues/80) - AttributeError at /user/login/yahoo/ 'Association' object has no attribute 'id' [\#78](https://github.com/omab/python-social-auth/issues/78) - Extending mongoengine User model for SOCIAL\_AUTH\_USER\_MODEL [\#70](https://github.com/omab/python-social-auth/issues/70) - Clarify what the callback url should be for github backend [\#66](https://github.com/omab/python-social-auth/issues/66) - Duplicate entry error when updating an existing user with a social user [\#63](https://github.com/omab/python-social-auth/issues/63) - Problem with do\_complete for Facebook backend [\#39](https://github.com/omab/python-social-auth/issues/39) - Using UserSocialAuth model with Django's generic FKs breaks [\#38](https://github.com/omab/python-social-auth/issues/38) **Merged pull requests:** - Use strategy.backend.name instead of strategy.backend\_name [\#88](https://github.com/omab/python-social-auth/pull/88) ([nitishr](https://github.com/nitishr)) - Use strategy.backend.name instead of strategy.backend\_name [\#87](https://github.com/omab/python-social-auth/pull/87) ([nitishr](https://github.com/nitishr)) - Use strategy.backend.name instead of strategy.backend\_name [\#86](https://github.com/omab/python-social-auth/pull/86) ([nitishr](https://github.com/nitishr)) - Raise Http404 in django auth view when the backend is not found [\#83](https://github.com/omab/python-social-auth/pull/83) ([despawnerer](https://github.com/despawnerer)) - Mod: URL for registering Windows Live key/secret [\#79](https://github.com/omab/python-social-auth/pull/79) ([yegle](https://github.com/yegle)) ## [v0.1.16](https://github.com/omab/python-social-auth/tree/v0.1.16) (2013-11-07) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.15...v0.1.16) **Closed issues:** - TypeError at /auth/login/google/: int\(\) argument must be a string or a number, not Association [\#76](https://github.com/omab/python-social-auth/issues/76) - Problem with Douban backend [\#72](https://github.com/omab/python-social-auth/issues/72) **Merged pull requests:** - Include actions module in distribution [\#77](https://github.com/omab/python-social-auth/pull/77) ([nijel](https://github.com/nijel)) - Update partial from session with more recent values from kwargs [\#75](https://github.com/omab/python-social-auth/pull/75) ([branden](https://github.com/branden)) - Tox support [\#74](https://github.com/omab/python-social-auth/pull/74) ([noirbizarre](https://github.com/noirbizarre)) - quote message for url inclusion in Django middleware [\#73](https://github.com/omab/python-social-auth/pull/73) ([noirbizarre](https://github.com/noirbizarre)) - Return the updated dict. [\#71](https://github.com/omab/python-social-auth/pull/71) ([branden](https://github.com/branden)) ## [v0.1.15](https://github.com/omab/python-social-auth/tree/v0.1.15) (2013-11-04) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.14...v0.1.15) **Closed issues:** - Complete authentication through REST API [\#68](https://github.com/omab/python-social-auth/issues/68) - Is there a short way to connect a social account to existing user [\#62](https://github.com/omab/python-social-auth/issues/62) - typo in docstring [\#61](https://github.com/omab/python-social-auth/issues/61) - Latest version tag gone wrong [\#60](https://github.com/omab/python-social-auth/issues/60) - LinkedIn extra\_data only partially retrieved [\#57](https://github.com/omab/python-social-auth/issues/57) - Django/Facebook login issue [\#56](https://github.com/omab/python-social-auth/issues/56) - user\_details pipeline does not update protected fields for new users [\#55](https://github.com/omab/python-social-auth/issues/55) - Bug in login with Django 1.6 [\#53](https://github.com/omab/python-social-auth/issues/53) - Token refreshing [\#52](https://github.com/omab/python-social-auth/issues/52) - Simple question - template use [\#50](https://github.com/omab/python-social-auth/issues/50) - Django - Error when I try to run ./manage.py [\#48](https://github.com/omab/python-social-auth/issues/48) **Merged pull requests:** - Add Tornado Support. [\#69](https://github.com/omab/python-social-auth/pull/69) ([san-mate](https://github.com/san-mate)) - Function user\_data returns list. This leads to exception in social/backe... [\#67](https://github.com/omab/python-social-auth/pull/67) ([akamit](https://github.com/akamit)) - Add RunKeeper [\#65](https://github.com/omab/python-social-auth/pull/65) ([JasonSanford](https://github.com/JasonSanford)) - Make partial\_pipeline JSON serializable for django 1.6 [\#64](https://github.com/omab/python-social-auth/pull/64) ([hannseman](https://github.com/hannseman)) - Appsfuel doc from dsa to psa [\#59](https://github.com/omab/python-social-auth/pull/59) ([z4r](https://github.com/z4r)) - Add openSUSE OpenID login [\#51](https://github.com/omab/python-social-auth/pull/51) ([nijel](https://github.com/nijel)) - `sanitize\_redirect` don't work with Django's `reverse\_lazy` [\#49](https://github.com/omab/python-social-auth/pull/49) ([volrath](https://github.com/volrath)) ## [v0.1.14](https://github.com/omab/python-social-auth/tree/v0.1.14) (2013-10-07) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.13...v0.1.14) **Closed issues:** - Amazon oauth , client\_id of None in url? [\#47](https://github.com/omab/python-social-auth/issues/47) - AttributeError: 'str' object has no attribute '\_meta' in Django's admin.py [\#45](https://github.com/omab/python-social-auth/issues/45) - Invalid documentation for Yahoo OAuth key/secret [\#43](https://github.com/omab/python-social-auth/issues/43) - using mongoengine \> 0.8, referencefields now store objectids not dbrefs [\#42](https://github.com/omab/python-social-auth/issues/42) - Google OAuth2 Disconnect [\#41](https://github.com/omab/python-social-auth/issues/41) - KeyError at /complete/facebook/ when trying to sign in without verifying e-mail address [\#40](https://github.com/omab/python-social-auth/issues/40) - MongoEngine compability [\#37](https://github.com/omab/python-social-auth/issues/37) - TypeError at /complete/facebook/ [\#36](https://github.com/omab/python-social-auth/issues/36) **Merged pull requests:** - Fixes \#45 -- AttributeError while resolving the user model in Django [\#46](https://github.com/omab/python-social-auth/pull/46) ([MarkusH](https://github.com/MarkusH)) - Add python 3.3 and django 1.6 compatibility [\#44](https://github.com/omab/python-social-auth/pull/44) ([nvbn](https://github.com/nvbn)) ## [v0.1.13](https://github.com/omab/python-social-auth/tree/v0.1.13) (2013-09-22) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.12...v0.1.13) **Closed issues:** - Error: django.db.models.fields.subclassing.JSONField [\#35](https://github.com/omab/python-social-auth/issues/35) - some linkedin oauth2 extra data doesn't show up [\#34](https://github.com/omab/python-social-auth/issues/34) - Odnoklassniki backend requires authization by POST [\#33](https://github.com/omab/python-social-auth/issues/33) - SOCIAL\_AUTH\_GOOGLE\_OAUTH2\_EXTRA\_SCOPE is ignored [\#28](https://github.com/omab/python-social-auth/issues/28) - Example for pyramid [\#27](https://github.com/omab/python-social-auth/issues/27) - Not working with instagram api [\#21](https://github.com/omab/python-social-auth/issues/21) **Merged pull requests:** - Update README.rst [\#32](https://github.com/omab/python-social-auth/pull/32) ([jontsai](https://github.com/jontsai)) - Update pipeline.rst [\#31](https://github.com/omab/python-social-auth/pull/31) ([jontsai](https://github.com/jontsai)) - Update README.rst [\#30](https://github.com/omab/python-social-auth/pull/30) ([jontsai](https://github.com/jontsai)) ## [v0.1.12](https://github.com/omab/python-social-auth/tree/v0.1.12) (2013-09-13) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.11...v0.1.12) **Closed issues:** - Setting the facebook scope wrongly documented [\#29](https://github.com/omab/python-social-auth/issues/29) **Merged pull requests:** - Fixed auth redirect URL for BaseOauth2 always redirecting wrong [\#26](https://github.com/omab/python-social-auth/pull/26) ([romanalexander](https://github.com/romanalexander)) - Adding support for ThisIsMyJam [\#25](https://github.com/omab/python-social-auth/pull/25) ([systemizer](https://github.com/systemizer)) - Add support for box.net [\#24](https://github.com/omab/python-social-auth/pull/24) ([samkuehn](https://github.com/samkuehn)) ## [v0.1.11](https://github.com/omab/python-social-auth/tree/v0.1.11) (2013-09-04) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.10...v0.1.11) **Closed issues:** - Steam user ID broken in Django backend [\#23](https://github.com/omab/python-social-auth/issues/23) - Flask example fails to complete connection to Github [\#22](https://github.com/omab/python-social-auth/issues/22) ## [v0.1.10](https://github.com/omab/python-social-auth/tree/v0.1.10) (2013-08-29) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.9...v0.1.10) ## [v0.1.9](https://github.com/omab/python-social-auth/tree/v0.1.9) (2013-08-29) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.8...v0.1.9) **Closed issues:** - google oauth 2.0 [\#20](https://github.com/omab/python-social-auth/issues/20) - Support for linkedin oauth2 [\#19](https://github.com/omab/python-social-auth/issues/19) - Invalid Steam backend user id. [\#17](https://github.com/omab/python-social-auth/issues/17) **Merged pull requests:** - SQLAlchemy fixes [\#18](https://github.com/omab/python-social-auth/pull/18) ([Flyflo](https://github.com/Flyflo)) ## [v0.1.8](https://github.com/omab/python-social-auth/tree/v0.1.8) (2013-07-13) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.7...v0.1.8) **Merged pull requests:** - Fix OpenId auth with Flask 0.10 [\#16](https://github.com/omab/python-social-auth/pull/16) ([Flyflo](https://github.com/Flyflo)) - Add CodersClan button [\#13](https://github.com/omab/python-social-auth/pull/13) ([DrorCohenCC](https://github.com/DrorCohenCC)) - Added a default to response in FacebookOAuth.do\_auth [\#12](https://github.com/omab/python-social-auth/pull/12) ([san-mate](https://github.com/san-mate)) - Bug fix of FacebookAppOAuth2 [\#11](https://github.com/omab/python-social-auth/pull/11) ([san-mate](https://github.com/san-mate)) ## [v0.1.7](https://github.com/omab/python-social-auth/tree/v0.1.7) (2013-06-03) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.6...v0.1.7) ## [v0.1.6](https://github.com/omab/python-social-auth/tree/v0.1.6) (2013-06-03) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.5...v0.1.6) ## [v0.1.5](https://github.com/omab/python-social-auth/tree/v0.1.5) (2013-06-01) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.4...v0.1.5) ## [v0.1.4](https://github.com/omab/python-social-auth/tree/v0.1.4) (2013-05-31) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.3...v0.1.4) ## [v0.1.3](https://github.com/omab/python-social-auth/tree/v0.1.3) (2013-05-31) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.2...v0.1.3) **Closed issues:** - get\_user\_details\(\) vs user\_data\(\) [\#7](https://github.com/omab/python-social-auth/issues/7) **Merged pull requests:** - Added support for django custom user with no 'username' field [\#10](https://github.com/omab/python-social-auth/pull/10) ([jgsogo](https://github.com/jgsogo)) - Add Trello backend support [\#9](https://github.com/omab/python-social-auth/pull/9) ([dongweiming](https://github.com/dongweiming)) - Podio backend [\#8](https://github.com/omab/python-social-auth/pull/8) ([gsakkis](https://github.com/gsakkis)) - VK.com \(former vkontakte\) backend update [\#6](https://github.com/omab/python-social-auth/pull/6) ([uruz](https://github.com/uruz)) - Bug fix with Vkontakte provider [\#5](https://github.com/omab/python-social-auth/pull/5) ([kazarinov](https://github.com/kazarinov)) ## [v0.1.2](https://github.com/omab/python-social-auth/tree/v0.1.2) (2013-04-04) [Full Changelog](https://github.com/omab/python-social-auth/compare/v0.1.1...v0.1.2) **Closed issues:** - Flask example - missing relation 'social\_auth\_usersocialauth' [\#4](https://github.com/omab/python-social-auth/issues/4) ## [v0.1.1](https://github.com/omab/python-social-auth/tree/v0.1.1) (2013-04-01) **Closed issues:** - confusing update to globals in the flask integration [\#1](https://github.com/omab/python-social-auth/issues/1) **Merged pull requests:** - Fixed South introspection path to new module structure. [\#2](https://github.com/omab/python-social-auth/pull/2) ([jezdez](https://github.com/jezdez)) \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* python-social-auth-0.2.21/tox.ini0000644000175500017550000000134512754357263016412 0ustar debacledebacle# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py27, py33, py34, pypy, doc [testenv] commands = nosetests --where=social/tests --stop deps = -r{toxinidir}/social/tests/requirements.txt [testenv:pypy] deps = -r{toxinidir}/social/tests/requirements-pypy.txt [testenv:py33] deps = -r{toxinidir}/social/tests/requirements-python3.txt [testenv:py34] deps = -r{toxinidir}/social/tests/requirements-python3.txt [testenv:doc] changedir = docs deps = sphinx commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html python-social-auth-0.2.21/.gitignore0000644000175500017550000000065512754357263017072 0ustar debacledebacle*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # PyCharm .idea/ test.db local_settings.py sessions/ _build/ fabfile.py changelog.sh .DS_Store .\#* \#*\# .python-version python-social-auth-0.2.21/site/0000755000175500017550000000000013035013651016017 5ustar debacledebaclepython-social-auth-0.2.21/site/index.html0000644000175500017550000001145612754357263020044 0ustar debacledebacle Python Social Auth

Python Social Auth

Python Social Auth is an easy to setup social authentication/registration mechanism with support for several frameworks and auth providers.

Crafted using base code from django-social-auth, implements a common interface to define new authentication providers from third parties. And to bring support for more frameworks and ORMs.

Learn more »

Frameworks

The lib supports a few frameworks at the moment with Django, Flask, Pyramid, Webpy, CherryPy and Tornado and more to come. The frameworks API should ease the implementation to increase the number of frameworks supported.

View details »

Authentication Providers

Ported from django-social-auth, the application brings plenty of authentication providers, many from popular services like Google, Facebook, Twitter and Github. The backends API have some implementation details on how to implement your own backends.

View details »

ORMs

There are multiple ORM python libraries around, some frameworks has their own built-in version too. python-social-auth tries to support the different interfaces available, at the moment SQLAlchemy, Django ORM and Mongoengine are supported, but with the Storage API it should be easy to add more support.

View details »

Development and Contact

The code is available on Github, report any issue if you find any. Pull requests are always welcome. There's a mailing list and IRC channel #python-social-auth on Freenode network.

View details »


© Matías Aguirre 2012

python-social-auth-0.2.21/site/js/0000755000175500017550000000000013035013520016426 5ustar debacledebaclepython-social-auth-0.2.21/site/docs0000777000175500017550000000000012754357263021313 2../docs/_build/ustar debacledebaclepython-social-auth-0.2.21/site/img/0000755000175500017550000000000012754357263016614 5ustar debacledebaclepython-social-auth-0.2.21/site/img/glyphicons-halflings-white.png0000644000175500017550000002111112754357263024560 0ustar debacledebaclePNG  IHDRӳ{PLTEmmmⰰᒒttt󻻻bbbeeeggg𶶶xxx󛛛Ƽ몪֢UUU鿿rOtRNS#_ /oS?C kDOS_6>4!~a @1_'onҋM3BQjp&%!l"Xqr; A[<`am}43/0IPCM!6(*gK&YQGDP,`{VP-x)h7e1]W$1bzSܕcO]U;Zi'y"؆K 64Y*.v@c.};tN%DI !ZЏ5LH26 ɯ" -bE,,)ʏ B>mn6pmRO wm@V#?'CȑZ#qb|$:)/E%nRqChn%i̓}lm ?idd",`H"r.z~(bQU&)5X#EMR<*p[[%.Ọk7lIoJF lV!̡ăuH`&,zRk$|$lXbjߪdU?Σ$HW$U'HE3*խU\}( zhVk}guRk$%|T|ck獳"D_W+.Q)@ƽHbslTDR2Xm#a 3lYzj㒚#! 4J8(cvt]aT D ΅Q?^-_^$:\V $N|=(vZ'q6Z׆B5V!y3K㱿bv4xR]al!IoP@tVyL٪mlڿIUb|[*lke'*WddDӝ}\W_WߝrN?vޫ۲X%0uoui*JVƦb%}i5IYlNE-wςf_W3mI-mQ)S kTC7m<"܌bT|'$ҘR&>O p6tSN\ׯLm\r@3uT b7t.5.q3r0=8TiJ\6uF R32^'ŪxI F8O{%8kJMSȴdBEdWCYO:/ON/I_=xFE! =i:o~ y?''[͓[͓[͓[͓[ͭ.U>$PƦc%]\c:| ,eSZ,oXrX!R@Zv 0>?* <|N60;{ad2v+D^t[q!۞V}fۨϏYeॗ)Vyl|" fUq@Ǽ4Y-Y-!6aB:o%JIUQ|UKO`=\ :0x Pau@!KPdxhw1>$j΍vZdxSUA&[URd7øzk/rU^w:I.VǮc>q.!zSr&2)Wg R -iQ 8Pa\ОU%iݡU_=p Lu(N?0?Æ:]άtB%U|NsorNf ,P !v" Y6hL_@@bscqgv4||0lϟ$S9bʱj#~?o}}7sAPm:IV=n !{{hEࢪ8suoLT$;VscqD3 ༂3.DBB4&V' T `D6Ϸqyj8V*X%@s\jrN$|=5Ά 'mUiKi%CI:ssaƅ`*`=l)>u՘MeuSI_OL_}o&jzp{lu:O)s%Q@$<]f xO%PCbhr2PKpf5Në3^o]eJiB464^tuٲU֌:G4'22YpuG'/Py4?.SBP_>I 1t3ΓBɭɭɭɭVVVVVs]!67(g y@ 4>Q VF}^Xׇڼje26 L%YGh lC})< !EEPZWZV+@†R 5{@ouɐ4&H6ey V݀VťcqZޒrJyByFzFN$Hb*+jՏqэ ګkݿUXle1d0d^-B%} {Y%r*j5Ak5u",:~ҸY~ hSA~6 fulՇf{ȵQtATHZkƭ/_Sn u']b]|m`BāJ,O$du]Zs FL:aǙT4o~by?wpj滥A(x]†f~an֧/^dڲcՇ,!1i&xi_VK@ip̓9Vi%a; L?0J*Ū5U'x^6V[^ {eU|:0=0d۫o*Jq%[YN.sQLud[29I:WnmXlڃ6!lNlVէKUjV\J%UߊBLcKfb>a=b~R]aG%[js@/9MطݘU>yɲX@} Ftg^vO\Ӹwvpz3K5i!$P>ā'VƛL2r@UMKZ6tw맟¦bm1h||]}~0MjA(JJP68C&yr׉e}j_cJ?I0k>šW |Bޝ."TEXd 8!cw*E(J)![W"j_ТeX_XB;oO0~?:PC (.[!Wq%*leY)E<^KZT60.#A\5;Rmtkd/8)5~^0 #Ckgey)ͶԺ6ĥ<(?&uAVm0^h.txR*a':,H|ō l5z;8+e#b'#|}2w(|KcJ l6 w^Տoi3H R ̔9,YgPְ:N [5SR![)]i}`mN4Хv`|;f(FltL8÷Z#AO%Y)NU5YedJE3dZذݣHT1 ;8MjnʏӤqp 1h^<<>yt{?|'j)}YUU{@V/J1F+7䀉[OWO[ yUY!?BD%DWj>-Ai6xz)U R7 d@g\so)a4zf[W+> P> |qLG8vȣlj2Zt+VA6gT *ʆUz(m)CD `He/.:zN9pgo &NC׃އ>Wհ_Hj)Xe6F7pm-`'c.AZ=^e8F;{Rtn(z!S7o Iew3]bܗ85|iϠRJkʱZRO+8U&:]ZieR(JMޗ7Z@5a^\GzsρU*rMezT^:ɬͦX=>$ bi>U&XQoybbGk8 Ҙn).Սo ^MmdZi$soo*{4eLbLٳ""mx:`:mk[geTެ)'0*TB{!I ''''[͓[͓[͓[͓[]Zj Q.e '/yvQ71(Z&X?(_Z){tڀmZWϏ)-C jqn,̋"IvUL!h꛿skAcrN佚фVE40yX~4zʸV㳰%,)fqtpu~  *^0:ܲ33JO(ZB?K^ v]unlWi0p6[착C_5X#[wX3b廫R{NKAe Se|wxso>P\儔ԕ6;nVmfI$V͓J-J%֌0UwYЎSnum藮xz˗VƫIvnW_qLZ"_Xz 8]Ap?C543zw({7e*Ȳ`۰!AQ:KUnz]1yVGaCm0PY ٚUx6TT&hV9V ӬzÑ 1[XzZ9erqJND/gX*9oN6D` {I%Mz9—TQ7f\"j_3~xB'ܷY]*KЌ%"5"qxq~ƕ=jS>jV&~]2xzF1X_yD<#NRB}K/iy !V^˿eJ}/FkA7 S+.(ecJ:zWZ몖wQ~ä́p6,e5,+,tv%O^OO}ן -O7>ekC6wa_C |9*WA)UJg8=:mjUvqysܒLglC6+[FSWg9wV31A ND<$5e(s[ ۨbaF.]KIENDB`python-social-auth-0.2.21/site/img/glyphicons-halflings.png0000644000175500017550000003077712754357263023464 0ustar debacledebaclePNG  IHDRtEXtSoftwareAdobe ImageReadyqe<1IDATx}ml\EW^ɺD$|nw';vю8m0kQSnSV;1KGsԩ>UoTU1cƖYuּca&#C,pؚ>kں ULW -sn3Vq~NocI~L{- H8%_M£wB6EW,ĢpY2+(Y@&A/3kXhߍ-aA<>P'\J;(}#Qz:4%m?nfntK*l9J+DIYu1YZ^(]YYEf@ОlXz]Ut u &5-PW}@t|#LY=s܂,w#+R+?Ƌax X0"ea)tG*ԡwVwV^rf%xB(qּ4>WG#lWU<ЁXJVѶlR$kDVrI7:X%X1NEzw;y9z9O%~~uɗ*=Ixcy}Y(ou ±N$^j e\iX񝜬];Y-rѲ&>!zlYaVHVN԰9=]=mRMdOUC JUiT}rWW'ڹu)ʢF"YU#P׾&ܑЅROwyzm$Os? +^FTIEq%&~ >M}]ԖwA? [Nteexn(措BdMTpʥnqqS?bWXmW6x*{V_!VjΧsVL^j XkQjU6sk̩n~[qǸ-` O:G7l"ksRe2vQ=QƼJUX`gQy~ ďKȰE]#P:td\T/u;س:Jc-%'e q ?j"/yh48Zi1|JUu>_N;hxwNU JQU7\j̮bT:B?6oJ1Ί%I UY-Ii4{=rǤ7@)HKJ+f4X8Cd?'j1 N< 39EWo VTGzg# %D0#ܠ3[tiآ( U,]125|Ṋfw7w u+Š]Db]K xbW ՛7|ВX㕛{UcGXk¬|(h)IUa)lp 3luPU]D)/7~4Wt5J}V X0z VM;>Gԙ^|gF:jaZ^)74C#jwr,еSlGu;1vm><)}ZQՖ&mZ:1UMB~ a:/᜗:KWWOҠ&Y2f7cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘g*3fF5LbN2#Tf=C`!ZGUe꣇e2V<1mkS4iϗ*.{N8Xaj~ڀnAx,%fE:|YDVj ¢lg6(:k~MM5?4 ]WO>诋WZiG|QGJeK[YcյpmjE\f/ǎ8&OQ3 .3tt2'-V8pXSrY#J!Q ",ub@FK:u^iy[]<.Cw+W\)b kr-.MtڀMqʄ۰#$^X$"V`T4m~w%Pp1|+&UxY8*r8:k7QЃҀT$Ўƙ S>~Sjs:5q.w&_Z.X=:ވbw` _kd{'0:ds#qi!224nq\9-KUTsSUuVo@;Uz>^=Np>oPO @I@'Gj5o*U>^*ew>ͫʧ᫠Q5 ̈́<$#5Jٻj6e)_ d]2B:^(*:8JYS鬆Kݗ ]U4_rj{5ׇaǑ/yV?GtGb@xPU7O3|鍪 IQ5QGw *(;wf0*PUU<YƔvbt5{2!,}Ҧ:)j2OkΪ' ֊0I.q\(%ojQĖՇa<ԍexAgt'[d;׸`rcdjPFU$UeJI6T&Z}z(z vfuz {}ۿߝݞlxUZ謊.Y岟b%nw@ǩS9|źs%>_o#9\EU~/ځt(r[QZuOo;!MrU]0TcpDő?.cPuF;L_Sb}R/J_+h2$ai UǩS9>Є}76rzu~国4oĨ 1J ^̘~iC޸55G׹]gwsn zTuO=?/zƲc>Οb#7ֻcgkޛTUj*-T=]uu}>ݨNЭ [ ]:%/_ Sz]6D.mD7Uƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1c>J4hPP+A;'G_XKmL5I.},wFFum$S-E-;Õ C3I-`BRx1ғTJݕ;hΊ8 DYJo;Yš5MKɰM;%Pd9KhnD[zgVh,'C p!^M(WK2X>UQ%^p8 ˽^#Ζ؄+.@gCz%ɔ-Pr KX n>=ՔѨeSvRLz5%9UQS \WիK'hp)ô Jrh M0F (f_R5///G+x 1"eS 5 :Tf=+7Qɧ\TEs༬rYs8&k#pSՊ5MTbD܊[Ng5Q\s5PB@[8ɨV1&4Wsy[Ǿ wU2V77jމd^~YfC_h;a.&M i UWpzs`>/"'OI۲y:BzdTq£=йb:"m/-/PWDQǴ͐57m`H%AV!Hԛ׿@"Qzދ|ߒT-*OU^Ҧ6!Cwk|h&Hd5LEYy'ƣ7%*{=)Z%ٝP *G]/8Lw$?8M)\į/#7Ufd7'6\h1 vIfEIr=1w\WKVZHKgZ͡$mx % `j}TuTQJZ*H>*QxkLFTyU-)ôbiA|q`F'+ 4^Qy xH)#t^?@]^`ARSqjgB:rK۷l<2-4YKhgQLxVwP~M Φ0l 3ƅaŊITȀhwJmxIMչ|U7xˆS~2ߕ?kW1kC3];YnSґAeXYz8,'x< k7Kx]$x$vgT#w;o@ z_Vmn|HֵhZg-^TAn- )@4[*9xKƋj>!,Vt:eqn8%ohS(2\Q^aigF3vTUDVlQꅧWc%Ueq4ҝº/U $_Q!>t| ,țG<tC[xTXmf|Q%d#jUՆ|; H[bά#,Ws7NT1~m&ǻ{' \㟾 bBKJo8%!$Qj:/RX)$Sy޳ 䍧RDUg_D軦J\jN֖SU;~?Ohssdƣ}6(T <_4b5 ^N N%8QejF7toMyө`)g[/|?өJuGL坕/=CTܠhdifHcǞG4,`D՞{'xG_p/5@m +$jVH3a"*ũ,,HJҵȸT^Qyo&IÉJUVwWLeM~3tA6rwɤ6տ \0HL%LX5c@HHÃZ|NV+7WM{cig*ȸU7iÉбzd * ?gtX8̝OX:]2ɍ]p^++>AVڛE{ DB.&/56ArxY#ܕy)cKQtȪ~! ;C}ʃtf{6$NVsj wupZ)zŁ|-wg+nMVj/d+U~ͯi:_ix whqr>駃-x뼬)ݷyR=! ì:J/lIkV@n74758Z KJ(Uxz1w)^\ԣzȪ󲦨c2f؍v+6f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘2N oC\F1ִ UZJV̚\4Mgq1z{&YT ,HX~D u\g}x>+YdN̮ol ZX+F[/j+S~2/jV8Jr^ԉ]J}J*ۏ<2԰&JݣjOM@ѯ#0O[SXB^ uze\]dd./xXE f'vO_H${%;kt7ށmő|d{aފ^ǛڎE5ʋBr]W=_SAf(0 oU5q ,_\luz˪uz㻲o=Yi~| 0+=VJت /ލzM\zCL[U:|k*^8"\Wٚ\ .XTjX5 SkFu\1 q'mģ/QUؕ*AɽDNZ׮?_[# ˍ4:^j|5LG ||øBW{6[uQF.1$qF9IHg)\5>C#uXZ$#*<ߐsRv1Tj>Jm>*#( [Fhsש5*jQʼ&&&P犛L[Q1* ;X}Iΰ[Q?qQZ Hݙ֞VEsBCZ9JTK tup˷ /O,.kUdsOHMg4=-)+ؿh2Nw/r|WQn=GIU;'j,vfdzpe$V GTYsBZO1pj:r"nTUSCgr veAۘ˜FC+Ֆ#[JTe'v9-3 Dmӻuuz?0 o hxuY &_54=f07kלU0]D:jdw/+PGUVS<\2uatc^zYRąmC+7#,|:iNw*|^sm|X>Ъ^1\#͹ &%{,2U>ݎ.c05z# ogNO+Q쓭 ,˗-%K\[S_`y+b_94"U+Ύap}I[M,B.NtwHj漬E L߀ 0DX(kڵ NoU{gquz RwkէRx'uZ[3'zyyד%sƕ3jYF\s=m1&VAɼ?k\+]6yモ1gtOIW7al|1 >$]e 7؝WIe?ަL#>| ҭ] pM5MUdI61ԠeǼYGhOn3խR:^k_'Yuuq#p# J2xl>OjcY馃!ڡ+sZ/ D}2AY mpc#<'xSKx`*W[,e|6BH)㶤kjpDU(2qzx9*tqa/, Z[ 0>Ө֜xN)fă@qըFU՝w(a;ˋ>|Tc|w2eiT]*!_\WG{ ]^݅Z5t|6oYHaO@= my^akE.uz]#٥hWv(:,6A߉JFa\ wWex>vetuMYA>).,;ɦCbwjE)W Fӫ@s4e6^Q9oI}4x<.B?B߫#$Hx.x9,a!RTpgd5xBe.L7@* AsduttSVUaRU|I xG߃$T񭟬#_IFMŒ_X@foQIDII?|%$r {ENĸwޕqq?Dؽ}}o/`ӣCTi /ywO rD 9YUD] Ή@s]+'UaL} hrU'7:sU|k)H@hNq#ϵ8y˭Xű#w 1!흉R'7fuד0p!WÖW+Nmp\-ioD$g٠˅%%ÐmV]̱rw*Z}y+L Nouj}xt)lStuqxmNyKUOnDbhf}k>6ufT%{ <񐮸mjFcmUïc;w8@dGFUA& =nq5]iP}z:k⼶-ʓ Κl*'UzaxWFdZzTNRs+# wzgi:MBqtM l#^'Gߣ*^t{=rERnQ$adJl02%Tڊ^<~g?Of*U^?:N+o[PUs|QR']V-L)H K䐞 mYn\4}YVD hR;g-'3aסM Dh}1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌk*Ț4`L$b U4\dt'>HȄ|.+Y+/Gy2OCWv3v,'kia W O6߯E=Hv $LlxI躍/}^]x\3 ɮ5 QT&G9Ay^i}O[5ޱwq4,s JJI.myE^%'VB~dׯ}*j* ~uTk\fKЬ*Y]_v'I˨鑩6Xo'j&uɧngT]oڌ9\*wVHӖ| >:5EF'J ɝ`!A e~_;5ױϊ镋m_&OVi<}"靍hW9X6KPƣ G"ƭ?/O^hCHLciPj)}QQզ#tMg9 xGw~d;_J+RỲ<;e 5/Qs/5N[!a+NPb+ѺI}-t_qU=MKʞY5no*vvbʊ{]| ~ Z{-끇^FVviϵ3Ya=6ndS;-ʹ^;uꪪ^ |=_w+"i&4l#wir|W3U$"J~O@]~tRJVMHw:̦@?>O?vdrtS*$&~1>Z}^nL(]f*&*QaIꝄ|3*O?r?*4Gyz[k/tkQϖWCCKk/x5|S*`ϹγQEwy o KYqTb$-/PtsZNKQ*>ݢU@Џ"JQ;¹& Lx;+T /+O赟> (T?ķD^N*'p$IW֐W~ =J|_UTe7ְP`;CYjk=sU[mߙ-;};2|wo1p0~>0m @Jrǟcٷ4͜?q\UUIV?2L/+Шꄾ< ܇^T ?tj\JrҀB*=km X,n}aՒIadp׷ll{\6v8RꅟҲf1F|Տ;e=\D ,D:ψrxQT◎*|{nS 9~=}ӕG~%j:Dj<ឫ:jO% $T8!jvm|'OЗ¹➱z\vsIv`Ȕʨj-^$-^G Q{m`T#c֞㸝|n.ߪN$O JUVʼt,jg-mסּNV z:(Ι*|1Ux=Yk*t MNNDUhK ؞X(刄Rv!#B_cxRŹoE5Dg>?fXQQ˔|@"աMveC>mO$H#]Y I=)_`k* :a>!X!W^wҒl'<;vwgIt_?Jh`#E:fdx=6Wu<Ӌd2di˂c#h¬c4?<HFYoVpN;ݷJ\ >` (t3{>⦊;;qFx4YcS$w.da*k|Q,+xs^K߫P^nO֮L5mIwl?-.ʲJ8 F B.-:2Ȕ!/A#b_m%I($|PZ[1G{^#o>3mw?'cx[^:Wk/`'=~֥W(gQbfv7UzM3+؍K:4|GCtA+Kʨ{@Ɩ [05E|yn4MIENDB`python-social-auth-0.2.21/site/css/0000755000175500017550000000000013035013751016610 5ustar debacledebaclepython-social-auth-0.2.21/site/css/site.css0000644000175500017550000000006612754357263020310 0ustar debacledebaclebody { padding-top: 60px; padding-bottom: 40px; } python-social-auth-0.2.21/requirements-python3.txt0000644000175500017550000000015012754357263021756 0ustar debacledebaclepython3-openid>=3.0.9 requests>=2.9.1 oauthlib>=1.0.3 requests-oauthlib>=0.6.1 six>=1.10.0 PyJWT>=1.4.0 python-social-auth-0.2.21/social/0000755000175500017550000000000012754357263016346 5ustar debacledebaclepython-social-auth-0.2.21/social/pipeline/0000755000175500017550000000000012754357263020153 5ustar debacledebaclepython-social-auth-0.2.21/social/pipeline/mail.py0000644000175500017550000000221312754357263021445 0ustar debacledebaclefrom social.exceptions import InvalidEmail from social.pipeline.partial import partial @partial def mail_validation(backend, details, is_new=False, *args, **kwargs): requires_validation = backend.REQUIRES_EMAIL_VALIDATION or \ backend.setting('FORCE_EMAIL_VALIDATION', False) send_validation = details.get('email') and \ (is_new or backend.setting('PASSWORDLESS', False)) if requires_validation and send_validation: data = backend.strategy.request_data() if 'verification_code' in data: backend.strategy.session_pop('email_validation_address') if not backend.strategy.validate_email(details['email'], data['verification_code']): raise InvalidEmail(backend) else: backend.strategy.send_email_validation(backend, details['email']) backend.strategy.session_set('email_validation_address', details['email']) return backend.strategy.redirect( backend.strategy.setting('EMAIL_VALIDATION_URL') ) python-social-auth-0.2.21/social/pipeline/debug.py0000644000175500017550000000037412754357263021617 0ustar debacledebaclefrom pprint import pprint def debug(response, details, *args, **kwargs): print('=' * 80) pprint(response) print('=' * 80) pprint(details) print('=' * 80) pprint(args) print('=' * 80) pprint(kwargs) print('=' * 80) python-social-auth-0.2.21/social/pipeline/disconnect.py0000644000175500017550000000207212754357263022657 0ustar debacledebaclefrom social.exceptions import NotAllowedToDisconnect def allowed_to_disconnect(strategy, user, name, user_storage, association_id=None, *args, **kwargs): if not user_storage.allowed_to_disconnect(user, name, association_id): raise NotAllowedToDisconnect() def get_entries(strategy, user, name, user_storage, association_id=None, *args, **kwargs): return { 'entries': user_storage.get_social_auth_for_user( user, name, association_id ) } def revoke_tokens(strategy, entries, *args, **kwargs): revoke_tokens = strategy.setting('REVOKE_TOKENS_ON_DISCONNECT', False) if revoke_tokens: for entry in entries: if 'access_token' in entry.extra_data: backend = entry.get_backend(strategy)(strategy) backend.revoke_token(entry.extra_data['access_token'], entry.uid) def disconnect(strategy, entries, user_storage, *args, **kwargs): for entry in entries: user_storage.disconnect(entry) python-social-auth-0.2.21/social/pipeline/partial.py0000644000175500017550000000146712754357263022171 0ustar debacledebaclefrom functools import wraps def save_status_to_session(strategy, pipeline_index, *args, **kwargs): """Saves current social-auth status to session.""" strategy.session_set('partial_pipeline', strategy.partial_to_session(pipeline_index + 1, *args, **kwargs)) def partial(func): @wraps(func) def wrapper(strategy, pipeline_index, *args, **kwargs): out = func(strategy=strategy, pipeline_index=pipeline_index, *args, **kwargs) or {} if not isinstance(out, dict): values = strategy.partial_to_session(pipeline_index, *args, **kwargs) strategy.session_set('partial_pipeline', values) return out return wrapper python-social-auth-0.2.21/social/pipeline/utils.py0000644000175500017550000000401112754357263021661 0ustar debacledebacleimport six SERIALIZABLE_TYPES = (dict, list, tuple, set, bool, type(None)) + \ six.integer_types + six.string_types + \ (six.text_type, six.binary_type,) def partial_to_session(strategy, next, backend, request=None, *args, **kwargs): user = kwargs.get('user') social = kwargs.get('social') clean_kwargs = { 'response': kwargs.get('response') or {}, 'details': kwargs.get('details') or {}, 'username': kwargs.get('username'), 'uid': kwargs.get('uid'), 'is_new': kwargs.get('is_new') or False, 'new_association': kwargs.get('new_association') or False, 'user': user and user.id or None, 'social': social and { 'provider': social.provider, 'uid': social.uid } or None } kwargs.update(clean_kwargs) # Clean any MergeDict data type from the values new_kwargs = {} for name, value in kwargs.items(): # Check for class name to avoid importing Django MergeDict or # Werkzeug MultiDict if isinstance(value, dict) or \ value.__class__.__name__ in ('MergeDict', 'MultiDict'): value = dict(value) if isinstance(value, SERIALIZABLE_TYPES): new_kwargs[name] = strategy.to_session_value(value) return { 'next': next, 'backend': backend.name, 'args': tuple(map(strategy.to_session_value, args)), 'kwargs': new_kwargs } def partial_from_session(strategy, session): kwargs = session['kwargs'].copy() user = kwargs.get('user') social = kwargs.get('social') if isinstance(social, dict): kwargs['social'] = strategy.storage.user.get_social_auth(**social) if user: kwargs['user'] = strategy.storage.user.get_user(user) return ( session['next'], session['backend'], list(map(strategy.from_session_value, session['args'])), dict((key, strategy.from_session_value(val)) for key, val in kwargs.items()) ) python-social-auth-0.2.21/social/pipeline/user.py0000644000175500017550000000672312754357263021513 0ustar debacledebaclefrom uuid import uuid4 from social.utils import slugify, module_member USER_FIELDS = ['username', 'email'] def get_username(strategy, details, user=None, *args, **kwargs): if 'username' not in strategy.setting('USER_FIELDS', USER_FIELDS): return storage = strategy.storage if not user: email_as_username = strategy.setting('USERNAME_IS_FULL_EMAIL', False) uuid_length = strategy.setting('UUID_LENGTH', 16) max_length = storage.user.username_max_length() do_slugify = strategy.setting('SLUGIFY_USERNAMES', False) do_clean = strategy.setting('CLEAN_USERNAMES', True) if do_clean: clean_func = storage.user.clean_username else: clean_func = lambda val: val if do_slugify: override_slug = strategy.setting('SLUGIFY_FUNCTION') if override_slug: slug_func = module_member(override_slug) else: slug_func = slugify else: slug_func = lambda val: val if email_as_username and details.get('email'): username = details['email'] elif details.get('username'): username = details['username'] else: username = uuid4().hex short_username = (username[:max_length - uuid_length] if max_length is not None else username) final_username = slug_func(clean_func(username[:max_length])) # 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. # The final_username may be empty and will skip the loop. while not final_username or \ storage.user.user_exists(username=final_username): username = short_username + uuid4().hex[:uuid_length] final_username = slug_func(clean_func(username[:max_length])) else: final_username = storage.user.get_username(user) return {'username': final_username} def create_user(strategy, details, user=None, *args, **kwargs): if user: return {'is_new': False} fields = dict((name, kwargs.get(name, details.get(name))) for name in strategy.setting('USER_FIELDS', USER_FIELDS)) if not fields: return return { 'is_new': True, 'user': strategy.create_user(**fields) } def user_details(strategy, details, user=None, *args, **kwargs): """Update user details using data from provider.""" if user: changed = False # flag to track changes protected = ('username', 'id', 'pk', 'email') + \ tuple(strategy.setting('PROTECTED_USER_FIELDS', [])) # Update user model attributes with the new data sent by the current # provider. Update on some attributes is disabled by default, for # example username and id fields. It's also possible to disable update # on fields defined in SOCIAL_AUTH_PROTECTED_FIELDS. for name, value in details.items(): if value and hasattr(user, name): # Check https://github.com/omab/python-social-auth/issues/671 current_value = getattr(user, name, None) if not current_value or name not in protected: changed |= current_value != value setattr(user, name, value) if changed: strategy.storage.user.changed(user) python-social-auth-0.2.21/social/pipeline/__init__.py0000644000175500017550000000452312754357263022270 0ustar debacledebacleDEFAULT_AUTH_PIPELINE = ( # Get the information we can about the user and return it in a simple # format to create the user instance later. On some cases the details are # already part of the auth response from the provider, but sometimes this # could hit a provider API. 'social.pipeline.social_auth.social_details', # Get the social uid from whichever service we're authing thru. The uid is # the unique identifier of the given user in the provider. 'social.pipeline.social_auth.social_uid', # Verifies that the current auth process is valid within the current # project, this is where emails and domains whitelists are applied (if # defined). 'social.pipeline.social_auth.auth_allowed', # Checks if the current social-account is already associated in the site. 'social.pipeline.social_auth.social_user', # Make up a username for this person, appends a random string at the end if # there's any collision. 'social.pipeline.user.get_username', # Send a validation email to the user to verify its email address. # 'social.pipeline.mail.mail_validation', # Associates the current social details with another user account with # a similar email address. # 'social.pipeline.social_auth.associate_by_email', # Create a user account if we haven't found one yet. 'social.pipeline.user.create_user', # Create the record that associated the social account with this user. 'social.pipeline.social_auth.associate_user', # Populate the extra_data field in the social record with the values # specified by settings (and the default ones like access_token, etc). 'social.pipeline.social_auth.load_extra_data', # Update the user record with any changed info from the auth service. 'social.pipeline.user.user_details' ) DEFAULT_DISCONNECT_PIPELINE = ( # Verifies that the social association can be disconnected from the current # user (ensure that the user login mechanism is not compromised by this # disconnection). 'social.pipeline.disconnect.allowed_to_disconnect', # Collects the social associations to disconnect. 'social.pipeline.disconnect.get_entries', # Revoke any access_token when possible. 'social.pipeline.disconnect.revoke_tokens', # Removes the social associations. 'social.pipeline.disconnect.disconnect' ) python-social-auth-0.2.21/social/pipeline/social_auth.py0000644000175500017550000000645312754357263023030 0ustar debacledebaclefrom social.exceptions import AuthAlreadyAssociated, AuthException, \ AuthForbidden def social_details(backend, details, response, *args, **kwargs): return {'details': dict(backend.get_user_details(response), **details)} def social_uid(backend, details, response, *args, **kwargs): return {'uid': backend.get_user_id(details, response)} def auth_allowed(backend, details, response, *args, **kwargs): if not backend.auth_allowed(response, details): raise AuthForbidden(backend) def social_user(backend, uid, user=None, *args, **kwargs): provider = backend.name social = backend.strategy.storage.user.get_social_auth(provider, uid) if social: if user and social.user != user: msg = 'This {0} account is already in use.'.format(provider) raise AuthAlreadyAssociated(backend, msg) elif not user: user = social.user return {'social': social, 'user': user, 'is_new': user is None, 'new_association': social is None} def associate_user(backend, uid, user=None, social=None, *args, **kwargs): if user and not social: try: social = backend.strategy.storage.user.create_social_auth( user, uid, backend.name ) except Exception as err: if not backend.strategy.storage.is_integrity_error(err): 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_user(backend, uid, user, *args, **kwargs) else: return {'social': social, 'user': social.user, 'new_association': True} def associate_by_email(backend, details, user=None, *args, **kwargs): """ Associate current auth with a user with the same email address in the DB. This pipeline entry is not 100% secure unless you know that the providers enabled enforce email verification on their side, otherwise a user can attempt to take over another user account by using the same (not validated) email address on some provider. This pipeline entry is disabled by default. """ 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. users = list(backend.strategy.storage.user.get_users_by_email(email)) if len(users) == 0: return None elif len(users) > 1: raise AuthException( backend, 'The given email address is associated with another account' ) else: return {'user': users[0], 'is_new': False} def load_extra_data(backend, details, response, uid, user, *args, **kwargs): social = kwargs.get('social') or \ backend.strategy.storage.user.get_social_auth(backend.name, uid) if social: extra_data = backend.extra_data(user, uid, response, details, *args, **kwargs) social.set_extra_data(extra_data) python-social-auth-0.2.21/social/actions.py0000644000175500017550000001167012754357263020365 0ustar debacledebaclefrom social.p3 import quote from social.utils import sanitize_redirect, user_is_authenticated, \ user_is_active, partial_pipeline_data, setting_url def do_auth(backend, redirect_name='next'): # Clean any partial pipeline data backend.strategy.clean_partial_pipeline() # Save any defined next value into session data = backend.strategy.request_data(merge=False) # Save extra data into session. for field_name in backend.setting('FIELDS_STORED_IN_SESSION', []): if field_name in data: backend.strategy.session_set(field_name, data[field_name]) if redirect_name in data: # Check and sanitize a user-defined GET/POST next field value redirect_uri = data[redirect_name] if backend.setting('SANITIZE_REDIRECTS', True): allowed_hosts = backend.setting('ALLOWED_REDIRECT_HOSTS', []) + \ [backend.strategy.request_host()] redirect_uri = sanitize_redirect(allowed_hosts, redirect_uri) backend.strategy.session_set( redirect_name, redirect_uri or backend.setting('LOGIN_REDIRECT_URL') ) return backend.start() def do_complete(backend, login, user=None, redirect_name='next', *args, **kwargs): data = backend.strategy.request_data() is_authenticated = user_is_authenticated(user) user = is_authenticated and user or None partial = partial_pipeline_data(backend, user, *args, **kwargs) if partial: xargs, xkwargs = partial user = backend.continue_pipeline(*xargs, **xkwargs) else: user = backend.complete(user=user, *args, **kwargs) # pop redirect value before the session is trashed on login(), but after # the pipeline so that the pipeline can change the redirect if needed redirect_value = backend.strategy.session_get(redirect_name, '') or \ data.get(redirect_name, '') user_model = backend.strategy.storage.user.user_model() if user and not isinstance(user, user_model): return user if is_authenticated: if not user: url = setting_url(backend, redirect_value, 'LOGIN_REDIRECT_URL') else: url = setting_url(backend, redirect_value, 'NEW_ASSOCIATION_REDIRECT_URL', 'LOGIN_REDIRECT_URL') elif user: if user_is_active(user): # catch is_new/social_user in case login() resets the instance is_new = getattr(user, 'is_new', False) social_user = user.social_user login(backend, user, social_user) # store last login backend name in session backend.strategy.session_set('social_auth_last_login_backend', social_user.provider) if is_new: url = setting_url(backend, 'NEW_USER_REDIRECT_URL', redirect_value, 'LOGIN_REDIRECT_URL') else: url = setting_url(backend, redirect_value, 'LOGIN_REDIRECT_URL') else: if backend.setting('INACTIVE_USER_LOGIN', False): social_user = user.social_user login(backend, user, social_user) url = setting_url(backend, 'INACTIVE_USER_URL', 'LOGIN_ERROR_URL', 'LOGIN_URL') else: url = setting_url(backend, 'LOGIN_ERROR_URL', 'LOGIN_URL') if redirect_value and redirect_value != url: redirect_value = quote(redirect_value) url += ('?' in url and '&' or '?') + \ '{0}={1}'.format(redirect_name, redirect_value) if backend.setting('SANITIZE_REDIRECTS', True): allowed_hosts = backend.setting('ALLOWED_REDIRECT_HOSTS', []) + \ [backend.strategy.request_host()] url = sanitize_redirect(allowed_hosts, url) or \ backend.setting('LOGIN_REDIRECT_URL') return backend.strategy.redirect(url) def do_disconnect(backend, user, association_id=None, redirect_name='next', *args, **kwargs): partial = partial_pipeline_data(backend, user, *args, **kwargs) if partial: xargs, xkwargs = partial if association_id and not xkwargs.get('association_id'): xkwargs['association_id'] = association_id response = backend.disconnect(*xargs, **xkwargs) else: response = backend.disconnect(user=user, association_id=association_id, *args, **kwargs) if isinstance(response, dict): response = backend.strategy.redirect( backend.strategy.absolute_uri( backend.strategy.request_data().get(redirect_name, '') or backend.setting('DISCONNECT_REDIRECT_URL') or backend.setting('LOGIN_REDIRECT_URL') ) ) return response python-social-auth-0.2.21/social/utils.py0000644000175500017550000001741712754357263020072 0ustar debacledebacleimport re import sys import unicodedata import collections import functools import logging import six import requests import social from requests.adapters import HTTPAdapter from requests.packages.urllib3.poolmanager import PoolManager from social.exceptions import AuthCanceled, AuthUnreachableProvider from social.p3 import urlparse, urlunparse, urlencode, \ parse_qs as battery_parse_qs SETTING_PREFIX = 'SOCIAL_AUTH' social_logger = logging.getLogger('social') class SSLHttpAdapter(HTTPAdapter): """" Transport adapter that allows to use any SSL protocol. Based on: http://requests.rtfd.org/latest/user/advanced/#example-specific-ssl-version """ def __init__(self, ssl_protocol): self.ssl_protocol = ssl_protocol super(SSLHttpAdapter, self).__init__() def init_poolmanager(self, connections, maxsize, block=False): self.poolmanager = PoolManager( num_pools=connections, maxsize=maxsize, block=block, ssl_version=self.ssl_protocol ) @classmethod def ssl_adapter_session(cls, ssl_protocol): session = requests.Session() session.mount('https://', SSLHttpAdapter(ssl_protocol)) return session def import_module(name): __import__(name) return sys.modules[name] def module_member(name): mod, member = name.rsplit('.', 1) module = import_module(mod) return getattr(module, member) def user_agent(): """Builds a simple User-Agent string to send in requests""" return 'python-social-auth-' + social.__version__ def url_add_parameters(url, params): """Adds parameters to URL, parameter will be repeated if already present""" if params: fragments = list(urlparse(url)) value = parse_qs(fragments[4]) value.update(params) fragments[4] = urlencode(value) url = urlunparse(fragments) return url def to_setting_name(*names): return '_'.join([name.upper().replace('-', '_') for name in names if name]) def setting_name(*names): return to_setting_name(*((SETTING_PREFIX,) + names)) def sanitize_redirect(hosts, redirect_to): """ Given a list of hostnames 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. """ if redirect_to: try: # Don't redirect to a host that's not in the list netloc = urlparse(redirect_to)[1] or hosts[0] except (TypeError, AttributeError): pass else: if netloc in hosts: return redirect_to def user_is_authenticated(user): if user and hasattr(user, 'is_authenticated'): if isinstance(user.is_authenticated, collections.Callable): authenticated = user.is_authenticated() else: authenticated = user.is_authenticated elif user: authenticated = True else: authenticated = False return authenticated def user_is_active(user): if user and hasattr(user, 'is_active'): if isinstance(user.is_active, collections.Callable): is_active = user.is_active() else: is_active = user.is_active elif user: is_active = True else: is_active = False return is_active # This slugify version was borrowed from django revision a61dbd6 def slugify(value): """Converts to lowercase, removes non-word characters (alphanumerics and underscores) and converts spaces to hyphens. Also strips leading and trailing whitespace.""" value = unicodedata.normalize('NFKD', value) \ .encode('ascii', 'ignore') \ .decode('ascii') value = re.sub('[^\w\s-]', '', value).strip().lower() return re.sub('[-\s]+', '-', value) def first(func, items): """Return the first item in the list for what func returns True""" for item in items: if func(item): return item def parse_qs(value): """Like urlparse.parse_qs but transform list values to single items""" return drop_lists(battery_parse_qs(value)) def drop_lists(value): out = {} for key, val in value.items(): val = val[0] if isinstance(key, six.binary_type): key = six.text_type(key, 'utf-8') if isinstance(val, six.binary_type): val = six.text_type(val, 'utf-8') out[key] = val return out def partial_pipeline_data(backend, user=None, *args, **kwargs): partial = backend.strategy.session_get('partial_pipeline', None) if partial: idx, backend_name, xargs, xkwargs = \ backend.strategy.partial_from_session(partial) partial_matches_request = False if backend_name == backend.name: partial_matches_request = True req_data = backend.strategy.request_data() # Normally when resuming a pipeline, request_data will be empty. We # only need to check for a uid match if new data was provided (i.e. # if current request specifies the ID_KEY). if backend.ID_KEY in req_data: id_from_partial = xkwargs.get('uid') id_from_request = req_data.get(backend.ID_KEY) if id_from_partial != id_from_request: partial_matches_request = False if partial_matches_request: kwargs.setdefault('pipeline_index', idx) if user: # don't update user if it's None kwargs.setdefault('user', user) kwargs.setdefault('request', backend.strategy.request_data()) xkwargs.update(kwargs) return xargs, xkwargs else: backend.strategy.clean_partial_pipeline() def build_absolute_uri(host_url, path=None): """Build absolute URI with given (optional) path""" path = path or '' if path.startswith('http://') or path.startswith('https://'): return path if host_url.endswith('/') and path.startswith('/'): path = path[1:] return host_url + path def constant_time_compare(val1, val2): """ Returns True if the two strings are equal, False otherwise. The time taken is independent of the number of characters that match. This code was borrowed from Django 1.5.4-final """ if len(val1) != len(val2): return False result = 0 if six.PY3 and isinstance(val1, bytes) and isinstance(val2, bytes): for x, y in zip(val1, val2): result |= x ^ y else: for x, y in zip(val1, val2): result |= ord(x) ^ ord(y) return result == 0 def is_url(value): return value and \ (value.startswith('http://') or value.startswith('https://') or value.startswith('/')) def setting_url(backend, *names): for name in names: if is_url(name): return name else: value = backend.setting(name) if is_url(value): return value def handle_http_errors(func): @functools.wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except requests.HTTPError as err: if err.response.status_code == 400: raise AuthCanceled(args[0], response=err.response) elif err.response.status_code == 503: raise AuthUnreachableProvider(args[0]) else: raise return wrapper def append_slash(url): """Make sure we append a slash at the end of the URL otherwise we have issues with urljoin Example: >>> urlparse.urljoin('http://www.example.com/api/v3', 'user/1/') 'http://www.example.com/api/user/1/' """ if url and not url.endswith('/'): url = '{0}/'.format(url) return url python-social-auth-0.2.21/social/apps/0000755000175500017550000000000012754357263017311 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/cherrypy_app/0000755000175500017550000000000012754357263022016 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/cherrypy_app/models.py0000644000175500017550000000334212754357263023655 0ustar debacledebacle"""Flask SQLAlchemy ORM models for Social Auth""" import cherrypy from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ BaseSQLAlchemyStorage SocialBase = declarative_base() DB_SESSION_ATTR = cherrypy.config.get(setting_name('DB_SESSION_ATTR'), 'db') UID_LENGTH = cherrypy.config.get(setting_name('UID_LENGTH'), 255) User = module_member(cherrypy.config[setting_name('USER_MODEL')]) class CherryPySocialBase(object): @classmethod def _session(cls): return getattr(cherrypy.request, DB_SESSION_ATTR) class UserSocialAuth(CherryPySocialBase, SQLAlchemyUserMixin, SocialBase): """Social Auth association model""" uid = Column(String(UID_LENGTH)) user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) user = relationship(User, backref='social_auth') @classmethod def username_max_length(cls): return User.__table__.columns.get('username').type.length @classmethod def user_model(cls): return User class Nonce(CherryPySocialBase, SQLAlchemyNonceMixin, SocialBase): """One use numbers""" pass class Association(CherryPySocialBase, SQLAlchemyAssociationMixin, SocialBase): """OpenId account association""" pass class CherryPyStorage(BaseSQLAlchemyStorage): user = UserSocialAuth nonce = Nonce association = Association python-social-auth-0.2.21/social/apps/cherrypy_app/utils.py0000644000175500017550000000327112754357263023533 0ustar debacledebacleimport warnings from functools import wraps import cherrypy from social.utils import setting_name, module_member from social.strategies.utils import get_strategy from social.backends.utils import get_backend, user_backends_data DEFAULTS = { 'STRATEGY': 'social.strategies.cherrypy_strategy.CherryPyStrategy', 'STORAGE': 'social.apps.cherrypy_app.models.CherryPyStorage' } def get_helper(name): return cherrypy.config.get(setting_name(name), DEFAULTS.get(name, None)) def load_backend(strategy, name, redirect_uri): backends = get_helper('AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy=strategy, redirect_uri=redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(self, backend=None, *args, **kwargs): uri = redirect_uri if uri and backend and '%(backend)s' in uri: uri = uri % {'backend': backend} self.strategy = get_strategy(get_helper('STRATEGY'), get_helper('STORAGE')) self.backend = load_backend(self.strategy, backend, uri) return func(self, backend, *args, **kwargs) return wrapper return decorator def backends(user): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" return user_backends_data(user, get_helper('AUTHENTICATION_BACKENDS'), module_member(get_helper('STORAGE'))) def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.21/social/apps/cherrypy_app/__init__.py0000644000175500017550000000000012754357263024115 0ustar debacledebaclepython-social-auth-0.2.21/social/apps/cherrypy_app/views.py0000644000175500017550000000200512754357263023522 0ustar debacledebacleimport cherrypy from social.utils import setting_name, module_member from social.actions import do_auth, do_complete, do_disconnect from social.apps.cherrypy_app.utils import psa class CherryPyPSAViews(object): @cherrypy.expose @psa('/complete/%(backend)s') def login(self, backend): return do_auth(self.backend) @cherrypy.expose @psa('/complete/%(backend)s') def complete(self, backend, *args, **kwargs): login = cherrypy.config.get(setting_name('LOGIN_METHOD')) do_login = module_member(login) if login else self.do_login user = getattr(cherrypy.request, 'user', None) return do_complete(self.backend, do_login, user=user, *args, **kwargs) @cherrypy.expose @psa() def disconnect(self, backend, association_id=None): user = getattr(cherrypy.request, 'user', None) return do_disconnect(self.backend, user, association_id) def do_login(self, backend, user, social_user): backend.strategy.session_set('user_id', user.id) python-social-auth-0.2.21/social/apps/__init__.py0000644000175500017550000000000012754357263021410 0ustar debacledebaclepython-social-auth-0.2.21/social/apps/webpy_app/0000755000175500017550000000000012754357263021277 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/webpy_app/app.py0000644000175500017550000000415112754357263022432 0ustar debacledebacleimport web from social.actions import do_auth, do_complete, do_disconnect from social.apps.webpy_app.utils import psa, load_strategy urls = ( '/login/(?P[^/]+)/?', 'auth', '/complete/(?P[^/]+)/?', 'complete', '/disconnect/(?P[^/]+)/?', 'disconnect', '/disconnect/(?P[^/]+)/(?P\d+)/?', 'disconnect', ) class BaseViewClass(object): def __init__(self, *args, **kwargs): self.session = web.web_session method = web.ctx.method == 'POST' and 'post' or 'get' self.strategy = load_strategy() self.data = web.input(_method=method) super(BaseViewClass, self).__init__(*args, **kwargs) def get_current_user(self): if not hasattr(self, '_user'): if self.session.get('logged_in'): self._user = self.strategy.get_user( self.session.get('user_id') ) else: self._user = None return self._user def login_user(self, user): self.session['logged_in'] = True self.session['user_id'] = user.id class auth(BaseViewClass): def GET(self, backend): return self._auth(backend) def POST(self, backend): return self._auth(backend) @psa('/complete/%(backend)s/') def _auth(self, backend): return do_auth(self.backend) class complete(BaseViewClass): def GET(self, backend, *args, **kwargs): return self._complete(backend, *args, **kwargs) def POST(self, backend, *args, **kwargs): return self._complete(backend, *args, **kwargs) @psa('/complete/%(backend)s/') def _complete(self, backend, *args, **kwargs): return do_complete( self.backend, login=lambda backend, user, social_user: self.login_user(user), user=self.get_current_user(), *args, **kwargs ) class disconnect(BaseViewClass): @psa() def POST(self, backend, association_id=None): return do_disconnect(self.backend, self.get_current_user(), association_id) app_social = web.application(urls, locals()) python-social-auth-0.2.21/social/apps/webpy_app/models.py0000644000175500017550000000336512754357263023143 0ustar debacledebacle"""Flask SQLAlchemy ORM models for Social Auth""" import web from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ SQLAlchemyCodeMixin, \ BaseSQLAlchemyStorage SocialBase = declarative_base() UID_LENGTH = web.config.get(setting_name('UID_LENGTH'), 255) User = module_member(web.config[setting_name('USER_MODEL')]) class WebpySocialBase(object): @classmethod def _session(cls): return web.db_session class UserSocialAuth(WebpySocialBase, SQLAlchemyUserMixin, SocialBase): """Social Auth association model""" uid = Column(String(UID_LENGTH)) user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) user = relationship(User, backref='social_auth') @classmethod def username_max_length(cls): return User.__table__.columns.get('username').type.length @classmethod def user_model(cls): return User class Nonce(WebpySocialBase, SQLAlchemyNonceMixin, SocialBase): """One use numbers""" pass class Association(WebpySocialBase, SQLAlchemyAssociationMixin, SocialBase): """OpenId account association""" pass class Code(WebpySocialBase, SQLAlchemyCodeMixin, SocialBase): pass class WebpyStorage(BaseSQLAlchemyStorage): user = UserSocialAuth nonce = Nonce association = Association code = Code python-social-auth-0.2.21/social/apps/webpy_app/utils.py0000644000175500017550000000413612754357263023015 0ustar debacledebacleimport warnings from functools import wraps import web from social.utils import setting_name, module_member from social.backends.utils import get_backend, user_backends_data from social.strategies.utils import get_strategy DEFAULTS = { 'STRATEGY': 'social.strategies.webpy_strategy.WebpyStrategy', 'STORAGE': 'social.apps.webpy_app.models.WebpyStorage' } def get_helper(name, do_import=False): config = web.config.get(setting_name(name), DEFAULTS.get(name, None)) return do_import and module_member(config) or config def load_strategy(): return get_strategy(get_helper('STRATEGY'), get_helper('STORAGE')) def load_backend(strategy, name, redirect_uri): backends = get_helper('AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy, redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(self, backend, *args, **kwargs): uri = redirect_uri if uri and backend and '%(backend)s' in uri: uri = uri % {'backend': backend} self.strategy = load_strategy() self.backend = load_backend(self.strategy, backend, uri) return func(self, backend, *args, **kwargs) return wrapper return decorator def backends(user): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" return user_backends_data(user, get_helper('AUTHENTICATION_BACKENDS'), get_helper('STORAGE', do_import=True)) def login_redirect(): """Load current redirect to context.""" method = web.ctx.method == 'POST' and 'post' or 'get' data = web.input(_method=method) value = data.get('next') return { 'REDIRECT_FIELD_NAME': 'next', 'REDIRECT_FIELD_VALUE': value, 'REDIRECT_QUERYSTRING': value and ('next=' + value) or '' } def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.21/social/apps/webpy_app/__init__.py0000644000175500017550000000024312754357263023407 0ustar debacledebaclefrom social.strategies.utils import set_current_strategy_getter from social.apps.webpy_app.utils import load_strategy set_current_strategy_getter(load_strategy) python-social-auth-0.2.21/social/apps/django_app/0000755000175500017550000000000012754357263021413 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/django_app/urls.py0000644000175500017550000000153012754357263022751 0ustar debacledebacle"""URLs module""" from django.conf import settings try: from django.conf.urls import url except ImportError: # Django < 1.4 from django.conf.urls.defaults import url from social.utils import setting_name from social.apps.django_app import views extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or '' urlpatterns = [ # authentication / association url(r'^login/(?P[^/]+){0}$'.format(extra), views.auth, name='begin'), url(r'^complete/(?P[^/]+){0}$'.format(extra), views.complete, name='complete'), # disconnection url(r'^disconnect/(?P[^/]+){0}$'.format(extra), views.disconnect, name='disconnect'), url(r'^disconnect/(?P[^/]+)/(?P[^/]+){0}$' .format(extra), views.disconnect, name='disconnect_individual'), ] python-social-auth-0.2.21/social/apps/django_app/utils.py0000644000175500017550000000454212754357263023132 0ustar debacledebacleimport warnings from functools import wraps from django.conf import settings from django.core.urlresolvers import reverse from django.http import Http404 from social.utils import setting_name, module_member from social.exceptions import MissingBackend from social.strategies.utils import get_strategy from social.backends.utils import get_backend BACKENDS = settings.AUTHENTICATION_BACKENDS STRATEGY = getattr(settings, setting_name('STRATEGY'), 'social.strategies.django_strategy.DjangoStrategy') STORAGE = getattr(settings, setting_name('STORAGE'), 'social.apps.django_app.default.models.DjangoStorage') Strategy = module_member(STRATEGY) Storage = module_member(STORAGE) def load_strategy(request=None): return get_strategy(STRATEGY, STORAGE, request) def load_backend(strategy, name, redirect_uri): Backend = get_backend(BACKENDS, name) return Backend(strategy, redirect_uri) def psa(redirect_uri=None, load_strategy=load_strategy): def decorator(func): @wraps(func) def wrapper(request, backend, *args, **kwargs): uri = redirect_uri if uri and not uri.startswith('/'): uri = reverse(redirect_uri, args=(backend,)) request.social_strategy = load_strategy(request) # backward compatibility in attribute name, only if not already # defined if not hasattr(request, 'strategy'): request.strategy = request.social_strategy try: request.backend = load_backend(request.social_strategy, backend, uri) except MissingBackend: raise Http404('Backend not found') return func(request, backend, *args, **kwargs) return wrapper return decorator def setting(name, default=None): try: return getattr(settings, setting_name(name)) except AttributeError: return getattr(settings, name, default) class BackendWrapper(object): # XXX: Deprecated, restored to avoid session issues def authenticate(self, *args, **kwargs): return None def get_user(self, user_id): return Strategy(storage=Storage).get_user(user_id) def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.21/social/apps/django_app/__init__.py0000644000175500017550000000157112754357263023530 0ustar debacledebacle""" Django framework support. To use this: * Add 'social.apps.django_app.default' if using default ORM, or 'social.apps.django_app.me' if using mongoengine * Add url('', include('social.apps.django_app.urls', namespace='social')) to urls.py * Define SOCIAL_AUTH_STORAGE and SOCIAL_AUTH_STRATEGY, default values: SOCIAL_AUTH_STRATEGY = 'social.strategies.django_strategy.DjangoStrategy' SOCIAL_AUTH_STORAGE = 'social.apps.django_app.default.models.DjangoStorage' """ import django if django.VERSION[0] == 1 and django.VERSION[1] < 7: from social.strategies.utils import set_current_strategy_getter from social.apps.django_app.utils import load_strategy # Set strategy loader method to workaround current strategy getter needed on # get_user() method on authentication backends when working with Django set_current_strategy_getter(load_strategy) python-social-auth-0.2.21/social/apps/django_app/default/0000755000175500017550000000000012754357263023037 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/django_app/default/managers.py0000644000175500017550000000060112754357263025203 0ustar debacledebaclefrom django.db import models class UserSocialAuthManager(models.Manager): """Manager for the UserSocialAuth django model.""" def get_social_auth(self, provider, uid): try: return self.select_related('user').get(provider=provider, uid=uid) except self.model.DoesNotExist: return None python-social-auth-0.2.21/social/apps/django_app/default/migrations/0000755000175500017550000000000012754357263025213 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/django_app/default/migrations/0002_add_related_name.py0000644000175500017550000000131012754357263031451 0ustar debacledebacle# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings from social.utils import setting_name USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' class Migration(migrations.Migration): replaces = [('default', '0002_add_related_name')] dependencies = [ ('social_auth', '0001_initial'), ] operations = [ migrations.AlterField( model_name='usersocialauth', name='user', field=models.ForeignKey(related_name='social_auth', to=USER_MODEL) ), ] python-social-auth-0.2.21/social/apps/django_app/default/migrations/__init__.py0000644000175500017550000000000012754357263027312 0ustar debacledebaclepython-social-auth-0.2.21/social/apps/django_app/default/migrations/0004_auto_20160423_0400.py0000644000175500017550000000104712754357263030626 0ustar debacledebacle# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models import social.apps.django_app.default.fields class Migration(migrations.Migration): replaces = [('default', '0004_auto_20160423_0400')] dependencies = [ ('social_auth', '0003_alter_email_max_length'), ] operations = [ migrations.AlterField( model_name='usersocialauth', name='extra_data', field=social.apps.django_app.default.fields.JSONField(default={}), ), ] python-social-auth-0.2.21/social/apps/django_app/default/migrations/0001_initial.py0000644000175500017550000001042212754357263027655 0ustar debacledebacle# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations import social.apps.django_app.default.fields from django.conf import settings import social.storage.django_orm from social.utils import setting_name USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' UID_LENGTH = getattr(settings, setting_name('UID_LENGTH'), 255) NONCE_SERVER_URL_LENGTH = getattr( settings, setting_name('NONCE_SERVER_URL_LENGTH'), 255 ) ASSOCIATION_SERVER_URL_LENGTH = getattr( settings, setting_name('ASSOCIATION_SERVER_URL_LENGTH'), 255 ) ASSOCIATION_HANDLE_LENGTH = getattr( settings, setting_name('ASSOCIATION_HANDLE_LENGTH'), 255 ) class Migration(migrations.Migration): replaces = [('default', '0001_initial')] dependencies = [ migrations.swappable_dependency(USER_MODEL), ] operations = [ migrations.CreateModel( name='Association', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('server_url', models.CharField(max_length=ASSOCIATION_SERVER_URL_LENGTH)), ('handle', models.CharField(max_length=ASSOCIATION_HANDLE_LENGTH)), ('secret', models.CharField(max_length=255)), ('issued', models.IntegerField()), ('lifetime', models.IntegerField()), ('assoc_type', models.CharField(max_length=64)), ], options={ 'db_table': 'social_auth_association', }, bases=( models.Model, social.storage.django_orm.DjangoAssociationMixin ), ), migrations.CreateModel( name='Code', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('email', models.EmailField(max_length=75)), ('code', models.CharField(max_length=32, db_index=True)), ('verified', models.BooleanField(default=False)), ], options={ 'db_table': 'social_auth_code', }, bases=(models.Model, social.storage.django_orm.DjangoCodeMixin), ), migrations.CreateModel( name='Nonce', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True )), ('server_url', models.CharField(max_length=NONCE_SERVER_URL_LENGTH)), ('timestamp', models.IntegerField()), ('salt', models.CharField(max_length=65)), ], options={ 'db_table': 'social_auth_nonce', }, bases=(models.Model, social.storage.django_orm.DjangoNonceMixin), ), migrations.CreateModel( name='UserSocialAuth', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('provider', models.CharField(max_length=32)), ('uid', models.CharField(max_length=UID_LENGTH)), ('extra_data', social.apps.django_app.default.fields.JSONField( default='{}')), ('user', models.ForeignKey( related_name='social_auth', to=USER_MODEL)), ], options={ 'db_table': 'social_auth_usersocialauth', }, bases=(models.Model, social.storage.django_orm.DjangoUserMixin), ), migrations.AlterUniqueTogether( name='usersocialauth', unique_together={('provider', 'uid')}, ), migrations.AlterUniqueTogether( name='code', unique_together={('email', 'code')}, ), migrations.AlterUniqueTogether( name='nonce', unique_together={('server_url', 'timestamp', 'salt')}, ), ] python-social-auth-0.2.21/social/apps/django_app/default/migrations/0003_alter_email_max_length.py0000644000175500017550000000114512754357263032714 0ustar debacledebacle# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.conf import settings from django.db import models, migrations from social.utils import setting_name EMAIL_LENGTH = getattr(settings, setting_name('EMAIL_LENGTH'), 254) class Migration(migrations.Migration): replaces = [('default', '0003_alter_email_max_length')] dependencies = [ ('social_auth', '0002_add_related_name'), ] operations = [ migrations.AlterField( model_name='code', name='email', field=models.EmailField(max_length=EMAIL_LENGTH), ), ] python-social-auth-0.2.21/social/apps/django_app/default/migrations/0005_auto_20160727_2333.py0000644000175500017550000000066512754357263030652 0ustar debacledebacle# -*- coding: utf-8 -*- # Generated by Django 1.9.5 on 2016-07-28 02:33 from __future__ import unicode_literals from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('social_auth', '0004_auto_20160423_0400'), ] operations = [ migrations.AlterUniqueTogether( name='association', unique_together=set([('server_url', 'handle')]), ), ] python-social-auth-0.2.21/social/apps/django_app/default/models.py0000644000175500017550000001005612754357263024676 0ustar debacledebacle"""Django ORM models for Social Auth""" import six from django.db import models from django.conf import settings from django.db.utils import IntegrityError from social.utils import setting_name from social.storage.django_orm import DjangoUserMixin, \ DjangoAssociationMixin, \ DjangoNonceMixin, \ DjangoCodeMixin, \ BaseDjangoStorage from social.apps.django_app.default.fields import JSONField from social.apps.django_app.default.managers import UserSocialAuthManager USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' UID_LENGTH = getattr(settings, setting_name('UID_LENGTH'), 255) EMAIL_LENGTH = getattr(settings, setting_name('EMAIL_LENGTH'), 254) NONCE_SERVER_URL_LENGTH = getattr( settings, setting_name('NONCE_SERVER_URL_LENGTH'), 255) ASSOCIATION_SERVER_URL_LENGTH = getattr( settings, setting_name('ASSOCIATION_SERVER_URL_LENGTH'), 255) ASSOCIATION_HANDLE_LENGTH = getattr( settings, setting_name('ASSOCIATION_HANDLE_LENGTH'), 255) class AbstractUserSocialAuth(models.Model, DjangoUserMixin): """Abstract 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() objects = UserSocialAuthManager() def __str__(self): return str(self.user) class Meta: abstract = True @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): username_field = cls.username_field() field = UserSocialAuth.user_model()._meta.get_field(username_field) return field.max_length @classmethod def user_model(cls): user_model = UserSocialAuth._meta.get_field('user').rel.to if isinstance(user_model, six.string_types): app_label, model_name = user_model.split('.') return models.get_model(app_label, model_name) return user_model class UserSocialAuth(AbstractUserSocialAuth): """Social Auth association model""" class Meta: """Meta data""" unique_together = ('provider', 'uid') db_table = 'social_auth_usersocialauth' class Nonce(models.Model, DjangoNonceMixin): """One use numbers""" server_url = models.CharField(max_length=NONCE_SERVER_URL_LENGTH) timestamp = models.IntegerField() salt = models.CharField(max_length=65) class Meta: unique_together = ('server_url', 'timestamp', 'salt') db_table = 'social_auth_nonce' class Association(models.Model, DjangoAssociationMixin): """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() lifetime = models.IntegerField() assoc_type = models.CharField(max_length=64) class Meta: db_table = 'social_auth_association' unique_together = ( ('server_url', 'handle',) ) class Code(models.Model, DjangoCodeMixin): email = models.EmailField(max_length=EMAIL_LENGTH) code = models.CharField(max_length=32, db_index=True) verified = models.BooleanField(default=False) class Meta: db_table = 'social_auth_code' unique_together = ('email', 'code') class DjangoStorage(BaseDjangoStorage): user = UserSocialAuth nonce = Nonce association = Association code = Code @classmethod def is_integrity_error(cls, exception): return exception.__class__ is IntegrityError python-social-auth-0.2.21/social/apps/django_app/default/config.py0000644000175500017550000000107212754357263024656 0ustar debacledebaclefrom django.apps import AppConfig class PythonSocialAuthConfig(AppConfig): name = 'social.apps.django_app.default' label = 'social_auth' verbose_name = 'Python Social Auth' def ready(self): from social.strategies.utils import set_current_strategy_getter from social.apps.django_app.utils import load_strategy # Set strategy loader method to workaround current strategy getter # needed on get_user() method on authentication backends when working # with Django set_current_strategy_getter(load_strategy) python-social-auth-0.2.21/social/apps/django_app/default/south_migrations/0000755000175500017550000000000012754357263026435 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/django_app/default/south_migrations/__init__.py0000644000175500017550000000257212754357263030554 0ustar debacledebaclefrom django.conf import settings from django.db.models.loading import get_model def get_custom_user_model_for_migrations(): user_model = getattr(settings, 'SOCIAL_AUTH_USER_MODEL', None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' if user_model != 'auth.User': # In case of having a proxy model defined as USER_MODEL # We use auth.User instead to prevent migration errors # Since proxy models aren't present in migrations if get_model(*user_model.split('.'))._meta.proxy: user_model = 'auth.User' return user_model def custom_user_frozen_models(user_model): migration_name = getattr(settings, 'INITIAL_CUSTOM_USER_MIGRATION', '0001_initial.py') if user_model != 'auth.User': from south.migration.base import Migrations from south.exceptions import NoMigrations from south.creator.freezer import freeze_apps user_app, user_model = user_model.split('.') try: user_migrations = Migrations(user_app) except NoMigrations: extra_model = freeze_apps(user_app) else: initial_user_migration = user_migrations.migration(migration_name) extra_model = initial_user_migration.migration_class().models else: extra_model = {} return extra_model python-social-auth-0.2.21/social/apps/django_app/default/south_migrations/0001_initial.py0000644000175500017550000002537212754357263031111 0ustar debacledebacle# -*- coding: utf-8 -*- from south.db import db from south.v2 import SchemaMigration from . import get_custom_user_model_for_migrations, custom_user_frozen_models USER_MODEL = get_custom_user_model_for_migrations() class Migration(SchemaMigration): def forwards(self, orm): # Adding model 'UserSocialAuth' db.create_table('social_auth_usersocialauth', ( (u'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=255)), ('extra_data', self.gf( 'social.apps.django_app.default.fields.JSONField' )(default='{}')), )) db.send_create_signal(u'default', ['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', ( (u'id', self.gf('django.db.models.fields.AutoField')( primary_key=True)), ('server_url', self.gf('django.db.models.fields.CharField')( max_length=255)), ('timestamp', self.gf('django.db.models.fields.IntegerField')()), ('salt', self.gf('django.db.models.fields.CharField')( max_length=65)), )) db.send_create_signal(u'default', ['Nonce']) # Adding model 'Association' db.create_table('social_auth_association', ( (u'id', self.gf('django.db.models.fields.AutoField')( primary_key=True)), ('server_url', self.gf('django.db.models.fields.CharField')( max_length=255)), ('handle', self.gf('django.db.models.fields.CharField')( max_length=255)), ('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(u'default', ['Association']) # Adding model 'Code' db.create_table('social_auth_code', ( (u'id', self.gf('django.db.models.fields.AutoField')( primary_key=True)), ('email', self.gf('django.db.models.fields.EmailField')( max_length=75)), ('code', self.gf('django.db.models.fields.CharField')( max_length=32, db_index=True)), ('verified', self.gf('django.db.models.fields.BooleanField')( default=False)), )) db.send_create_signal(u'default', ['Code']) # Adding unique constraint on 'Code', fields ['email', 'code'] db.create_unique('social_auth_code', ['email', 'code']) def backwards(self, orm): # Removing unique constraint on 'Code', fields ['email', 'code'] db.delete_unique('social_auth_code', ['email', 'code']) # 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') # Deleting model 'Code' db.delete_table('social_auth_code') models = { u'auth.group': { 'Meta': {'object_name': 'Group'}, u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, u'auth.permission': { 'Meta': { 'ordering': "(u'content_type__app_label', " "u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission' }, 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, u'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'}), u'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'}) }, u'default.association': { 'Meta': {'object_name': 'Association', 'db_table': "'social_auth_association'"}, 'assoc_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}), u'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': '255'}) }, u'default.code': { 'Meta': {'unique_together': "(('email', 'code'),)", 'object_name': 'Code', 'db_table': "'social_auth_code'"}, 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) }, u'default.nonce': { 'Meta': {'object_name': 'Nonce', 'db_table': "'social_auth_nonce'"}, u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'salt': ('django.db.models.fields.CharField', [], {'max_length': '65'}), 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'timestamp': ('django.db.models.fields.IntegerField', [], {}) }, u'default.usersocialauth': { 'Meta': {'unique_together': "(('provider', 'uid'),)", 'object_name': 'UserSocialAuth', 'db_table': "'social_auth_usersocialauth'"}, 'extra_data': ('social.apps.django_app.default.fields.JSONField', [], {'default': "'{}'"}), u'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': '255'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'social_auth'", 'to': u"orm['auth.User']"}) } } models.update(custom_user_frozen_models(USER_MODEL)) complete_apps = ['default'] python-social-auth-0.2.21/social/apps/django_app/default/__init__.py0000644000175500017550000000044212754357263025150 0ustar debacledebacle""" Django default ORM backend support. To enable this app: * Add 'social.apps.django_app.default' to INSTALLED_APPS * In urls.py include url('', include('social.apps.django_app.urls')) """ default_app_config = \ 'social.apps.django_app.default.config.PythonSocialAuthConfig' python-social-auth-0.2.21/social/apps/django_app/default/admin.py0000644000175500017550000000427112754357263024505 0ustar debacledebacle"""Admin settings""" from itertools import chain from django.conf import settings from django.contrib import admin from social.utils import setting_name from social.apps.django_app.default.models import UserSocialAuth, Nonce, \ Association class UserSocialAuthOption(admin.ModelAdmin): """Social Auth user options""" list_display = ('user', 'id', 'provider', 'uid') list_filter = ('provider',) raw_id_fields = ('user',) list_select_related = True def get_search_fields(self, request=None): search_fields = getattr( settings, setting_name('ADMIN_USER_SEARCH_FIELDS'), None ) if search_fields is None: _User = UserSocialAuth.user_model() username = getattr(_User, 'USERNAME_FIELD', None) or \ hasattr(_User, 'username') and 'username' or \ None fieldnames = ('first_name', 'last_name', 'email', username) all_names = self._get_all_field_names(_User._meta) search_fields = [name for name in fieldnames if name and name in all_names] return ['user__' + name for name in search_fields] @staticmethod def _get_all_field_names(model): names = chain.from_iterable( (field.name, field.attname) if hasattr(field, 'attname') else (field.name,) for field in model.get_fields() # For complete backwards compatibility, you may want to exclude # GenericForeignKey from the results. if not (field.many_to_one and field.related_model is None) ) return list(set(names)) 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) python-social-auth-0.2.21/social/apps/django_app/default/fields.py0000644000175500017550000000537312754357263024667 0ustar debacledebacleimport json import six import functools from django.core.exceptions import ValidationError from django.db import models try: from django.utils.encoding import smart_unicode as smart_text smart_text # placate pyflakes except ImportError: from django.utils.encoding import smart_text try: from django.db.models import SubfieldBase field_class = functools.partial(six.with_metaclass, SubfieldBase) except ImportError: field_class = functools.partial(six.with_metaclass, type) class JSONField(field_class(models.TextField)): """Simple JSON field that stores python structures as JSON strings on database. """ def __init__(self, *args, **kwargs): kwargs.setdefault('default', {}) super(JSONField, self).__init__(*args, **kwargs) def from_db_value(self, value, expression, connection, context): return self.to_python(value) 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 {} value = value or '{}' if isinstance(value, six.binary_type): value = six.text_type(value, 'utf-8') if isinstance(value, six.string_types): try: # with django 1.6 i have '"{}"' as default value here if value[0] == value[-1] == '"': value = value[1:-1] return json.loads(value) except Exception as err: raise ValidationError(str(err)) else: return value def validate(self, value, model_instance): """Check value is a valid JSON string, raise ValidationError on error.""" if isinstance(value, six.string_types): super(JSONField, self).validate(value, model_instance) try: json.loads(value) except Exception as err: raise ValidationError(str(err)) def get_prep_value(self, value): """Convert value to JSON string before save""" try: return json.dumps(value) except Exception as err: raise ValidationError(str(err)) def value_to_string(self, obj): """Return value from object converted to string properly""" return smart_text(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\.apps\.django_app\.default\.fields\.JSONField"] ) except: pass python-social-auth-0.2.21/social/apps/django_app/default/tests.py0000644000175500017550000000005312754357263024551 0ustar debacledebaclefrom social.apps.django_app.tests import * python-social-auth-0.2.21/social/apps/django_app/me/0000755000175500017550000000000012754357263022014 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/django_app/me/models.py0000644000175500017550000000411412754357263023651 0ustar debacledebacle""" MongoEngine Django models for Social Auth. Requires MongoEngine 0.8.6 or higher. """ from django.conf import settings from mongoengine import Document, ReferenceField from mongoengine.queryset import OperationError from social.utils import setting_name, module_member from social.storage.django_orm import BaseDjangoStorage from social.storage.mongoengine_orm import MongoengineUserMixin, \ MongoengineNonceMixin, \ MongoengineAssociationMixin, \ MongoengineCodeMixin UNUSABLE_PASSWORD = '!' # Borrowed from django 1.4 def _get_user_model(): """ Get the User Document class user for MongoEngine authentication. Use the model defined in SOCIAL_AUTH_USER_MODEL if defined, or defaults to MongoEngine's configured user document class. """ custom_model = getattr(settings, setting_name('USER_MODEL'), None) if custom_model: return module_member(custom_model) try: # Custom user model support with MongoEngine 0.8 from mongoengine.django.mongo_auth.models import get_user_document return get_user_document() except ImportError: return module_member('mongoengine.django.auth.User') USER_MODEL = _get_user_model() class UserSocialAuth(Document, MongoengineUserMixin): """Social Auth association model""" user = ReferenceField(USER_MODEL) @classmethod def user_model(cls): return USER_MODEL class Nonce(Document, MongoengineNonceMixin): """One use numbers""" pass class Association(Document, MongoengineAssociationMixin): """OpenId account association""" pass class Code(Document, MongoengineCodeMixin): """Mail validation single one time use code""" pass class DjangoStorage(BaseDjangoStorage): user = UserSocialAuth nonce = Nonce association = Association code = Code @classmethod def is_integrity_error(cls, exception): return exception.__class__ is OperationError and \ 'E11000' in exception.message python-social-auth-0.2.21/social/apps/django_app/me/config.py0000644000175500017550000000103312754357263023630 0ustar debacledebaclefrom django.apps import AppConfig class PythonSocialAuthConfig(AppConfig): name = 'social.apps.django_app.me' verbose_name = 'Python Social Auth' def ready(self): from social.strategies.utils import set_current_strategy_getter from social.apps.django_app.utils import load_strategy # Set strategy loader method to workaround current strategy getter # needed on get_user() method on authentication backends when working # with Django set_current_strategy_getter(load_strategy) python-social-auth-0.2.21/social/apps/django_app/me/__init__.py0000644000175500017550000000042012754357263024121 0ustar debacledebacle""" Mongoengine backend support. To enable this app: * Add 'social.apps.django_app.me' to INSTALLED_APPS * In urls.py include url('', include('social.apps.django_app.urls')) """ default_app_config = \ 'social.apps.django_app.me.config.PythonSocialAuthConfig' python-social-auth-0.2.21/social/apps/django_app/me/tests.py0000644000175500017550000000005312754357263023526 0ustar debacledebaclefrom social.apps.django_app.tests import * python-social-auth-0.2.21/social/apps/django_app/middleware.py0000644000175500017550000000435512754357263024111 0ustar debacledebacle# -*- coding: utf-8 -*- import six from django.conf import settings from django.contrib import messages from django.contrib.messages.api import MessageFailure from django.shortcuts import redirect from django.utils.http import urlquote from social.exceptions import SocialAuthBaseException from social.utils import social_logger 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 SOCIAL_AUTH_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): strategy = getattr(request, 'social_strategy', None) if strategy is None or self.raise_exception(request, exception): return if isinstance(exception, SocialAuthBaseException): backend = getattr(request, 'backend', None) backend_name = getattr(backend, 'name', 'unknown-backend') message = self.get_message(request, exception) social_logger.error(message) url = self.get_redirect_uri(request, exception) try: messages.error(request, message, extra_tags='social-auth ' + backend_name) except MessageFailure: url += ('?' in url and '&' or '?') + \ 'message={0}&backend={1}'.format(urlquote(message), backend_name) return redirect(url) def raise_exception(self, request, exception): strategy = getattr(request, 'social_strategy', None) if strategy is not None: return strategy.setting('RAISE_EXCEPTIONS', settings.DEBUG) def get_message(self, request, exception): return six.text_type(exception) def get_redirect_uri(self, request, exception): strategy = getattr(request, 'social_strategy', None) return strategy.setting('LOGIN_ERROR_URL') python-social-auth-0.2.21/social/apps/django_app/context_processors.py0000644000175500017550000000300412754357263025730 0ustar debacledebaclefrom django.contrib.auth import REDIRECT_FIELD_NAME from django.utils.functional import SimpleLazyObject try: from django.utils.functional import empty as _empty empty = _empty except ImportError: # django < 1.4 empty = None from social.backends.utils import user_backends_data from social.apps.django_app.utils import Storage, BACKENDS 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 backends(request): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" return {'backends': LazyDict(lambda: user_backends_data(request.user, BACKENDS, Storage))} def login_redirect(request): """Load current redirect to context.""" value = request.method == 'POST' and \ request.POST.get(REDIRECT_FIELD_NAME) or \ request.GET.get(REDIRECT_FIELD_NAME) querystring = value and (REDIRECT_FIELD_NAME + '=' + value) or '' return { 'REDIRECT_FIELD_NAME': REDIRECT_FIELD_NAME, 'REDIRECT_FIELD_VALUE': value, 'REDIRECT_QUERYSTRING': querystring } python-social-auth-0.2.21/social/apps/django_app/views.py0000644000175500017550000000420512754357263023123 0ustar debacledebaclefrom django.conf import settings from django.contrib.auth import login, REDIRECT_FIELD_NAME from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt, csrf_protect from django.views.decorators.http import require_POST from django.views.decorators.cache import never_cache from social.utils import setting_name from social.actions import do_auth, do_complete, do_disconnect from social.apps.django_app.utils import psa NAMESPACE = getattr(settings, setting_name('URL_NAMESPACE'), None) or 'social' @never_cache @psa('{0}:complete'.format(NAMESPACE)) def auth(request, backend): return do_auth(request.backend, redirect_name=REDIRECT_FIELD_NAME) @never_cache @csrf_exempt @psa('{0}:complete'.format(NAMESPACE)) def complete(request, backend, *args, **kwargs): """Authentication complete view""" return do_complete(request.backend, _do_login, request.user, redirect_name=REDIRECT_FIELD_NAME, *args, **kwargs) @never_cache @login_required @psa() @require_POST @csrf_protect def disconnect(request, backend, association_id=None): """Disconnects given backend from current logged in user.""" return do_disconnect(request.backend, request.user, association_id, redirect_name=REDIRECT_FIELD_NAME) def _do_login(backend, user, social_user): user.backend = '{0}.{1}'.format(backend.__module__, backend.__class__.__name__) login(backend.strategy.request, user) if backend.setting('SESSION_EXPIRATION', False): # Set session expiration date if present and enabled # 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: backend.strategy.request.session.set_expiry( expiration.seconds + expiration.days * 86400 ) except OverflowError: # Handle django time zone overflow backend.strategy.request.session.set_expiry(None) python-social-auth-0.2.21/social/apps/django_app/tests.py0000644000175500017550000000464312754357263023136 0ustar debacledebaclefrom social.tests.test_exceptions import * from social.tests.test_pipeline import * from social.tests.test_storage import * from social.tests.test_utils import * from social.tests.actions.test_associate import * from social.tests.actions.test_disconnect import * from social.tests.actions.test_login import * from social.tests.backends.test_amazon import * from social.tests.backends.test_angel import * from social.tests.backends.test_behance import * from social.tests.backends.test_bitbucket import * from social.tests.backends.test_box import * from social.tests.backends.test_broken import * from social.tests.backends.test_coinbase import * from social.tests.backends.test_dailymotion import * from social.tests.backends.test_disqus import * from social.tests.backends.test_dropbox import * from social.tests.backends.test_dummy import * from social.tests.backends.test_email import * from social.tests.backends.test_evernote import * from social.tests.backends.test_facebook import * from social.tests.backends.test_fitbit import * from social.tests.backends.test_flickr import * from social.tests.backends.test_foursquare import * from social.tests.backends.test_google import * from social.tests.backends.test_instagram import * from social.tests.backends.test_linkedin import * from social.tests.backends.test_live import * from social.tests.backends.test_livejournal import * from social.tests.backends.test_mixcloud import * from social.tests.backends.test_podio import * from social.tests.backends.test_readability import * from social.tests.backends.test_reddit import * from social.tests.backends.test_sketchfab import * from social.tests.backends.test_skyrock import * from social.tests.backends.test_soundcloud import * from social.tests.backends.test_stackoverflow import * from social.tests.backends.test_steam import * from social.tests.backends.test_stocktwits import * from social.tests.backends.test_stripe import * from social.tests.backends.test_thisismyjam import * from social.tests.backends.test_tripit import * from social.tests.backends.test_tumblr import * from social.tests.backends.test_twitter import * from social.tests.backends.test_username import * from social.tests.backends.test_utils import * from social.tests.backends.test_vk import * from social.tests.backends.test_xing import * from social.tests.backends.test_yahoo import * from social.tests.backends.test_yammer import * from social.tests.backends.test_yandex import * python-social-auth-0.2.21/social/apps/tornado_app/0000755000175500017550000000000012754357263021617 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/tornado_app/handlers.py0000644000175500017550000000226112754357263023772 0ustar debacledebaclefrom tornado.web import RequestHandler from social.apps.tornado_app.utils import psa from social.actions import do_auth, do_complete, do_disconnect class BaseHandler(RequestHandler): def user_id(self): return self.get_secure_cookie('user_id') def get_current_user(self): user_id = self.user_id() if user_id: return self.backend.strategy.get_user(int(user_id)) def login_user(self, user): self.set_secure_cookie('user_id', str(user.id)) class AuthHandler(BaseHandler): def get(self, backend): self._auth(backend) def post(self, backend): self._auth(backend) @psa('complete') def _auth(self, backend): do_auth(self.backend) class CompleteHandler(BaseHandler): def get(self, backend): self._complete(backend) def post(self, backend): self._complete(backend) @psa('complete') def _complete(self, backend): do_complete( self.backend, login=lambda backend, user, social_user: self.login_user(user), user=self.get_current_user() ) class DisconnectHandler(BaseHandler): def post(self): do_disconnect() python-social-auth-0.2.21/social/apps/tornado_app/routes.py0000644000175500017550000000075112754357263023515 0ustar debacledebaclefrom tornado.web import url from .handlers import AuthHandler, CompleteHandler, DisconnectHandler SOCIAL_AUTH_ROUTES = [ url(r'/login/(?P[^/]+)/?', AuthHandler, name='begin'), url(r'/complete/(?P[^/]+)/', CompleteHandler, name='complete'), url(r'/disconnect/(?P[^/]+)/?', DisconnectHandler, name='disconnect'), url(r'/disconnect/(?P[^/]+)/(?P\d+)/?', DisconnectHandler, name='disconect_individual'), ] python-social-auth-0.2.21/social/apps/tornado_app/models.py0000644000175500017550000000401712754357263023456 0ustar debacledebacle"""Tornado SQLAlchemy ORM models for Social Auth""" from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship, backref from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ SQLAlchemyCodeMixin, \ BaseSQLAlchemyStorage class TornadoStorage(BaseSQLAlchemyStorage): user = None nonce = None association = None code = None def init_social(Base, session, settings): UID_LENGTH = settings.get(setting_name('UID_LENGTH'), 255) User = module_member(settings[setting_name('USER_MODEL')]) app_session = session class _AppSession(object): @classmethod def _session(cls): return app_session class UserSocialAuth(_AppSession, Base, SQLAlchemyUserMixin): """Social Auth association model""" uid = Column(String(UID_LENGTH)) user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) user = relationship(User, backref=backref('social_auth', lazy='dynamic')) @classmethod def username_max_length(cls): return User.__table__.columns.get('username').type.length @classmethod def user_model(cls): return User class Nonce(_AppSession, Base, SQLAlchemyNonceMixin): """One use numbers""" pass class Association(_AppSession, Base, SQLAlchemyAssociationMixin): """OpenId account association""" pass class Code(_AppSession, Base, SQLAlchemyCodeMixin): pass # Set the references in the storage class TornadoStorage.user = UserSocialAuth TornadoStorage.nonce = Nonce TornadoStorage.association = Association TornadoStorage.code = Code python-social-auth-0.2.21/social/apps/tornado_app/utils.py0000644000175500017550000000301212754357263023325 0ustar debacledebacleimport warnings from functools import wraps from social.utils import setting_name from social.strategies.utils import get_strategy from social.backends.utils import get_backend DEFAULTS = { 'STORAGE': 'social.apps.tornado_app.models.TornadoStorage', 'STRATEGY': 'social.strategies.tornado_strategy.TornadoStrategy' } def get_helper(request_handler, name): return request_handler.settings.get(setting_name(name), DEFAULTS.get(name, None)) def load_strategy(request_handler): strategy = get_helper(request_handler, 'STRATEGY') storage = get_helper(request_handler, 'STORAGE') return get_strategy(strategy, storage, request_handler) def load_backend(request_handler, strategy, name, redirect_uri): backends = get_helper(request_handler, 'AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy, redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(self, backend, *args, **kwargs): uri = redirect_uri if uri and not uri.startswith('/'): uri = self.reverse_url(uri, backend) self.strategy = load_strategy(self) self.backend = load_backend(self, self.strategy, backend, uri) return func(self, backend, *args, **kwargs) return wrapper return decorator def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.21/social/apps/tornado_app/__init__.py0000644000175500017550000000000012754357263023716 0ustar debacledebaclepython-social-auth-0.2.21/social/apps/pyramid_app/0000755000175500017550000000000012754357263021616 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/pyramid_app/models.py0000644000175500017550000000415412754357263023457 0ustar debacledebacle"""Pyramid SQLAlchemy ORM models for Social Auth""" from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship, backref from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ SQLAlchemyCodeMixin, \ BaseSQLAlchemyStorage class PyramidStorage(BaseSQLAlchemyStorage): user = None nonce = None association = None def init_social(config, Base, session): if hasattr(config, 'registry'): config = config.registry.settings UID_LENGTH = config.get(setting_name('UID_LENGTH'), 255) User = module_member(config[setting_name('USER_MODEL')]) app_session = session class _AppSession(object): COMMIT_SESSION = False @classmethod def _session(cls): return app_session class UserSocialAuth(_AppSession, Base, SQLAlchemyUserMixin): """Social Auth association model""" uid = Column(String(UID_LENGTH)) user_id = Column(User.id.type, ForeignKey(User.id), nullable=False, index=True) user = relationship(User, backref=backref('social_auth', lazy='dynamic')) @classmethod def username_max_length(cls): return User.__table__.columns.get('username').type.length @classmethod def user_model(cls): return User class Nonce(_AppSession, Base, SQLAlchemyNonceMixin): """One use numbers""" pass class Association(_AppSession, Base, SQLAlchemyAssociationMixin): """OpenId account association""" pass class Code(_AppSession, Base, SQLAlchemyCodeMixin): pass # Set the references in the storage class PyramidStorage.user = UserSocialAuth PyramidStorage.nonce = Nonce PyramidStorage.association = Association PyramidStorage.code = Code python-social-auth-0.2.21/social/apps/pyramid_app/utils.py0000644000175500017550000000463212754357263023335 0ustar debacledebacleimport warnings from functools import wraps from pyramid.threadlocal import get_current_registry from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden from social.utils import setting_name, module_member from social.strategies.utils import get_strategy from social.backends.utils import get_backend, user_backends_data DEFAULTS = { 'STORAGE': 'social.apps.pyramid_app.models.PyramidStorage', 'STRATEGY': 'social.strategies.pyramid_strategy.PyramidStrategy' } def get_helper(name): settings = get_current_registry().settings return settings.get(setting_name(name), DEFAULTS.get(name, None)) def load_strategy(request): return get_strategy( get_helper('STRATEGY'), get_helper('STORAGE'), request ) def load_backend(strategy, name, redirect_uri): backends = get_helper('AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy=strategy, redirect_uri=redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): backend = request.matchdict.get('backend') if not backend: return HTTPNotFound('Missing backend') uri = redirect_uri if uri and not uri.startswith('/'): uri = request.route_url(uri, backend=backend) request.strategy = load_strategy(request) request.backend = load_backend(request.strategy, backend, uri) return func(request, *args, **kwargs) return wrapper return decorator def login_required(func): @wraps(func) def wrapper(request, *args, **kwargs): is_logged_in = module_member( request.backend.setting('LOGGEDIN_FUNCTION') ) if not is_logged_in(request): raise HTTPForbidden('Not authorized user') return func(request, *args, **kwargs) return wrapper def backends(request, user): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" storage = module_member(get_helper('STORAGE')) return { 'backends': user_backends_data( user, get_helper('AUTHENTICATION_BACKENDS'), storage ) } def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.21/social/apps/pyramid_app/__init__.py0000644000175500017550000000075512754357263023736 0ustar debacledebaclefrom social.strategies.utils import set_current_strategy_getter from social.apps.pyramid_app.utils import load_strategy def includeme(config): config.add_route('social.auth', '/login/{backend}') config.add_route('social.complete', '/complete/{backend}') config.add_route('social.disconnect', '/disconnect/{backend}') config.add_route('social.disconnect_association', '/disconnect/{backend}/{association_id}') set_current_strategy_getter(load_strategy) python-social-auth-0.2.21/social/apps/pyramid_app/views.py0000644000175500017550000000211512754357263023324 0ustar debacledebaclefrom pyramid.view import view_config from social.utils import module_member from social.actions import do_auth, do_complete, do_disconnect from social.apps.pyramid_app.utils import psa, login_required @view_config(route_name='social.auth', request_method=('GET', 'POST')) @psa('social.complete') def auth(request): return do_auth(request.backend, redirect_name='next') @view_config(route_name='social.complete', request_method=('GET', 'POST')) @psa('social.complete') def complete(request, *args, **kwargs): do_login = module_member(request.backend.setting('LOGIN_FUNCTION')) return do_complete(request.backend, do_login, request.user, redirect_name='next', *args, **kwargs) @view_config(route_name='social.disconnect', request_method=('POST',)) @view_config(route_name='social.disconnect_association', request_method=('POST',)) @psa() @login_required def disconnect(request): return do_disconnect(request.backend, request.user, request.matchdict.get('association_id'), redirect_name='next') python-social-auth-0.2.21/social/apps/flask_app/0000755000175500017550000000000012754357263021251 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/flask_app/routes.py0000644000175500017550000000311212754357263023141 0ustar debacledebaclefrom flask import g, Blueprint, request from flask_login import login_required, login_user from social.actions import do_auth, do_complete, do_disconnect from social.apps.flask_app.utils import psa social_auth = Blueprint('social', __name__) @social_auth.route('/login//', methods=('GET', 'POST')) @psa('social.complete') def auth(backend): return do_auth(g.backend) @social_auth.route('/complete//', methods=('GET', 'POST')) @psa('social.complete') def complete(backend, *args, **kwargs): """Authentication complete view, override this view if transaction management doesn't suit your needs.""" return do_complete(g.backend, login=do_login, user=g.user, *args, **kwargs) @social_auth.route('/disconnect//', methods=('POST',)) @social_auth.route('/disconnect///', methods=('POST',)) @social_auth.route('/disconnect///', methods=('POST',)) @login_required @psa() def disconnect(backend, association_id=None): """Disconnects given backend from current logged in user.""" return do_disconnect(g.backend, g.user, association_id) def do_login(backend, user, social_user): name = backend.strategy.setting('REMEMBER_SESSION_NAME', 'keep') remember = backend.strategy.session_get(name) or \ request.cookies.get(name) or \ request.args.get(name) or \ request.form.get(name) or \ False return login_user(user, remember=remember) python-social-auth-0.2.21/social/apps/flask_app/utils.py0000644000175500017550000000313312754357263022763 0ustar debacledebacleimport warnings from functools import wraps from flask import current_app, url_for, g from social.utils import module_member, setting_name from social.strategies.utils import get_strategy from social.backends.utils import get_backend DEFAULTS = { 'STORAGE': 'social.apps.flask_app.default.models.FlaskStorage', 'STRATEGY': 'social.strategies.flask_strategy.FlaskStrategy' } def get_helper(name, do_import=False): config = current_app.config.get(setting_name(name), DEFAULTS.get(name, None)) return do_import and module_member(config) or config def load_strategy(): strategy = get_helper('STRATEGY') storage = get_helper('STORAGE') return get_strategy(strategy, storage) def load_backend(strategy, name, redirect_uri, *args, **kwargs): backends = get_helper('AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy=strategy, redirect_uri=redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(backend, *args, **kwargs): uri = redirect_uri if uri and not uri.startswith('/'): uri = url_for(uri, backend=backend) g.strategy = load_strategy() g.backend = load_backend(g.strategy, backend, redirect_uri=uri, *args, **kwargs) return func(backend, *args, **kwargs) return wrapper return decorator def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.21/social/apps/flask_app/__init__.py0000644000175500017550000000024312754357263023361 0ustar debacledebaclefrom social.strategies.utils import set_current_strategy_getter from social.apps.flask_app.utils import load_strategy set_current_strategy_getter(load_strategy) python-social-auth-0.2.21/social/apps/flask_app/template_filters.py0000644000175500017550000000151612754357263025171 0ustar debacledebaclefrom flask import g, request from social.backends.utils import user_backends_data from social.apps.flask_app.utils import get_helper def backends(): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" return { 'backends': user_backends_data(g.user, get_helper('AUTHENTICATION_BACKENDS'), get_helper('STORAGE', do_import=True)) } def login_redirect(): """Load current redirect to context.""" value = request.form.get('next', '') or \ request.args.get('next', '') return { 'REDIRECT_FIELD_NAME': 'next', 'REDIRECT_FIELD_VALUE': value, 'REDIRECT_QUERYSTRING': value and ('next=' + value) or '' } python-social-auth-0.2.21/social/apps/flask_app/default/0000755000175500017550000000000012754357263022675 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/flask_app/default/models.py0000644000175500017550000000450112754357263024532 0ustar debacledebacle"""Flask SQLAlchemy ORM models for Social Auth""" from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship, backref from sqlalchemy.schema import UniqueConstraint from sqlalchemy.ext.declarative import declarative_base from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ SQLAlchemyCodeMixin, \ BaseSQLAlchemyStorage PSABase = declarative_base() class _AppSession(PSABase): __abstract__ = True @classmethod def _set_session(cls, app_session): cls.app_session = app_session @classmethod def _session(cls): return cls.app_session class UserSocialAuth(_AppSession, SQLAlchemyUserMixin): """Social Auth association model""" # Temporary override of constraints to avoid an error on the still-to-be # missing column uid. __table_args__ = () @classmethod def user_model(cls): return cls.user.property.argument @classmethod def username_max_length(cls): user_model = cls.user_model() return user_model.__table__.columns.get('username').type.length class Nonce(_AppSession, SQLAlchemyNonceMixin): """One use numbers""" pass class Association(_AppSession, SQLAlchemyAssociationMixin): """OpenId account association""" pass class Code(_AppSession, SQLAlchemyCodeMixin): pass class FlaskStorage(BaseSQLAlchemyStorage): user = UserSocialAuth nonce = Nonce association = Association code = Code def init_social(app, session): UID_LENGTH = app.config.get(setting_name('UID_LENGTH'), 255) User = module_member(app.config[setting_name('USER_MODEL')]) _AppSession._set_session(session) UserSocialAuth.__table_args__ = (UniqueConstraint('provider', 'uid'),) UserSocialAuth.uid = Column(String(UID_LENGTH)) UserSocialAuth.user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) UserSocialAuth.user = relationship(User, backref=backref('social_auth', lazy='dynamic')) python-social-auth-0.2.21/social/apps/flask_app/default/__init__.py0000644000175500017550000000000012754357263024774 0ustar debacledebaclepython-social-auth-0.2.21/social/apps/flask_app/me/0000755000175500017550000000000012754357263021652 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/flask_app/me/models.py0000644000175500017550000000255012754357263023511 0ustar debacledebacle"""Flask SQLAlchemy ORM models for Social Auth""" from mongoengine import ReferenceField from social.utils import setting_name, module_member from social.storage.mongoengine_orm import MongoengineUserMixin, \ MongoengineAssociationMixin, \ MongoengineNonceMixin, \ MongoengineCodeMixin, \ BaseMongoengineStorage class FlaskStorage(BaseMongoengineStorage): user = None nonce = None association = None code = None def init_social(app, db): User = module_member(app.config[setting_name('USER_MODEL')]) class UserSocialAuth(db.Document, MongoengineUserMixin): """Social Auth association model""" user = ReferenceField(User) @classmethod def user_model(cls): return User class Nonce(db.Document, MongoengineNonceMixin): """One use numbers""" pass class Association(db.Document, MongoengineAssociationMixin): """OpenId account association""" pass class Code(db.Document, MongoengineCodeMixin): pass # Set the references in the storage class FlaskStorage.user = UserSocialAuth FlaskStorage.nonce = Nonce FlaskStorage.association = Association FlaskStorage.code = Code python-social-auth-0.2.21/social/apps/flask_app/me/__init__.py0000644000175500017550000000000012754357263023751 0ustar debacledebaclepython-social-auth-0.2.21/social/apps/flask_app/peewee/0000755000175500017550000000000012754357263022523 5ustar debacledebaclepython-social-auth-0.2.21/social/apps/flask_app/peewee/models.py0000644000175500017550000000262712754357263024367 0ustar debacledebacle"""Flask Peewee ORM models for Social Auth""" from peewee import Model, ForeignKeyField, Proxy from social.utils import setting_name, module_member from social.storage.peewee_orm import PeeweeUserMixin, \ PeeweeAssociationMixin, \ PeeweeNonceMixin, \ PeeweeCodeMixin, \ BasePeeweeStorage, \ database_proxy class FlaskStorage(BasePeeweeStorage): user = None nonce = None association = None code = None def init_social(app, db): User = module_member(app.config[setting_name('USER_MODEL')]) database_proxy.initialize(db) class UserSocialAuth(PeeweeUserMixin): """Social Auth association model""" user = ForeignKeyField(User, related_name='social_auth') @classmethod def user_model(cls): return User class Nonce(PeeweeNonceMixin): """One use numbers""" pass class Association(PeeweeAssociationMixin): """OpenId account association""" pass class Code(PeeweeCodeMixin): pass # Set the references in the storage class FlaskStorage.user = UserSocialAuth FlaskStorage.nonce = Nonce FlaskStorage.association = Association FlaskStorage.code = Code python-social-auth-0.2.21/social/apps/flask_app/peewee/__init__.py0000644000175500017550000000000012754357263024622 0ustar debacledebaclepython-social-auth-0.2.21/social/__init__.py0000644000175500017550000000032412754357263020456 0ustar debacledebacle""" python-social-auth application, allows OpenId or OAuth user registration/authentication just adding a few configurations. """ version = (0, 2, 21) extra = '' __version__ = '.'.join(map(str, version)) + extra python-social-auth-0.2.21/social/backends/0000755000175500017550000000000012754357263020120 5ustar debacledebaclepython-social-auth-0.2.21/social/backends/wunderlist.py0000644000175500017550000000211112754357263022665 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 class WunderlistOAuth2(BaseOAuth2): """Wunderlist OAuth2 authentication backend""" name = 'wunderlist' AUTHORIZATION_URL = 'https://www.wunderlist.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://www.wunderlist.com/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_details(self, response): """Return user details from Wunderlist account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': str(response.get('id')), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" headers = { 'X-Access-Token': access_token, 'X-Client-ID': self.setting('KEY')} return self.get_json( 'https://a.wunderlist.com/api/v1/user', headers=headers) python-social-auth-0.2.21/social/backends/douban.py0000644000175500017550000000406412754357263021746 0ustar debacledebacle""" Douban OAuth1 and OAuth2 backends, docs at: http://psa.matiasaguirre.net/docs/backends/douban.html """ from social.backends.oauth import BaseOAuth2, BaseOAuth1 class DoubanOAuth(BaseOAuth1): """Douban OAuth authentication backend""" name = 'douban' EXTRA_DATA = [('id', 'id')] AUTHORIZATION_URL = 'http://www.douban.com/service/auth/authorize' REQUEST_TOKEN_URL = 'http://www.douban.com/service/auth/request_token' ACCESS_TOKEN_URL = 'http://www.douban.com/service/auth/access_token' 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': ''} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json('http://api.douban.com/people/%40me?&alt=json', auth=self.oauth_auth(access_token)) class DoubanOAuth2(BaseOAuth2): """Douban OAuth authentication backend""" name = 'douban-oauth2' AUTHORIZATION_URL = 'https://www.douban.com/service/auth2/auth' ACCESS_TOKEN_URL = 'https://www.douban.com/service/auth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('id', 'id'), ('uid', 'username'), ('refresh_token', 'refresh_token'), ] def get_user_details(self, response): """Return user details from Douban""" fullname, first_name, last_name = self.get_user_names( response.get('name', '') ) return {'username': response.get('uid', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': ''} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( 'https://api.douban.com/v2/user/~me', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.21/social/backends/echosign.py0000644000175500017550000000153312754357263022273 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 class EchosignOAuth2(BaseOAuth2): name = 'echosign' REDIRECT_STATE = False ACCESS_TOKEN_METHOD = 'POST' REFRESH_TOKEN_METHOD = 'POST' REVOKE_TOKEN_METHOD = 'POST' AUTHORIZATION_URL = 'https://secure.echosign.com/public/oauth' ACCESS_TOKEN_URL = 'https://secure.echosign.com/oauth/token' REFRESH_TOKEN_URL = 'https://secure.echosign.com/oauth/refresh' REVOKE_TOKEN_URL = 'https://secure.echosign.com/oauth/revoke' def get_user_details(self, response): return response def get_user_id(self, details, response): return details['userInfoList'][0]['userId'] def user_data(self, access_token, *args, **kwargs): return self.get_json( 'https://api.echosign.com/api/rest/v3/users', headers={'Access-Token': access_token}) python-social-auth-0.2.21/social/backends/flickr.py0000644000175500017550000000273012754357263021746 0ustar debacledebacle""" Flickr OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/flickr.html """ from social.backends.oauth import BaseOAuth1 class FlickrOAuth(BaseOAuth1): """Flickr OAuth authentication backend""" name = 'flickr' AUTHORIZATION_URL = 'https://www.flickr.com/services/oauth/authorize' REQUEST_TOKEN_URL = 'https://www.flickr.com/services/oauth/request_token' ACCESS_TOKEN_URL = 'https://www.flickr.com/services/oauth/access_token' EXTRA_DATA = [ ('id', 'id'), ('username', 'username'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Flickr account""" fullname, first_name, last_name = self.get_user_names( response.get('fullname') ) return {'username': response.get('username') or response.get('id'), 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} 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.get('fullname', ''), } def auth_extra_arguments(self): params = super(FlickrOAuth, self).auth_extra_arguments() or {} if 'perms' not in params: params['perms'] = 'read' return params python-social-auth-0.2.21/social/backends/twitch.py0000644000175500017550000000161612754357263022000 0ustar debacledebacle""" Twitch OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/twitch.html """ from social.backends.oauth import BaseOAuth2 class TwitchOAuth2(BaseOAuth2): """Twitch OAuth authentication backend""" name = 'twitch' ID_KEY = '_id' AUTHORIZATION_URL = 'https://api.twitch.tv/kraken/oauth2/authorize' ACCESS_TOKEN_URL = 'https://api.twitch.tv/kraken/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' DEFAULT_SCOPE = ['user_read'] REDIRECT_STATE = False def get_user_details(self, response): return { 'username': response.get('name'), 'email': response.get('email'), 'first_name': '', 'last_name': '' } def user_data(self, access_token, *args, **kwargs): return self.get_json( 'https://api.twitch.tv/kraken/user/', params={'oauth_token': access_token} ) python-social-auth-0.2.21/social/backends/pocket.py0000644000175500017550000000324212754357263021760 0ustar debacledebacle""" Pocket OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/pocket.html """ from social.backends.base import BaseAuth from social.utils import handle_http_errors class PocketAuth(BaseAuth): name = 'pocket' AUTHORIZATION_URL = 'https://getpocket.com/auth/authorize' ACCESS_TOKEN_URL = 'https://getpocket.com/v3/oauth/authorize' REQUEST_TOKEN_URL = 'https://getpocket.com/v3/oauth/request' ID_KEY = 'username' def get_json(self, url, *args, **kwargs): headers = {'X-Accept': 'application/json'} kwargs.update({'method': 'POST', 'headers': headers}) return super(PocketAuth, self).get_json(url, *args, **kwargs) def get_user_details(self, response): return {'username': response['username']} def extra_data(self, user, uid, response, details=None, *args, **kwargs): return response def auth_url(self): data = { 'consumer_key': self.setting('KEY'), 'redirect_uri': self.redirect_uri, } token = self.get_json(self.REQUEST_TOKEN_URL, data=data)['code'] self.strategy.session_set('pocket_request_token', token) bits = (self.AUTHORIZATION_URL, token, self.redirect_uri) return '%s?request_token=%s&redirect_uri=%s' % bits @handle_http_errors def auth_complete(self, *args, **kwargs): data = { 'consumer_key': self.setting('KEY'), 'code': self.strategy.session_get('pocket_request_token'), } response = self.get_json(self.ACCESS_TOKEN_URL, data=data) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.21/social/backends/saml.py0000644000175500017550000003041012754357263021424 0ustar debacledebacle""" Backend for SAML 2.0 support Terminology: "Service Provider" (SP): Your web app "Identity Provider" (IdP): The third-party site that is authenticating users via SAML """ from onelogin.saml2.auth import OneLogin_Saml2_Auth from onelogin.saml2.settings import OneLogin_Saml2_Settings from social.backends.base import BaseAuth from social.exceptions import AuthFailed, AuthMissingParameter # Helpful constants: OID_COMMON_NAME = "urn:oid:2.5.4.3" OID_EDU_PERSON_PRINCIPAL_NAME = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6" OID_EDU_PERSON_ENTITLEMENT = "urn:oid:1.3.6.1.4.1.5923.1.1.1.7" OID_GIVEN_NAME = "urn:oid:2.5.4.42" OID_MAIL = "urn:oid:0.9.2342.19200300.100.1.3" OID_SURNAME = "urn:oid:2.5.4.4" OID_USERID = "urn:oid:0.9.2342.19200300.100.1.1" class SAMLIdentityProvider(object): """Wrapper around configuration for a SAML Identity provider""" def __init__(self, name, **kwargs): """Load and parse configuration""" self.name = name # name should be a slug and must not contain a colon, which # could conflict with uid prefixing: assert ':' not in self.name and ' ' not in self.name, \ 'IdP "name" should be a slug (short, no spaces)' self.conf = kwargs def get_user_permanent_id(self, attributes): """ The most important method: Get a permanent, unique identifier for this user from the attributes supplied by the IdP. If you want to use the NameID, it's available via attributes['name_id'] """ return attributes[ self.conf.get('attr_user_permanent_id', OID_USERID) ][0] # Attributes processing: def get_user_details(self, attributes): """ Given the SAML attributes extracted from the SSO response, get the user data like name. """ return { 'fullname': self.get_attr(attributes, 'attr_full_name', OID_COMMON_NAME), 'first_name': self.get_attr(attributes, 'attr_first_name', OID_GIVEN_NAME), 'last_name': self.get_attr(attributes, 'attr_last_name', OID_SURNAME), 'username': self.get_attr(attributes, 'attr_username', OID_USERID), 'email': self.get_attr(attributes, 'attr_email', OID_MAIL), } def get_attr(self, attributes, conf_key, default_attribute): """ Internal helper method. Get the attribute 'default_attribute' out of the attributes, unless self.conf[conf_key] overrides the default by specifying another attribute to use. """ key = self.conf.get(conf_key, default_attribute) return attributes[key][0] if key in attributes else None @property def entity_id(self): """Get the entity ID for this IdP""" # Required. e.g. "https://idp.testshib.org/idp/shibboleth" return self.conf['entity_id'] @property def sso_url(self): """Get the SSO URL for this IdP""" # Required. e.g. # "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO" return self.conf['url'] @property def x509cert(self): """X.509 Public Key Certificate for this IdP""" return self.conf['x509cert'] @property def saml_config_dict(self): """Get the IdP configuration dict in the format required by python-saml""" return { 'entityId': self.entity_id, 'singleSignOnService': { 'url': self.sso_url, # python-saml only supports Redirect 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' }, 'x509cert': self.x509cert, } class DummySAMLIdentityProvider(SAMLIdentityProvider): """ A placeholder IdP used when we must specify something, e.g. when generating SP metadata. If OneLogin_Saml2_Auth is modified to not always require IdP config, this can be removed. """ def __init__(self): super(DummySAMLIdentityProvider, self).__init__( 'dummy', entity_id='https://dummy.none/saml2', url='https://dummy.none/SSO', x509cert='' ) class SAMLAuth(BaseAuth): """ PSA Backend that implements SAML 2.0 Service Provider (SP) functionality. Unlike all of the other backends, this one can be configured to work with many identity providers (IdPs). For example, a University that belongs to a Shibboleth federation may support authentication via ~100 partner universities. Also, the IdP configuration can be changed at runtime if you require that functionality - just subclass this and override `get_idp()`. Several settings are required. Here's an example: SOCIAL_AUTH_SAML_SP_ENTITY_ID = "https://saml.example.com/" SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = "... X.509 certificate string ..." SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = "... private key ..." SOCIAL_AUTH_SAML_ORG_INFO = { "en-US": { "name": "example", "displayname": "Example Inc.", "url": "http://example.com" } } SOCIAL_AUTH_SAML_TECHNICAL_CONTACT = { "givenName": "Tech Gal", "emailAddress": "technical@example.com" } SOCIAL_AUTH_SAML_SUPPORT_CONTACT = { "givenName": "Support Guy", "emailAddress": "support@example.com" } SOCIAL_AUTH_SAML_ENABLED_IDPS = { "testshib": { "entity_id": "https://idp.testshib.org/idp/shibboleth", "url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", "x509cert": "MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0B... ...8Bbnl+ev0peYzxFyF5sQA==", } } Optional settings: SOCIAL_AUTH_SAML_SP_EXTRA = {} SOCIAL_AUTH_SAML_SECURITY_CONFIG = {} """ name = "saml" def get_idp(self, idp_name): """Given the name of an IdP, get a SAMLIdentityProvider instance""" idp_config = self.setting('ENABLED_IDPS')[idp_name] return SAMLIdentityProvider(idp_name, **idp_config) def generate_saml_config(self, idp): """ Generate the configuration required to instantiate OneLogin_Saml2_Auth """ # The shared absolute URL that all IdPs redirect back to - # this is specified in our metadata.xml: abs_completion_url = self.redirect_uri config = { 'contactPerson': { 'technical': self.setting('TECHNICAL_CONTACT'), 'support': self.setting('SUPPORT_CONTACT') }, 'debug': True, 'idp': idp.saml_config_dict, 'organization': self.setting('ORG_INFO'), 'security': { 'metadataValidUntil': '', 'metadataCacheDuration': 'P10D', # metadata valid for ten days }, 'sp': { 'assertionConsumerService': { 'url': abs_completion_url, # python-saml only supports HTTP-POST 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' }, 'entityId': self.setting('SP_ENTITY_ID'), 'x509cert': self.setting('SP_PUBLIC_CERT'), 'privateKey': self.setting('SP_PRIVATE_KEY'), }, 'strict': True, # We must force strict mode - for security } config["security"].update(self.setting("SECURITY_CONFIG", {})) config["sp"].update(self.setting("SP_EXTRA", {})) return config def generate_metadata_xml(self): """ Helper method that can be used from your web app to generate the XML metadata required to link your web app as a Service Provider with each IdP you wish to use. Returns (metadata XML string, list of errors) Example usage (Django): from social.apps.django_app.utils import load_strategy, \ load_backend def saml_metadata_view(request): complete_url = reverse('social:complete', args=("saml", )) saml_backend = load_backend(load_strategy(request), "saml", complete_url) metadata, errors = saml_backend.generate_metadata_xml() if not errors: return HttpResponse(content=metadata, content_type='text/xml') return HttpResponseServerError(content=', '.join(errors)) """ # python-saml requires us to specify something here even # though it's not used idp = DummySAMLIdentityProvider() config = self.generate_saml_config(idp) saml_settings = OneLogin_Saml2_Settings(config) metadata = saml_settings.get_sp_metadata() errors = saml_settings.validate_metadata(metadata) return metadata, errors def _create_saml_auth(self, idp): """Get an instance of OneLogin_Saml2_Auth""" config = self.generate_saml_config(idp) request_info = { 'https': 'on' if self.strategy.request_is_secure() else 'off', 'http_host': self.strategy.request_host(), 'script_name': self.strategy.request_path(), 'server_port': self.strategy.request_port(), 'get_data': self.strategy.request_get(), 'post_data': self.strategy.request_post(), } return OneLogin_Saml2_Auth(request_info, config) def auth_url(self): """Get the URL to which we must redirect in order to authenticate the user""" try: idp_name = self.strategy.request_data()['idp'] except KeyError: raise AuthMissingParameter(self, 'idp') auth = self._create_saml_auth(idp=self.get_idp(idp_name)) # Below, return_to sets the RelayState, which can contain # arbitrary data. We use it to store the specific SAML IdP # name, since we multiple IdPs share the same auth_complete # URL. return auth.login(return_to=idp_name) def get_user_details(self, response): """Get user details like full name, email, etc. from the response - see auth_complete""" idp = self.get_idp(response['idp_name']) return idp.get_user_details(response['attributes']) def get_user_id(self, details, response): """ Get the permanent ID for this user from the response. We prefix each ID with the name of the IdP so that we can connect multiple IdPs to this user. """ idp = self.get_idp(response['idp_name']) uid = idp.get_user_permanent_id(response['attributes']) return '{0}:{1}'.format(idp.name, uid) def auth_complete(self, *args, **kwargs): """ The user has been redirected back from the IdP and we should now log them in, if everything checks out. """ idp_name = self.strategy.request_data()['RelayState'] idp = self.get_idp(idp_name) auth = self._create_saml_auth(idp) auth.process_response() errors = auth.get_errors() if errors or not auth.is_authenticated(): reason = auth.get_last_error_reason() raise AuthFailed( self, 'SAML login failed: {0} ({1})'.format(errors, reason) ) attributes = auth.get_attributes() attributes['name_id'] = auth.get_nameid() self._check_entitlements(idp, attributes) response = { 'idp_name': idp_name, 'attributes': attributes, 'session_index': auth.get_session_index(), } kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def _check_entitlements(self, idp, attributes): """ Additional verification of a SAML response before authenticating the user. Subclasses can override this method if they need custom validation code, such as requiring the presence of an eduPersonEntitlement. raise social.exceptions.AuthForbidden if the user should not be authenticated, or do nothing to allow the login pipeline to continue. """ pass python-social-auth-0.2.21/social/backends/stackoverflow.py0000644000175500017550000000266012754357263023367 0ustar debacledebacle""" Stackoverflow OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/stackoverflow.html """ from social.backends.oauth import BaseOAuth2 class StackoverflowOAuth2(BaseOAuth2): """Stackoverflow OAuth2 authentication backend""" name = 'stackoverflow' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://stackexchange.com/oauth' ACCESS_TOKEN_URL = 'https://stackexchange.com/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Stackoverflow account""" fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': response.get('link').rsplit('/', 1)[-1], 'full_name': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.stackexchange.com/2.1/me', params={ 'site': 'stackoverflow', 'access_token': access_token, 'key': self.setting('API_KEY') } )['items'][0] def request_access_token(self, *args, **kwargs): return self.get_querystring(*args, **kwargs) python-social-auth-0.2.21/social/backends/withings.py0000644000175500017550000000102512754357263022324 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth1 class WithingsOAuth(BaseOAuth1): name = 'withings' AUTHORIZATION_URL = 'https://oauth.withings.com/account/authorize' REQUEST_TOKEN_URL = 'https://oauth.withings.com/account/request_token' ACCESS_TOKEN_URL = 'https://oauth.withings.com/account/access_token' ID_KEY = 'userid' def get_user_details(self, response): """Return user details from Withings account""" return {'userid': response['access_token']['userid'], 'email': ''} python-social-auth-0.2.21/social/backends/foursquare.py0000644000175500017550000000254012754357263022667 0ustar debacledebacle""" Foursquare OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/foursquare.html """ from social.backends.oauth import BaseOAuth2 class FoursquareOAuth2(BaseOAuth2): name = 'foursquare' AUTHORIZATION_URL = 'https://foursquare.com/oauth2/authenticate' ACCESS_TOKEN_URL = 'https://foursquare.com/oauth2/access_token' ACCESS_TOKEN_METHOD = 'POST' API_VERSION = '20140128' def get_user_id(self, details, response): return response['response']['user']['id'] def get_user_details(self, response): """Return user details from Foursquare account""" info = response['response']['user'] email = info['contact']['email'] fullname, first_name, last_name = self.get_user_names( first_name=info.get('firstName', ''), last_name=info.get('lastName', '') ) return {'username': first_name + ' ' + last_name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.foursquare.com/v2/users/self', params={'oauth_token': access_token, 'v': self.API_VERSION}) python-social-auth-0.2.21/social/backends/vimeo.py0000644000175500017550000000540412754357263021614 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth1, BaseOAuth2 class VimeoOAuth1(BaseOAuth1): """Vimeo OAuth authentication backend""" name = 'vimeo' AUTHORIZATION_URL = 'https://vimeo.com/oauth/authorize' REQUEST_TOKEN_URL = 'https://vimeo.com/oauth/request_token' ACCESS_TOKEN_URL = 'https://vimeo.com/oauth/access_token' def get_user_id(self, details, response): return response.get('person', {}).get('id') def get_user_details(self, response): """Return user details from Twitter account""" person = response.get('person', {}) fullname, first_name, last_name = self.get_user_names( person.get('display_name', '') ) return {'username': person.get('username', ''), 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( 'https://vimeo.com/api/rest/v2', params={'format': 'json', 'method': 'vimeo.people.getInfo'}, auth=self.oauth_auth(access_token) ) class VimeoOAuth2(BaseOAuth2): """Vimeo OAuth2 authentication backend""" name = 'vimeo-oauth2' AUTHORIZATION_URL = 'https://api.vimeo.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://api.vimeo.com/oauth/access_token' REFRESH_TOKEN_URL = 'https://api.vimeo.com/oauth/request_token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' API_ACCEPT_HEADER = {'Accept': 'application/vnd.vimeo.*+json;version=3.0'} def get_redirect_uri(self, state=None): """ Build redirect with redirect_state parameter. @Vimeo API 3 requires exact redirect uri without additional additional state parameter included """ return self.redirect_uri def get_user_id(self, details, response): """Return user id""" try: user_id = response.get('user', {})['uri'].split('/')[-1] except KeyError: user_id = None return user_id def get_user_details(self, response): """Return user details from account""" user = response.get('user', {}) fullname, first_name, last_name = self.get_user_names( user.get('name', '') ) return {'username': fullname, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( 'https://api.vimeo.com/me', params={'access_token': access_token}, headers=VimeoOAuth2.API_ACCEPT_HEADER, ) python-social-auth-0.2.21/social/backends/vend.py0000644000175500017550000000233212754357263021426 0ustar debacledebacle""" Vend OAuth2 backend: """ from social.backends.oauth import BaseOAuth2 class VendOAuth2(BaseOAuth2): name = 'vend' AUTHORIZATION_URL = 'https://secure.vendhq.com/connect' ACCESS_TOKEN_URL = 'https://{0}.vendhq.com/api/1.0/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('refresh_token', 'refresh_token'), ('domain_prefix', 'domain_prefix') ] def access_token_url(self): return self.ACCESS_TOKEN_URL.format(self.data['domain_prefix']) def get_user_details(self, response): email = response['email'] username = response.get('username') or email.split('@', 1)[0] return { 'username': username, 'email': email, 'fullname': '', 'first_name': '', 'last_name': '' } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" prefix = kwargs['response']['domain_prefix'] url = 'https://{0}.vendhq.com/api/users'.format(prefix) data = self.get_json(url, headers={ 'Authorization': 'Bearer {0}'.format(access_token) }) return data['users'][0] if data.get('users') else {} python-social-auth-0.2.21/social/backends/yammer.py0000644000175500017550000000300012754357263021755 0ustar debacledebacle""" Yammer OAuth2 production and staging backends, docs at: http://psa.matiasaguirre.net/docs/backends/yammer.html """ from social.backends.oauth import BaseOAuth2 class YammerOAuth2(BaseOAuth2): name = 'yammer' AUTHORIZATION_URL = 'https://www.yammer.com/dialog/oauth' ACCESS_TOKEN_URL = 'https://www.yammer.com/oauth2/access_token' 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'] fullname, first_name, last_name = self.get_user_names( fullname=response['user']['full_name'], first_name=response['user']['first_name'], last_name=response['user']['last_name'] ) email = response['user']['contact']['email_addresses'][0]['address'] mugshot_url = response['user']['mugshot_url'] return { 'username': username, 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'picture_url': mugshot_url } class YammerStagingOAuth2(YammerOAuth2): name = 'yammer-staging' AUTHORIZATION_URL = 'https://www.staging.yammer.com/dialog/oauth' ACCESS_TOKEN_URL = 'https://www.staging.yammer.com/oauth2/access_token' REQUEST_TOKEN_URL = 'https://www.staging.yammer.com/oauth2/request_token' python-social-auth-0.2.21/social/backends/github_enterprise.py0000644000175500017550000000246212754357263024220 0ustar debacledebacle""" Github Enterprise OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/github_enterprise.html """ from six.moves.urllib.parse import urljoin from social.utils import append_slash from social.backends.github import GithubOAuth2, GithubOrganizationOAuth2, \ GithubTeamOAuth2 class GithubEnterpriseMixin(object): def api_url(self): return append_slash(self.setting('API_URL')) def authorization_url(self): return self._url('login/oauth/authorize') def access_token_url(self): return self._url('login/oauth/access_token') def _url(self, path): return urljoin(append_slash(self.setting('URL')), path) class GithubEnterpriseOAuth2(GithubEnterpriseMixin, GithubOAuth2): """Github Enterprise OAuth authentication backend""" name = 'github-enterprise' class GithubEnterpriseOrganizationOAuth2(GithubEnterpriseMixin, GithubOrganizationOAuth2): """Github Enterprise OAuth2 authentication backend for organizations""" name = 'github-enterprise-org' DEFAULT_SCOPE = ['read:org'] class GithubEnterpriseTeamOAuth2(GithubEnterpriseMixin, GithubTeamOAuth2): """Github Enterprise OAuth2 authentication backend for teams""" name = 'github-enterprise-team' DEFAULT_SCOPE = ['read:org'] python-social-auth-0.2.21/social/backends/tripit.py0000644000175500017550000000336012754357263022007 0ustar debacledebacle""" Tripit OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/tripit.html """ from xml.dom import minidom from social.backends.oauth import BaseOAuth1 class TripItOAuth(BaseOAuth1): """TripIt OAuth authentication backend""" name = 'tripit' 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' EXTRA_DATA = [('screen_name', 'screen_name')] def get_user_details(self, response): """Return user details from TripIt account""" fullname, first_name, last_name = self.get_user_names(response['name']) return {'username': response['screen_name'], 'email': response['email'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" dom = minidom.parseString(self.oauth_request( access_token, 'https://api.tripit.com/v1/get/profile' ).content) 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 } python-social-auth-0.2.21/social/backends/zotero.py0000644000175500017550000000165512754357263022023 0ustar debacledebacle""" Zotero OAuth1 backends, docs at: http://psa.matiasaguirre.net/docs/backends/zotero.html """ from social.backends.oauth import BaseOAuth1 class ZoteroOAuth(BaseOAuth1): """Zotero OAuth authorization mechanism""" name = 'zotero' AUTHORIZATION_URL = 'https://www.zotero.org/oauth/authorize' REQUEST_TOKEN_URL = 'https://www.zotero.org/oauth/request' ACCESS_TOKEN_URL = 'https://www.zotero.org/oauth/access' def get_user_id(self, details, response): """ Return user unique id provided by service. For Ubuntu One the nickname should be original. """ return details['userID'] def get_user_details(self, response): """Return user details from Zotero API account""" access_token = response.get('access_token', {}) return { 'username': access_token.get('username', ''), 'userID': access_token.get('userID', '') } python-social-auth-0.2.21/social/backends/coinbase.py0000644000175500017550000000235612754357263022263 0ustar debacledebacle""" Coinbase OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/coinbase.html """ from social.backends.oauth import BaseOAuth2 class CoinbaseOAuth2(BaseOAuth2): name = 'coinbase' SCOPE_SEPARATOR = '+' DEFAULT_SCOPE = ['user', 'balance'] AUTHORIZATION_URL = 'https://coinbase.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://coinbase.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_id(self, details, response): return response['users'][0]['user']['id'] def get_user_details(self, response): """Return user details from Coinbase account""" user_data = response['users'][0]['user'] email = user_data.get('email', '') name = user_data['name'] fullname, first_name, last_name = self.get_user_names(name) return {'username': name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://coinbase.com/api/v1/users', params={'access_token': access_token}) python-social-auth-0.2.21/social/backends/beats.py0000644000175500017550000000433112754357263021571 0ustar debacledebacle""" Beats backend, docs at: https://developer.beatsmusic.com/docs """ import base64 from social.utils import handle_http_errors from social.backends.oauth import BaseOAuth2 class BeatsOAuth2(BaseOAuth2): name = 'beats' SCOPE_SEPARATOR = ' ' ID_KEY = 'user_context' AUTHORIZATION_URL = \ 'https://partner.api.beatsmusic.com/v1/oauth2/authorize' ACCESS_TOKEN_URL = 'https://partner.api.beatsmusic.com/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_id(self, details, response): return response['result'][BeatsOAuth2.ID_KEY] def auth_headers(self): return { 'Authorization': 'Basic {0}'.format(base64.urlsafe_b64encode( ('{0}:{1}'.format(*self.get_key_and_secret()).encode()) )) } @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) response = self.request_access_token( self.ACCESS_TOKEN_URL, data=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) # mashery wraps in jsonrpc if response.get('jsonrpc', None): response = response.get('result', None) return self.do_auth(response['access_token'], response=response, *args, **kwargs) def get_user_details(self, response): """Return user details from Beats account""" response = response['result'] fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': response.get('id'), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://partner.api.beatsmusic.com/v1/api/me', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.21/social/backends/gae.py0000644000175500017550000000241412754357263021227 0ustar debacledebacle""" Google App Engine support using User API """ from __future__ import absolute_import from google.appengine.api import users from social.backends.base import BaseAuth from social.exceptions import AuthException class GoogleAppEngineAuth(BaseAuth): """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': ''} def auth_url(self): """Build and return complete URL.""" return users.create_login_url(self.redirect_uri) def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance.""" if not users.get_current_user(): raise AuthException('Authentication error') kwargs.update({'response': '', 'backend': self}) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.21/social/backends/ngpvan.py0000644000175500017550000000410012754357263021756 0ustar debacledebacle""" NGP VAN's `ActionID` Provider http://developers.ngpvan.com/action-id """ from openid.extensions import ax from social.backends.open_id import OpenIdAuth class ActionIDOpenID(OpenIdAuth): """ NGP VAN's ActionID OpenID 1.1 authentication backend """ name = 'actionid-openid' URL = 'https://accounts.ngpvan.com/Home/Xrds' USERNAME_KEY = 'email' def get_ax_attributes(self): """ Return the AX attributes that ActionID responds with, as well as the user data result that it must map to. """ return [ ('http://openid.net/schema/contact/internet/email', 'email'), ('http://openid.net/schema/contact/phone/business', 'phone'), ('http://openid.net/schema/namePerson/first', 'first_name'), ('http://openid.net/schema/namePerson/last', 'last_name'), ('http://openid.net/schema/namePerson', 'fullname'), ] def setup_request(self, params=None): """ Setup the OpenID request Because ActionID does not advertise the availiability of AX attributes nor use standard attribute aliases, we need to setup the attributes manually instead of rely on the parent OpenIdAuth.setup_request() """ request = self.openid_request(params) fetch_request = ax.FetchRequest() fetch_request.add(ax.AttrInfo( 'http://openid.net/schema/contact/internet/email', alias='ngpvanemail', required=True )) fetch_request.add(ax.AttrInfo( 'http://openid.net/schema/contact/phone/business', alias='ngpvanphone', required=False )) fetch_request.add(ax.AttrInfo( 'http://openid.net/schema/namePerson/first', alias='ngpvanfirstname', required=False )) fetch_request.add(ax.AttrInfo( 'http://openid.net/schema/namePerson/last', alias='ngpvanlastname', required=False )) request.addExtension(fetch_request) return request python-social-auth-0.2.21/social/backends/open_id.py0000644000175500017550000003474012754357263022117 0ustar debacledebacleimport datetime from calendar import timegm from jwt import InvalidTokenError, decode as jwt_decode from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE from openid.consumer.discover import DiscoveryFailure from openid.extensions import sreg, ax, pape from social.utils import url_add_parameters from social.backends.base import BaseAuth from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthException, AuthFailed, AuthCanceled, \ AuthUnknownError, AuthMissingParameter, \ AuthTokenError # 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' class OpenIdAuth(BaseAuth): """Generic OpenID authentication backend""" name = 'openid' URL = None USERNAME_KEY = 'username' def get_user_id(self, details, response): """Return user unique id provided by service""" return response.identity_url def get_ax_attributes(self): attrs = self.setting('AX_SCHEMA_ATTRS', []) if attrs and self.setting('IGNORE_DEFAULT_AX_ATTRS', True): return attrs return attrs + AX_SCHEMA_ATTRS + OLD_AX_ATTRS def get_sreg_attributes(self): return self.setting('SREG_ATTR') or SREG_ATTR 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, self.get_sreg_attributes(), self.get_ax_attributes() )) fullname = values.get('fullname') or '' first_name = values.get('first_name') or '' last_name = values.get('last_name') or '' email = values.get('email') or '' if not fullname and first_name and last_name: fullname = first_name + ' ' + last_name elif fullname: try: first_name, last_name = fullname.rsplit(' ', 1) except ValueError: last_name = fullname username_key = self.setting('USERNAME_KEY') or self.USERNAME_KEY values.update({'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'username': values.get(username_key) or (first_name.title() + last_name.title()), 'email': email}) return values def extra_data(self, user, uid, response, details=None, *args, **kwargs): """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 """ sreg_names = self.setting('SREG_EXTRA_DATA') ax_names = self.setting('AX_EXTRA_DATA') values = self.values_from_response(response, sreg_names, ax_names) from_details = super(OpenIdAuth, self).extra_data( user, uid, {}, details, *args, **kwargs ) values.update(from_details) return values 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.strategy.absolute_uri(self.redirect_uri) 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.strategy.absolute_uri(self.redirect_uri) 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 self.setting('OPENID_TRUST_ROOT') or \ self.strategy.absolute_uri('/') def continue_pipeline(self, *args, **kwargs): """Continue previous halted pipeline""" response = self.consumer().complete(dict(self.data.items()), self.strategy.absolute_uri( self.redirect_uri )) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def auth_complete(self, *args, **kwargs): """Complete auth process""" response = self.consumer().complete(dict(self.data.items()), self.strategy.absolute_uri( self.redirect_uri )) self.process_error(response) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def process_error(self, data): if not data: raise AuthException(self, 'OpenID relying party endpoint') elif data.status == FAILURE: raise AuthFailed(self, data.message) elif data.status == CANCEL: raise AuthCanceled(self) elif data.status != SUCCESS: raise AuthUnknownError(self, data.status) def setup_request(self, params=None): """Setup request""" request = self.openid_request(params) # Request some user details. Use attribute exchange if provider # advertises support. if request.endpoint.supportsType(ax.AXMessage.ns_uri): fetch_request = ax.FetchRequest() # Mark all attributes as required, Google ignores optional ones for attr, alias in self.get_ax_attributes(): fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True)) else: fetch_request = sreg.SRegRequest( optional=list(dict(self.get_sreg_attributes()).keys()) ) request.addExtension(fetch_request) # Add PAPE Extension for if configured preferred_policies = self.setting( 'OPENID_PAPE_PREFERRED_AUTH_POLICIES' ) preferred_level_types = self.setting( 'OPENID_PAPE_PREFERRED_AUTH_LEVEL_TYPES' ) max_age = self.setting('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 or preferred_level_types: pape_request = pape.Request( max_auth_age=max_age, preferred_auth_policies=preferred_policies, preferred_auth_level_types=preferred_level_types ) request.addExtension(pape_request) return request def consumer(self): """Create an OpenID Consumer object for the given Django request.""" if not hasattr(self, '_consumer'): self._consumer = self.create_consumer(self.strategy.openid_store()) return self._consumer def create_consumer(self, store=None): return Consumer(self.strategy.openid_session_dict(SESSION_NAME), store) def uses_redirect(self): """Return true if openid request will be handled with redirect or HTML content will be returned. """ return self.openid_request().shouldSendRedirect() def openid_request(self, params=None): """Return openid request""" try: return self.consumer().begin(url_add_parameters(self.openid_url(), params)) except DiscoveryFailure as err: raise AuthException(self, 'OpenID discovery error: {0}'.format( err )) def openid_url(self): """Return service provider URL. This base class is generic accepting a POST parameter that specifies provider URL.""" if self.URL: return self.URL elif OPENID_ID_FIELD in self.data: return self.data[OPENID_ID_FIELD] else: raise AuthMissingParameter(self, OPENID_ID_FIELD) class OpenIdConnectAssociation(object): """ Use Association model to save the nonce by force. """ def __init__(self, handle, secret='', issued=0, lifetime=0, assoc_type=''): self.handle = handle # as nonce self.secret = secret.encode() # not use self.issued = issued # not use self.lifetime = lifetime # not use self.assoc_type = assoc_type # as state class OpenIdConnectAuth(BaseOAuth2): """ Base class for Open ID Connect backends. Currently only the code response type is supported. """ ID_TOKEN_ISSUER = None ID_TOKEN_MAX_AGE = 600 DEFAULT_SCOPE = ['openid'] EXTRA_DATA = ['id_token', 'refresh_token', ('sub', 'id')] # Set after access_token is retrieved id_token = None def auth_params(self, state=None): """Return extra arguments needed on auth process.""" params = super(OpenIdConnectAuth, self).auth_params(state) params['nonce'] = self.get_and_store_nonce( self.AUTHORIZATION_URL, state ) return params def auth_complete_params(self, state=None): params = super(OpenIdConnectAuth, self).auth_complete_params(state) # Add a nonce to the request so that to help counter CSRF params['nonce'] = self.get_and_store_nonce( self.ACCESS_TOKEN_URL, state ) return params def get_and_store_nonce(self, url, state): # Create a nonce nonce = self.strategy.random_string(64) # Store the nonce association = OpenIdConnectAssociation(nonce, assoc_type=state) self.strategy.storage.association.store(url, association) return nonce def get_nonce(self, nonce): try: return self.strategy.storage.association.get( server_url=self.ACCESS_TOKEN_URL, handle=nonce )[0] except IndexError: pass def remove_nonce(self, nonce_id): self.strategy.storage.association.remove([nonce_id]) def validate_and_return_id_token(self, id_token): """ Validates the id_token according to the steps at http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation. """ client_id, _client_secret = self.get_key_and_secret() decode_kwargs = { 'algorithms': ['HS256'], 'audience': client_id, 'issuer': self.ID_TOKEN_ISSUER, 'key': self.setting('ID_TOKEN_DECRYPTION_KEY'), 'options': { 'verify_signature': True, 'verify_exp': True, 'verify_iat': True, 'verify_aud': True, 'verify_iss': True, 'require_exp': True, 'require_iat': True, }, } decode_kwargs.update(self.setting('ID_TOKEN_JWT_DECODE_KWARGS', {})) try: # Decode the JWT and raise an error if the secret is invalid or # the response has expired. id_token = jwt_decode(id_token, **decode_kwargs) except InvalidTokenError as err: raise AuthTokenError(self, err) # Verify the token was issued within a specified amount of time iat_leeway = self.setting('ID_TOKEN_MAX_AGE', self.ID_TOKEN_MAX_AGE) utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple()) if id_token['iat'] < (utc_timestamp - iat_leeway): raise AuthTokenError(self, 'Incorrect id_token: iat') # Validate the nonce to ensure the request was not modified nonce = id_token.get('nonce') if not nonce: raise AuthTokenError(self, 'Incorrect id_token: nonce') nonce_obj = self.get_nonce(nonce) if nonce_obj: self.remove_nonce(nonce_obj.id) else: raise AuthTokenError(self, 'Incorrect id_token: nonce') return id_token def request_access_token(self, *args, **kwargs): """ Retrieve the access token. Also, validate the id_token and store it (temporarily). """ response = self.get_json(*args, **kwargs) self.id_token = self.validate_and_return_id_token(response['id_token']) return response python-social-auth-0.2.21/social/backends/podio.py0000644000175500017550000000233612754357263021610 0ustar debacledebacle""" Podio OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/podio.html """ from social.backends.oauth import BaseOAuth2 class PodioOAuth2(BaseOAuth2): """Podio OAuth authentication backend""" name = 'podio' AUTHORIZATION_URL = 'https://podio.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://podio.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('access_token', 'access_token'), ('token_type', 'token_type'), ('expires_in', 'expires'), ('refresh_token', 'refresh_token'), ] def get_user_id(self, details, response): return response['ref']['id'] def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( response['profile']['name'] ) return { 'username': 'user_%d' % response['user']['user_id'], 'email': response['user']['mail'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, } def user_data(self, access_token, *args, **kwargs): return self.get_json('https://api.podio.com/user/status', headers={'Authorization': 'OAuth2 ' + access_token}) python-social-auth-0.2.21/social/backends/justgiving.py0000644000175500017550000000415112754357263022664 0ustar debacledebaclefrom requests.auth import HTTPBasicAuth from social.utils import handle_http_errors from social.backends.oauth import BaseOAuth2 class JustGivingOAuth2(BaseOAuth2): """Just Giving OAuth authentication backend""" name = 'justgiving' ID_KEY = 'userId' AUTHORIZATION_URL = 'https://identity.justgiving.com/connect/authorize' ACCESS_TOKEN_URL = 'https://identity.justgiving.com/connect/token' ACCESS_TOKEN_METHOD = 'POST' USER_DATA_URL = 'https://api.justgiving.com/v1/account' DEFAULT_SCOPE = ['openid', 'account', 'profile', 'email', 'fundraise'] def get_user_details(self, response): """Return user details from Just Giving account""" fullname, first_name, last_name = self.get_user_names( '', response.get('firstName'), response.get('lastName')) return { 'username': response.get('email'), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" key, secret = self.get_key_and_secret() return self.get_json(self.USER_DATA_URL, headers={ 'Authorization': 'Bearer {0}'.format(access_token), 'Content-Type': 'application/json', 'x-application-key': secret, 'x-api-key': key }) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" state = self.validate_state() self.process_error(self.data) key, secret = self.get_key_and_secret() response = self.request_access_token( self.access_token_url(), data=self.auth_complete_params(state), headers=self.auth_headers(), auth=HTTPBasicAuth(key, secret), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) python-social-auth-0.2.21/social/backends/fitbit.py0000644000175500017550000000442612754357263021761 0ustar debacledebacle""" Fitbit OAuth backend, docs at: http://psa.matiasaguirre.net/docs/backends/fitbit.html """ import base64 from social.backends.oauth import BaseOAuth1, BaseOAuth2 class FitbitOAuth1(BaseOAuth1): """Fitbit OAuth1 authentication backend""" name = 'fitbit' AUTHORIZATION_URL = 'https://www.fitbit.com/oauth/authorize' REQUEST_TOKEN_URL = 'https://api.fitbit.com/oauth/request_token' ACCESS_TOKEN_URL = 'https://api.fitbit.com/oauth/access_token' ID_KEY = 'encodedId' EXTRA_DATA = [('encodedId', 'id'), ('displayName', 'username')] def get_user_details(self, response): """Return user details from Fitbit account""" return {'username': response.get('displayName'), 'email': ''} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.fitbit.com/1/user/-/profile.json', auth=self.oauth_auth(access_token) )['user'] class FitbitOAuth2(BaseOAuth2): """Fitbit OAuth2 authentication backend""" name = 'fitbit' AUTHORIZATION_URL = 'https://www.fitbit.com/oauth2/authorize' ACCESS_TOKEN_URL = 'https://api.fitbit.com/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REFRESH_TOKEN_URL = 'https://api.fitbit.com/oauth2/token' DEFAULT_SCOPE = ['profile'] ID_KEY = 'encodedId' REDIRECT_STATE = False EXTRA_DATA = [('expires_in', 'expires'), ('refresh_token', 'refresh_token', True), ('encodedId', 'id'), ('displayName', 'username')] def get_user_details(self, response): """Return user details from Fitbit account""" return {'username': response.get('displayName'), 'email': ''} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" auth_header = {"Authorization": "Bearer %s" % access_token} return self.get_json( 'https://api.fitbit.com/1/user/-/profile.json', headers=auth_header )['user'] def auth_headers(self): return { 'Authorization': 'Basic {0}'.format(base64.urlsafe_b64encode( ('{0}:{1}'.format(*self.get_key_and_secret()).encode()) )) } python-social-auth-0.2.21/social/backends/classlink.py0000644000175500017550000000271712754357263022464 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 class ClasslinkOAuth(BaseOAuth2): """ Classlink OAuth authentication backend. Docs: https://developer.classlink.com/docs/oauth2-workflow """ name = 'classlink' AUTHORIZATION_URL = 'https://launchpad.classlink.com/oauth2/v2/auth' ACCESS_TOKEN_URL = 'https://launchpad.classlink.com/oauth2/v2/token' ACCESS_TOKEN_METHOD = 'POST' DEFAULT_SCOPE = ['profile'] REDIRECT_STATE = False SCOPE_SEPARATOR = ' ' def get_user_id(self, details, response): """Return user unique id provided by service""" return response['UserId'] def get_user_details(self, response): """Return user details from Classlink account""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('FirstName'), last_name=response.get('LastName') ) return { 'username': response.get('Email') or response.get('LoginId'), 'email': response.get('Email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, } def user_data(self, token, *args, **kwargs): """Loads user data from service""" url = 'https://nodeapi.classlink.com/v2/my/info' auth_header = {"Authorization": "Bearer %s" % token} try: return self.get_json(url, headers=auth_header) except ValueError: return None python-social-auth-0.2.21/social/backends/exacttarget.py0000644000175500017550000000767512754357263023024 0ustar debacledebacle""" ExactTarget OAuth support. Support Authentication from IMH using JWT token and pre-shared key. Requires package pyjwt """ from datetime import timedelta, datetime import jwt from social.exceptions import AuthFailed, AuthCanceled from social.backends.oauth import BaseOAuth2 class ExactTargetOAuth2(BaseOAuth2): name = 'exacttarget' def get_user_details(self, response): """Use the email address of the user, suffixed by _et""" user = response.get('token', {})\ .get('request', {})\ .get('user', {}) if 'email' in user: user['username'] = user['email'] return user def get_user_id(self, details, response): """ Create a user ID from the ET user ID. Uses details rather than the default response, as only the token is available in response. details is much richer: { 'expiresIn': 1200, 'username': 'example@example.com', 'refreshToken': '1234567890abcdef', 'internalOauthToken': 'jwttoken.......', 'oauthToken': 'yetanothertoken', 'id': 123456, 'culture': 'en-US', 'timezone': { 'shortName': 'CST', 'offset': -6.0, 'dst': False, 'longName': '(GMT-06:00) Central Time (No Daylight Saving)' }, 'email': 'example@example.com' } """ return '{0}'.format(details.get('id')) def uses_redirect(self): return False def auth_url(self): return None def process_error(self, data): if data.get('error'): error = self.data.get('error_description') or self.data['error'] raise AuthFailed(self, error) def do_auth(self, token, *args, **kwargs): dummy, secret = self.get_key_and_secret() try: # Decode the token, using the Application Signature from settings decoded = jwt.decode(token, secret, algorithms=['HS256']) except jwt.DecodeError: # Wrong signature, fail authentication raise AuthCanceled(self) kwargs.update({'response': {'token': decoded}, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" token = self.data.get('jwt', {}) if not token: raise AuthFailed(self, 'Authentication Failed') return self.do_auth(token, *args, **kwargs) def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Load extra details from the JWT token""" data = { 'id': details.get('id'), 'email': details.get('email'), # OAuth token, for use with legacy SOAP API calls: # http://bit.ly/13pRHfo 'internalOauthToken': details.get('internalOauthToken'), # Token for use with the Application ClientID for the FUEL API 'oauthToken': details.get('oauthToken'), # If the token has expired, use the FUEL API to get a new token see # http://bit.ly/10v1K5l and http://bit.ly/11IbI6F - set legacy=1 'refreshToken': details.get('refreshToken'), } # The expiresIn value determines how long the tokens are valid for. # Take a bit off, then convert to an int timestamp expiresSeconds = details.get('expiresIn', 0) - 30 expires = datetime.utcnow() + timedelta(seconds=expiresSeconds) data['expires'] = (expires - datetime(1970, 1, 1)).total_seconds() if response.get('token'): token = response['token'] org = token.get('request', {}).get('organization') if org: data['stack'] = org.get('stackKey') data['enterpriseId'] = org.get('enterpriseId') return data python-social-auth-0.2.21/social/backends/aol.py0000644000175500017550000000033612754357263021247 0ustar debacledebacle""" AOL OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/aol.html """ from social.backends.open_id import OpenIdAuth class AOLOpenId(OpenIdAuth): name = 'aol' URL = 'http://openid.aol.com' python-social-auth-0.2.21/social/backends/changetip.py0000644000175500017550000000156612754357263022444 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 class ChangeTipOAuth2(BaseOAuth2): """ChangeTip OAuth authentication backend https://www.changetip.com/api """ name = 'changetip' AUTHORIZATION_URL = 'https://www.changetip.com/o/authorize/' ACCESS_TOKEN_URL = 'https://www.changetip.com/o/token/' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ' ' def get_user_details(self, response): """Return user details from ChangeTip account""" return { 'username': response['username'], 'email': response.get('email', ''), 'first_name': '', 'last_name': '', } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.changetip.com/v2/me/', params={ 'access_token': access_token }) python-social-auth-0.2.21/social/backends/facebook.py0000644000175500017550000002012112754357263022237 0ustar debacledebacle""" Facebook OAuth2 and Canvas Application backends, docs at: http://psa.matiasaguirre.net/docs/backends/facebook.html """ import hmac import time import json import base64 import hashlib from social.utils import parse_qs, constant_time_compare, handle_http_errors from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthException, AuthCanceled, AuthUnknownError, \ AuthMissingParameter class FacebookOAuth2(BaseOAuth2): """Facebook OAuth2 authentication backend""" name = 'facebook' RESPONSE_TYPE = None SCOPE_SEPARATOR = ',' AUTHORIZATION_URL = 'https://www.facebook.com/v2.7/dialog/oauth' ACCESS_TOKEN_URL = 'https://graph.facebook.com/v2.7/oauth/access_token' REVOKE_TOKEN_URL = 'https://graph.facebook.com/v2.7/{uid}/permissions' REVOKE_TOKEN_METHOD = 'DELETE' USER_DATA_URL = 'https://graph.facebook.com/v2.7/me' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Facebook account""" fullname, first_name, last_name = self.get_user_names( response.get('name', ''), response.get('first_name', ''), response.get('last_name', '') ) return {'username': response.get('username', response.get('name')), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" params = self.setting('PROFILE_EXTRA_PARAMS', {}) params['access_token'] = access_token if self.setting('APPSECRET_PROOF', True): _, secret = self.get_key_and_secret() params['appsecret_proof'] = hmac.new( secret.encode('utf8'), msg=access_token.encode('utf8'), digestmod=hashlib.sha256 ).hexdigest() return self.get_json(self.USER_DATA_URL, params=params) def process_error(self, data): super(FacebookOAuth2, self).process_error(data) if data.get('error_code'): raise AuthCanceled(self, data.get('error_message') or data.get('error_code')) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) if not self.data.get('code'): raise AuthMissingParameter(self, 'code') state = self.validate_state() key, secret = self.get_key_and_secret() response = self.request(self.ACCESS_TOKEN_URL, params={ 'client_id': key, 'redirect_uri': self.get_redirect_uri(state), 'client_secret': secret, 'code': self.data['code'] }) # API v2.3 returns a JSON, according to the documents linked at issue # #592, but it seems that this needs to be enabled(?), otherwise the # usual querystring type response is returned. try: response = response.json() except ValueError: response = parse_qs(response.text) access_token = response['access_token'] return self.do_auth(access_token, response, *args, **kwargs) def process_refresh_token_response(self, response, *args, **kwargs): return parse_qs(response.content) def refresh_token_params(self, token, *args, **kwargs): client_id, client_secret = self.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, response=None, *args, **kwargs): response = response or {} 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' in response: data['expires'] = response['expires'] kwargs.update({'backend': self, 'response': data}) return self.strategy.authenticate(*args, **kwargs) def revoke_token_url(self, token, uid): return self.REVOKE_TOKEN_URL.format(uid=uid) def revoke_token_params(self, token, uid): return {'access_token': token} def process_revoke_token_response(self, response): return super(FacebookOAuth2, self).process_revoke_token_response( response ) and response.content == 'true' class FacebookAppOAuth2(FacebookOAuth2): """Facebook Application Authentication support""" name = 'facebook-app' def uses_redirect(self): return False def auth_complete(self, *args, **kwargs): access_token = None response = {} if 'signed_request' in self.data: key, secret = self.get_key_and_secret() response = self.load_signed_request(self.data['signed_request']) if 'user_id' not in response and 'oauth_token' not in response: raise AuthException(self) if response is not None: access_token = response.get('access_token') or \ response.get('oauth_token') or \ self.data.get('access_token') if access_token is None: if self.data.get('error') == 'access_denied': raise AuthCanceled(self) else: raise AuthException(self) return self.do_auth(access_token, response, *args, **kwargs) def auth_html(self): key, secret = self.get_key_and_secret() namespace = self.setting('NAMESPACE', None) scope = self.setting('SCOPE', '') if scope: scope = self.SCOPE_SEPARATOR.join(scope) ctx = { 'FACEBOOK_APP_NAMESPACE': namespace or key, 'FACEBOOK_KEY': key, 'FACEBOOK_EXTENDED_PERMISSIONS': scope, 'FACEBOOK_COMPLETE_URI': self.redirect_uri, } tpl = self.setting('LOCAL_HTML', 'facebook.html') return self.strategy.render_html(tpl=tpl, context=ctx) def load_signed_request(self, signed_request): def base64_url_decode(data): data = data.encode('ascii') data += '='.encode('ascii') * (4 - (len(data) % 4)) return base64.urlsafe_b64decode(data) key, secret = self.get_key_and_secret() try: sig, payload = signed_request.split('.', 1) except ValueError: pass # ignore if can't split on dot else: sig = base64_url_decode(sig) payload_json_bytes = base64_url_decode(payload) data = json.loads(payload_json_bytes.decode('utf-8', 'replace')) expected_sig = hmac.new(secret.encode('ascii'), msg=payload.encode('ascii'), digestmod=hashlib.sha256).digest() # allow the signed_request to function for upto 1 day if constant_time_compare(sig, expected_sig) and \ data['issued_at'] > (time.time() - 86400): return data class Facebook2OAuth2(FacebookOAuth2): """Facebook OAuth2 authentication backend using Facebook Open Graph 2.0""" AUTHORIZATION_URL = 'https://www.facebook.com/v2.0/dialog/oauth' ACCESS_TOKEN_URL = 'https://graph.facebook.com/v2.0/oauth/access_token' REVOKE_TOKEN_URL = 'https://graph.facebook.com/v2.0/{uid}/permissions' USER_DATA_URL = 'https://graph.facebook.com/v2.0/me' class Facebook2AppOAuth2(Facebook2OAuth2, FacebookAppOAuth2): pass python-social-auth-0.2.21/social/backends/arcgis.py0000644000175500017550000000206212754357263021742 0ustar debacledebacle""" ArcGIS OAuth2 backend """ from social.backends.oauth import BaseOAuth2 class ArcGISOAuth2(BaseOAuth2): name = 'arcgis' ID_KEY = 'username' AUTHORIZATION_URL = 'https://www.arcgis.com/sharing/rest/oauth2/authorize' ACCESS_TOKEN_URL = 'https://www.arcgis.com/sharing/rest/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('expires_in', 'expires_in') ] def get_user_details(self, response): """Return user details from ArcGIS account""" return {'username': response.get('username'), 'email': response.get('email'), 'fullname': response.get('fullName'), 'first_name': response.get('firstName'), 'last_name': response.get('lastName')} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://www.arcgis.com/sharing/rest/community/self', params={ 'token': access_token, 'f': 'json' } ) python-social-auth-0.2.21/social/backends/khanacademy.py0000644000175500017550000001165712754357263022751 0ustar debacledebacle""" Khan Academy OAuth backend, docs at: https://github.com/Khan/khan-api/wiki/Khan-Academy-API-Authentication """ import six from oauthlib.oauth1 import SIGNATURE_HMAC, SIGNATURE_TYPE_QUERY from requests_oauthlib import OAuth1 from social.backends.oauth import BaseOAuth1 from social.p3 import urlencode class BrowserBasedOAuth1(BaseOAuth1): """Browser based mechanism OAuth authentication, fill the needed parameters to communicate properly with authentication service. REQUEST_TOKEN_URL Request token URL (opened in web browser) ACCESS_TOKEN_URL Access token URL """ REQUEST_TOKEN_URL = '' OAUTH_TOKEN_PARAMETER_NAME = 'oauth_token' REDIRECT_URI_PARAMETER_NAME = 'redirect_uri' ACCESS_TOKEN_URL = '' def auth_url(self): """Return redirect url""" return self.unauthorized_token_request() def get_unauthorized_token(self): return self.strategy.request_data() def unauthorized_token_request(self): """Return request for unauthorized token (first stage)""" params = self.request_token_extra_arguments() params.update(self.get_scope_argument()) key, secret = self.get_key_and_secret() # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() auth = OAuth1( key, secret, callback_uri=self.get_redirect_uri(state), decoding=decoding, signature_method=SIGNATURE_HMAC, signature_type=SIGNATURE_TYPE_QUERY ) url = self.REQUEST_TOKEN_URL + '?' + urlencode(params) url, _, _ = auth.client.sign(url) return url def oauth_auth(self, token=None, oauth_verifier=None): key, secret = self.get_key_and_secret() oauth_verifier = oauth_verifier or self.data.get('oauth_verifier') token = token or {} # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() return OAuth1(key, secret, resource_owner_key=token.get('oauth_token'), resource_owner_secret=token.get('oauth_token_secret'), callback_uri=self.get_redirect_uri(state), verifier=oauth_verifier, signature_method=SIGNATURE_HMAC, signature_type=SIGNATURE_TYPE_QUERY, decoding=decoding) class KhanAcademyOAuth1(BrowserBasedOAuth1): """ Class used for autorising with Khan Academy. Flow of Khan Academy is a bit different than most OAuth 1.0 and consinsts of the following steps: 1. Create signed params to attach to the REQUEST_TOKEN_URL 2. Redirect user to the REQUEST_TOKEN_URL that will respond with oauth_secret, oauth_token, oauth_verifier that should be used with ACCESS_TOKEN_URL 3. Go to ACCESS_TOKEN_URL and grab oauth_token_secret. Note that we don't use the AUTHORIZATION_URL. REQUEST_TOKEN_URL requires the following arguments: oauth_consumer_key - Your app's consumer key oauth_nonce - Random 64-bit, unsigned number encoded as an ASCII string in decimal format. The nonce/timestamp pair should always be unique. oauth_version - OAuth version used by your app. Must be "1.0" for now. oauth_signature - String generated using the referenced signature method. oauth_signature_method - Signature algorithm (currently only support "HMAC-SHA1") oauth_timestamp - Integer representing the time the request is sent. The timestamp should be expressed in number of seconds after January 1, 1970 00:00:00 GMT. oauth_callback (optional) - URL to redirect to after request token is received and authorized by the user's chosen identity provider. """ name = 'khanacademy-oauth1' ID_KEY = 'user_id' REQUEST_TOKEN_URL = 'http://www.khanacademy.org/api/auth/request_token' ACCESS_TOKEN_URL = 'https://www.khanacademy.org/api/auth/access_token' REDIRECT_URI_PARAMETER_NAME = 'oauth_callback' USER_DATA_URL = 'https://www.khanacademy.org/api/v1/user' EXTRA_DATA = [('user_id', 'user_id')] def get_user_details(self, response): """Return user details from Khan Academy account""" return { 'username': response.get('key_email'), 'email': response.get('key_email'), 'fullname': '', 'first_name': '', 'last_name': '', 'user_id': response.get('user_id') } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" auth = self.oauth_auth(access_token) url, _, _ = auth.client.sign(self.USER_DATA_URL) return self.get_json(url) python-social-auth-0.2.21/social/backends/skyrock.py0000644000175500017550000000225312754357263022161 0ustar debacledebacle""" Skyrock OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/skyrock.html """ from social.backends.oauth import BaseOAuth1 class SkyrockOAuth(BaseOAuth1): """Skyrock OAuth authentication backend""" name = 'skyrock' ID_KEY = 'id_user' AUTHORIZATION_URL = 'https://api.skyrock.com/v2/oauth/authenticate' REQUEST_TOKEN_URL = 'https://api.skyrock.com/v2/oauth/initiate' ACCESS_TOKEN_URL = 'https://api.skyrock.com/v2/oauth/token' EXTRA_DATA = [('id', 'id')] def get_user_details(self, response): """Return user details from Skyrock account""" fullname, first_name, last_name = self.get_user_names( first_name=response['firstname'], last_name=response['name'] ) return {'username': response['username'], 'email': response['email'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token): """Return user data provided""" return self.get_json('https://api.skyrock.com/v2/user/get.json', auth=self.oauth_auth(access_token)) python-social-auth-0.2.21/social/backends/utils.py0000644000175500017550000000610612754357263021635 0ustar debacledebaclefrom social.exceptions import MissingBackend from social.backends.base import BaseAuth from social.utils import module_member, user_is_authenticated # Cache for discovered backends. BACKENDSCACHE = {} def load_backends(backends, force_load=False): """ Load backends defined on SOCIAL_AUTH_AUTHENTICATION_BACKENDS, backends will be imported and cached on BACKENDSCACHE. The key in that dict will be the backend name, and the value is the backend class. Only subclasses of BaseAuth (and sub-classes) are considered backends. Previously there was a BACKENDS attribute expected on backends modules, this is not needed anymore since it's enough with the AUTHENTICATION_BACKENDS setting. BACKENDS was used because backends used to be split on two classes the authentication backend and another class that dealt with the auth mechanism with the provider, those classes are joined now. A force_load boolean argument is also provided so that get_backend below can retry a requested backend that may not yet be discovered. """ global BACKENDSCACHE if force_load: BACKENDSCACHE = {} if not BACKENDSCACHE: for auth_backend in backends: backend = module_member(auth_backend) if issubclass(backend, BaseAuth): BACKENDSCACHE[backend.name] = backend return BACKENDSCACHE def get_backend(backends, name): """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] except KeyError: # Reload BACKENDS to ensure a missing backend hasn't been missed load_backends(backends, force_load=True) try: return BACKENDSCACHE[name] except KeyError: raise MissingBackend(name) def user_backends_data(user, backends, storage): """ Will return backends data for given user, the return value will have the following keys: 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 'associated' list is empty, and there's no difference between 'not_associated' and 'backends'. """ available = list(load_backends(backends).keys()) values = {'associated': [], 'not_associated': available, 'backends': available} if user_is_authenticated(user): associated = storage.user.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 python-social-auth-0.2.21/social/backends/twitter.py0000644000175500017550000000274112754357263022200 0ustar debacledebacle""" Twitter OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/twitter.html """ from social.backends.oauth import BaseOAuth1 from social.exceptions import AuthCanceled class TwitterOAuth(BaseOAuth1): """Twitter OAuth authentication backend""" name = 'twitter' EXTRA_DATA = [('id', 'id')] REQUEST_TOKEN_METHOD = 'POST' ACCESS_TOKEN_METHOD = 'POST' AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authenticate' REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token' ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token' REDIRECT_STATE = True def process_error(self, data): if 'denied' in data: raise AuthCanceled(self) else: super(TwitterOAuth, self).process_error(data) def get_user_details(self, response): """Return user details from Twitter account""" fullname, first_name, last_name = self.get_user_names(response['name']) return {'username': response['screen_name'], 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( 'https://api.twitter.com/1.1/account/verify_credentials.json', params={'include_email': 'true'}, auth=self.oauth_auth(access_token) ) python-social-auth-0.2.21/social/backends/odnoklassniki.py0000644000175500017550000001556312754357263023354 0ustar debacledebacle""" Odnoklassniki OAuth2 and Iframe Application backends, docs at: http://psa.matiasaguirre.net/docs/backends/odnoklassnikiru.html """ from hashlib import md5 from social.p3 import unquote from social.backends.base import BaseAuth from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthFailed class OdnoklassnikiOAuth2(BaseOAuth2): """Odnoklassniki authentication backend""" name = 'odnoklassniki-oauth2' ID_KEY = 'uid' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ';' AUTHORIZATION_URL = 'https://connect.ok.ru/oauth/authorize' ACCESS_TOKEN_URL = 'https://api.ok.ru/oauth/token.do' EXTRA_DATA = [('refresh_token', 'refresh_token'), ('expires_in', 'expires')] def get_user_details(self, response): """Return user details from Odnoklassniki request""" fullname, first_name, last_name = self.get_user_names( fullname=unquote(response['name']), first_name=unquote(response['first_name']), last_name=unquote(response['last_name']) ) return { 'username': response['uid'], 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Return user data from Odnoklassniki REST API""" data = {'access_token': access_token, 'method': 'users.getCurrentUser'} key, secret = self.get_key_and_secret() public_key = self.setting('PUBLIC_NAME') return odnoklassniki_api(self, data, 'https://api.ok.ru/', public_key, secret, 'oauth') class OdnoklassnikiApp(BaseAuth): """Odnoklassniki iframe app authentication backend""" name = 'odnoklassniki-app' ID_KEY = 'uid' def extra_data(self, user, uid, response, details=None, *args, **kwargs): return dict([(key, value) for key, value in response.items() if key in response['extra_data_list']]) def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( fullname=unquote(response['name']), first_name=unquote(response['first_name']), last_name=unquote(response['last_name']) ) return { 'username': response['uid'], 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def auth_complete(self, *args, **kwargs): self.verify_auth_sig() response = self.get_response() fields = ('uid', 'first_name', 'last_name', 'name') + \ self.setting('EXTRA_USER_DATA_LIST', ()) data = { 'method': 'users.getInfo', 'uids': '{0}'.format(response['logged_user_id']), 'fields': ','.join(fields), } client_key, client_secret = self.get_key_and_secret() public_key = self.setting('PUBLIC_NAME') details = odnoklassniki_api(self, 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 = self.setting('EXTRA_AUTH_DATA_LIST', ('api_server', 'apiconnection', 'session_key', 'authorized', 'session_secret_key')) for field in auth_data_fields: details[field] = response[field] details['extra_data_list'] = fields + auth_data_fields kwargs.update({'backend': self, 'response': details}) else: raise AuthFailed(self, 'Cannot get user details: API error') return self.strategy.authenticate(*args, **kwargs) def get_auth_sig(self): secret_key = self.setting('SECRET') hash_source = '{0:s}{1:s}{2:s}'.format(self.data['logged_user_id'], self.data['session_key'], secret_key) return md5(hash_source.encode('utf-8')).hexdigest() def get_response(self): fields = ('logged_user_id', 'api_server', 'application_key', 'session_key', 'session_secret_key', 'authorized', 'apiconnection') return dict((name, self.data[name]) for name in fields if name in self.data) def verify_auth_sig(self): correct_key = self.get_auth_sig() key = self.data['auth_sig'].lower() if correct_key != key: raise AuthFailed(self, 'Wrong authorization key') def odnoklassniki_oauth_sig(data, client_secret): """ Calculates signature of request data access_token value must be included Algorithm is described at https://apiok.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).encode('utf-8') ).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).encode('utf-8')).hexdigest() def odnoklassniki_iframe_sig(data, client_secret_or_session_secret): """ Calculates signature as described at: https://apiok.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).encode('utf-8') ).hexdigest() def odnoklassniki_api(backend, data, api_url, public_key, client_secret, request_type='oauth'): """Calls Odnoklassniki REST API method https://apiok.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(backend, msg.format(request_type)) return backend.get_json(api_url + 'fb.do', params=data) python-social-auth-0.2.21/social/backends/live.py0000644000175500017550000000303512754357263021432 0ustar debacledebacle""" Live OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/live.html """ from social.backends.oauth import BaseOAuth2 class LiveOAuth2(BaseOAuth2): name = 'live' AUTHORIZATION_URL = 'https://login.live.com/oauth20_authorize.srf' ACCESS_TOKEN_URL = 'https://login.live.com/oauth20_token.srf' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' DEFAULT_SCOPE = ['wl.basic', 'wl.emails'] EXTRA_DATA = [ ('id', 'id'), ('access_token', 'access_token'), ('authentication_token', 'authentication_token'), ('refresh_token', 'refresh_token'), ('expires_in', 'expires'), ('email', 'email'), ('first_name', 'first_name'), ('last_name', 'last_name'), ('token_type', 'token_type'), ] REDIRECT_STATE = False def get_user_details(self, response): """Return user details from Live Connect account""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('first_name'), last_name=response.get('last_name') ) return {'username': response.get('name'), 'email': response.get('emails', {}).get('account', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://apis.live.net/v5.0/me', params={ 'access_token': access_token }) python-social-auth-0.2.21/social/backends/docker.py0000644000175500017550000000311712754357263021743 0ustar debacledebacle""" Docker Hub OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/docker.html """ from social.backends.oauth import BaseOAuth2 class DockerOAuth2(BaseOAuth2): name = 'docker' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://hub.docker.com/api/v1.1/o/authorize/' ACCESS_TOKEN_URL = 'https://hub.docker.com/api/v1.1/o/token/' REFRESH_TOKEN_URL = 'https://hub.docker.com/api/v1.1/o/token/' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('user_id', 'user_id'), ('email', 'email'), ('full_name', 'fullname'), ('location', 'location'), ('url', 'url'), ('company', 'company'), ('gravatar_email', 'gravatar_email'), ] def get_user_details(self, response): """Return user details from Docker Hub account""" fullname, first_name, last_name = self.get_user_names( response.get('full_name') or response.get('username') or '' ) return { 'username': response.get('username'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': response.get('email', '') } def user_data(self, access_token, *args, **kwargs): """Grab user profile information from Docker Hub.""" username = kwargs['response']['username'] return self.get_json( 'https://hub.docker.com/api/v1.1/users/%s/' % username, headers={'Authorization': 'Bearer %s' % access_token} ) python-social-auth-0.2.21/social/backends/stripe.py0000644000175500017550000000355512754357263022010 0ustar debacledebacle""" Stripe OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/stripe.html """ from social.backends.oauth import BaseOAuth2 class StripeOAuth2(BaseOAuth2): """Stripe OAuth2 authentication backend""" name = 'stripe' ID_KEY = 'stripe_user_id' AUTHORIZATION_URL = 'https://connect.stripe.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://connect.stripe.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False 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': ''} def auth_params(self, state=None): client_id, client_secret = self.get_key_and_secret() params = {'response_type': 'code', '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'] } def auth_headers(self): client_id, client_secret = self.get_key_and_secret() return {'Accept': 'application/json', 'Authorization': 'Bearer {0}'.format(client_secret)} def refresh_token_params(self, refresh_token, *args, **kwargs): return {'refresh_token': refresh_token, 'grant_type': 'refresh_token'} python-social-auth-0.2.21/social/backends/naver.py0000644000175500017550000000373712754357263021617 0ustar debacledebaclefrom xml.dom import minidom from social.backends.oauth import BaseOAuth2 class NaverOAuth2(BaseOAuth2): """Naver OAuth authentication backend""" name = 'naver' AUTHORIZATION_URL = 'https://nid.naver.com/oauth2.0/authorize' ACCESS_TOKEN_URL = 'https://nid.naver.com/oauth2.0/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('id', 'id'), ] def get_user_id(self, details, response): return response.get('id') def get_user_details(self, response): """Return user details from Naver account""" return { 'username': response.get('username'), 'email': response.get('email'), 'fullname': response.get('username'), } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" response = self.request( 'https://openapi.naver.com/v1/nid/getUserProfile.xml', headers={ 'Authorization': 'Bearer {0}'.format(access_token), 'Content_Type': 'text/xml' } ) dom = minidom.parseString(response.text.encode('utf-8').strip()) return { 'id': self._dom_value(dom, 'id'), 'email': self._dom_value(dom, 'email'), 'username': self._dom_value(dom, 'name'), 'nickname': self._dom_value(dom, 'nickname'), 'gender': self._dom_value(dom, 'gender'), 'age': self._dom_value(dom, 'age'), 'birthday': self._dom_value(dom, 'birthday'), 'profile_image': self._dom_value(dom, 'profile_image') } def auth_headers(self): client_id, client_secret = self.get_key_and_secret() return { 'grant_type': 'authorization_code', 'code': self.data.get('code'), 'client_id': client_id, 'client_secret': client_secret, } def _dom_value(self, dom, key): return dom.getElementsByTagName(key)[0].childNodes[0].data python-social-auth-0.2.21/social/backends/__init__.py0000644000175500017550000000000012754357263022217 0ustar debacledebaclepython-social-auth-0.2.21/social/backends/evernote.py0000644000175500017550000000525012754357263022323 0ustar debacledebacle""" Evernote OAuth1 backend (with sandbox mode support), docs at: http://psa.matiasaguirre.net/docs/backends/evernote.html """ from requests import HTTPError from social.exceptions import AuthCanceled from social.backends.oauth import BaseOAuth1 class EvernoteOAuth(BaseOAuth1): """ 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' ID_KEY = 'edam_userId' AUTHORIZATION_URL = 'https://www.evernote.com/OAuth.action' REQUEST_TOKEN_URL = 'https://www.evernote.com/oauth' ACCESS_TOKEN_URL = 'https://www.evernote.com/oauth' EXTRA_DATA = [ ('access_token', 'access_token'), ('oauth_token', 'oauth_token'), ('edam_noteStoreUrl', 'store_url'), ('edam_expires', 'expires') ] def get_user_details(self, response): """Return user details from Evernote account""" return {'username': response['edam_userId'], 'email': ''} def access_token(self, token): """Return request for access token value""" try: return self.get_querystring(self.ACCESS_TOKEN_URL, auth=self.oauth_auth(token)) except HTTPError as err: # Evernote returns a 401 error when AuthCanceled if err.response.status_code == 401: raise AuthCanceled(self, response=err.response) else: raise def extra_data(self, user, uid, response, details=None, *args, **kwargs): data = super(EvernoteOAuth, self).extra_data(user, uid, response, details, *args, **kwargs) # Evernote returns expiration timestamp in milliseconds, so it needs to # be normalized. if 'expires' in data: data['expires'] = int(data['expires']) / 1000 return data def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return access_token.copy() class EvernoteSandboxOAuth(EvernoteOAuth): name = 'evernote-sandbox' AUTHORIZATION_URL = 'https://sandbox.evernote.com/OAuth.action' REQUEST_TOKEN_URL = 'https://sandbox.evernote.com/oauth' ACCESS_TOKEN_URL = 'https://sandbox.evernote.com/oauth' python-social-auth-0.2.21/social/backends/meetup.py0000644000175500017550000000225212754357263021772 0ustar debacledebacle""" Meetup OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/meetup.html """ from social.backends.oauth import BaseOAuth2 class MeetupOAuth2(BaseOAuth2): """Meetup OAuth2 authentication backend""" name = 'meetup' AUTHORIZATION_URL = 'https://secure.meetup.com/oauth2/authorize' ACCESS_TOKEN_URL = 'https://secure.meetup.com/oauth2/access' ACCESS_TOKEN_METHOD = 'POST' DEFAULT_SCOPE = ['basic'] SCOPE_SEPARATOR = ',' REDIRECT_STATE = False STATE_PARAMETER = 'state' def get_user_details(self, response): """Return user details from Meetup account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': response.get('username'), 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.meetup.com/2/member/self', params={'access_token': access_token}) python-social-auth-0.2.21/social/backends/box.py0000644000175500017550000000422112754357263021261 0ustar debacledebacle""" Box.net OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/box.html """ from social.backends.oauth import BaseOAuth2 class BoxOAuth2(BaseOAuth2): """Box.net OAuth authentication backend""" name = 'box' AUTHORIZATION_URL = 'https://www.box.com/api/oauth2/authorize' ACCESS_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'https://www.box.com/api/oauth2/token' REVOKE_TOKEN_URL = 'https://www.box.com/api/oauth2/revoke' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('id', 'id'), ('expires', 'expires'), ] def do_auth(self, access_token, response=None, *args, **kwargs): response = response or {} data = self.user_data(access_token) data['access_token'] = response.get('access_token') data['refresh_token'] = response.get('refresh_token') data['expires'] = response.get('expires_in') kwargs.update({'backend': self, 'response': data}) return self.strategy.authenticate(*args, **kwargs) def get_user_details(self, response): """Return user details Box.net account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': response.get('login'), 'email': response.get('login') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" params = self.setting('PROFILE_EXTRA_PARAMS', {}) params['access_token'] = access_token return self.get_json('https://api.box.com/2.0/users/me', params=params) def refresh_token(self, token, *args, **kwargs): params = self.refresh_token_params(token, *args, **kwargs) request = self.request(self.REFRESH_TOKEN_URL or self.ACCESS_TOKEN_URL, data=params, headers=self.auth_headers(), method='POST') return self.process_refresh_token_response(request, *args, **kwargs) python-social-auth-0.2.21/social/backends/github.py0000644000175500017550000000755212754357263021765 0ustar debacledebacle""" Github OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/github.html """ from requests import HTTPError from six.moves.urllib.parse import urljoin from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthFailed class GithubOAuth2(BaseOAuth2): """Github OAuth authentication backend""" name = 'github' API_URL = 'https://api.github.com/' AUTHORIZATION_URL = 'https://github.com/login/oauth/authorize' ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires'), ('login', 'login') ] def api_url(self): return self.API_URL def get_user_details(self, response): """Return user details from Github account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': response.get('login'), 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" data = self._user_data(access_token) if not data.get('email'): try: emails = self._user_data(access_token, '/emails') except (HTTPError, ValueError, TypeError): emails = [] if emails: email = emails[0] primary_emails = [ e for e in emails if not isinstance(e, dict) or e.get('primary') ] if primary_emails: email = primary_emails[0] if isinstance(email, dict): email = email.get('email', '') data['email'] = email return data def _user_data(self, access_token, path=None): url = urljoin(self.api_url(), 'user{0}'.format(path or '')) return self.get_json(url, params={'access_token': access_token}) class GithubMemberOAuth2(GithubOAuth2): no_member_string = '' def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" user_data = super(GithubMemberOAuth2, self).user_data( access_token, *args, **kwargs ) try: self.request(self.member_url(user_data), params={ 'access_token': access_token }) except HTTPError as err: # if the user is a member of the organization, response code # will be 204, see http://bit.ly/ZS6vFl if err.response.status_code != 204: raise AuthFailed(self, 'User doesn\'t belong to the organization') return user_data def member_url(self, user_data): raise NotImplementedError('Implement in subclass') class GithubOrganizationOAuth2(GithubMemberOAuth2): """Github OAuth2 authentication backend for organizations""" name = 'github-org' no_member_string = 'User doesn\'t belong to the organization' def member_url(self, user_data): return urljoin( self.api_url(), 'orgs/{org}/members/{username}'.format( org=self.setting('NAME'), username=user_data.get('login') ) ) class GithubTeamOAuth2(GithubMemberOAuth2): """Github OAuth2 authentication backend for teams""" name = 'github-team' no_member_string = 'User doesn\'t belong to the team' def member_url(self, user_data): return urljoin( self.api_url(), 'teams/{team_id}/members/{username}'.format( team_id=self.setting('ID'), username=user_data.get('login') ) ) python-social-auth-0.2.21/social/backends/livejournal.py0000644000175500017550000000173712754357263023034 0ustar debacledebacle""" LiveJournal OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/livejournal.html """ from social.p3 import urlsplit from social.backends.open_id import OpenIdAuth from social.exceptions import AuthMissingParameter class LiveJournalOpenId(OpenIdAuth): """LiveJournal OpenID authentication backend""" name = 'livejournal' def get_user_details(self, response): """Generate username from identity url""" values = super(LiveJournalOpenId, self).get_user_details(response) values['username'] = values.get('username') or \ urlsplit(response.identity_url)\ .netloc.split('.', 1)[0] return values def openid_url(self): """Returns LiveJournal authentication URL""" if not self.data.get('openid_lj_user'): raise AuthMissingParameter(self, 'openid_lj_user') return 'http://{0}.livejournal.com'.format(self.data['openid_lj_user']) python-social-auth-0.2.21/social/backends/dropbox.py0000644000175500017550000000452112754357263022151 0ustar debacledebacle""" Dropbox OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/dropbox.html """ from social.backends.oauth import BaseOAuth1, BaseOAuth2 class DropboxOAuth(BaseOAuth1): """Dropbox OAuth authentication backend""" name = 'dropbox' ID_KEY = 'uid' AUTHORIZATION_URL = 'https://www.dropbox.com/1/oauth/authorize' REQUEST_TOKEN_URL = 'https://api.dropbox.com/1/oauth/request_token' REQUEST_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'https://api.dropbox.com/1/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_URI_PARAMETER_NAME = 'oauth_callback' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Dropbox account""" fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': str(response.get('uid')), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.dropbox.com/1/account/info', auth=self.oauth_auth(access_token)) class DropboxOAuth2(BaseOAuth2): name = 'dropbox-oauth2' ID_KEY = 'uid' AUTHORIZATION_URL = 'https://www.dropbox.com/1/oauth2/authorize' ACCESS_TOKEN_URL = 'https://api.dropbox.com/1/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('uid', 'username'), ] def get_user_details(self, response): """Return user details from Dropbox account""" fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': str(response.get('uid')), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.dropbox.com/1/account/info', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.21/social/backends/drip.py0000644000175500017550000000151212754357263021427 0ustar debacledebacle""" Drip OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/drip.html """ from social.backends.oauth import BaseOAuth2 class DripOAuth(BaseOAuth2): name = 'drip' AUTHORIZATION_URL = 'https://www.getdrip.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://www.getdrip.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' def get_user_id(self, details, response): return details['email'] def get_user_details(self, response): return {'email': response['users'][0]['email'], 'fullname': response['users'][0]['name'], 'username': response['users'][0]['email']} def user_data(self, access_token, *args, **kwargs): return self.get_json('https://api.getdrip.com/v2/user', headers={ 'Authorization': 'Bearer %s' % access_token }) python-social-auth-0.2.21/social/backends/mineid.py0000644000175500017550000000235112754357263021740 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 class MineIDOAuth2(BaseOAuth2): """MineID OAuth2 authentication backend""" name = 'mineid' _AUTHORIZATION_URL = '%(scheme)s://%(host)s/oauth/authorize' _ACCESS_TOKEN_URL = '%(scheme)s://%(host)s/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ] def get_user_details(self, response): """Return user details""" return {'email': response.get('email'), 'username': response.get('email')} def user_data(self, access_token, *args, **kwargs): return self._user_data(access_token) def _user_data(self, access_token, path=None): url = '%(scheme)s://%(host)s/api/user' % self.get_mineid_url_params() return self.get_json(url, params={'access_token': access_token}) @property def AUTHORIZATION_URL(self): return self._AUTHORIZATION_URL % self.get_mineid_url_params() @property def ACCESS_TOKEN_URL(self): return self._ACCESS_TOKEN_URL % self.get_mineid_url_params() def get_mineid_url_params(self): return { 'host': self.setting('HOST', 'www.mineid.org'), 'scheme': self.setting('SCHEME', 'https'), } python-social-auth-0.2.21/social/backends/edmodo.py0000644000175500017550000000214112754357263021737 0ustar debacledebacle""" Edmodo OAuth2 Sign-in backend, docs at: http://psa.matiasaguirre.net/docs/backends/edmodo.html """ from social.backends.oauth import BaseOAuth2 class EdmodoOAuth2(BaseOAuth2): """Edmodo OAuth2""" name = 'edmodo' AUTHORIZATION_URL = 'https://api.edmodo.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://api.edmodo.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' def get_user_details(self, response): """Return user details from Edmodo account""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('first_name'), last_name=response.get('last_name') ) return { 'username': response.get('username'), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Loads user data from Edmodo""" return self.get_json( 'https://api.edmodo.com/users/me', params={'access_token': access_token} ) python-social-auth-0.2.21/social/backends/steam.py0000644000175500017550000000302112754357263021577 0ustar debacledebacle""" Steam OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/steam.html """ from social.backends.open_id import OpenIdAuth from social.exceptions import AuthFailed USER_INFO = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' class SteamOpenId(OpenIdAuth): name = 'steam' URL = 'https://steamcommunity.com/openid' 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): player = self.get_json(USER_INFO, params={ 'key': self.setting('API_KEY'), 'steamids': self._user_id(response) }) if len(player['response']['players']) > 0: player = player['response']['players'][0] details = {'username': player.get('personaname'), 'email': '', 'fullname': '', 'first_name': '', 'last_name': '', 'player': player} else: details = {} return details def consumer(self): # Steam seems to support stateless mode only, ignore store if not hasattr(self, '_consumer'): self._consumer = self.create_consumer() return self._consumer def _user_id(self, response): user_id = response.identity_url.rsplit('/', 1)[-1] if not user_id.isdigit(): raise AuthFailed(self, 'Missing Steam Id') return user_id python-social-auth-0.2.21/social/backends/suse.py0000644000175500017550000000071312754357263021452 0ustar debacledebacle""" Open Suse OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/suse.html """ from social.backends.open_id import OpenIdAuth class OpenSUSEOpenId(OpenIdAuth): name = 'opensuse' URL = 'https://www.opensuse.org/openid/user/' def get_user_id(self, details, response): """ Return user unique id provided by service. For openSUSE the nickname is original. """ return details['nickname'] python-social-auth-0.2.21/social/backends/soundcloud.py0000644000175500017550000000415412754357263022655 0ustar debacledebacle""" Soundcloud OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/soundcloud.html """ from social.p3 import urlencode from social.backends.oauth import BaseOAuth2 class SoundcloudOAuth2(BaseOAuth2): """Soundcloud OAuth authentication backend""" name = 'soundcloud' AUTHORIZATION_URL = 'https://soundcloud.com/connect' ACCESS_TOKEN_URL = 'https://api.soundcloud.com/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' REDIRECT_STATE = False EXTRA_DATA = [ ('id', 'id'), ('refresh_token', 'refresh_token'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Soundcloud account""" fullname, first_name, last_name = self.get_user_names( response.get('full_name') ) return {'username': response.get('username'), 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.soundcloud.com/me.json', params={'oauth_token': access_token}) def auth_url(self): """Return redirect url""" state = None 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.name + '_state' state = self.strategy.session_get(name) or self.state_token() self.strategy.session_set(name, state) params = self.auth_params(state) params.update(self.get_scope_argument()) params.update(self.auth_extra_arguments()) return self.AUTHORIZATION_URL + '?' + urlencode(params) python-social-auth-0.2.21/social/backends/deezer.py0000644000175500017550000000321212754357263021746 0ustar debacledebacle""" Deezer backend, docs at: https://developers.deezer.com/api/oauth https://developers.deezer.com/api/permissions """ from six.moves.urllib.parse import parse_qsl from social.backends.oauth import BaseOAuth2 class DeezerOAuth2(BaseOAuth2): """Deezer OAuth2 authentication backend""" name = 'deezer' ID_KEY = 'name' AUTHORIZATION_URL = 'https://connect.deezer.com/oauth/auth.php' ACCESS_TOKEN_URL = 'https://connect.deezer.com/oauth/access_token.php' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' REDIRECT_STATE = False def auth_complete_params(self, state=None): client_id, client_secret = self.get_key_and_secret() return { 'app_id': client_id, 'secret': client_secret, 'code': self.data.get('code') } def request_access_token(self, *args, **kwargs): response = self.request(*args, **kwargs) return dict(parse_qsl(response.text)) def get_user_details(self, response): """Return user details from Deezer account""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('firstname'), last_name=response.get('lastname') ) return {'username': response.get('name'), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('http://api.deezer.com/user/me', params={ 'access_token': access_token }) python-social-auth-0.2.21/social/backends/goclioeu.py0000644000175500017550000000074212754357263022303 0ustar debacledebaclefrom social.backends.goclio import GoClioOAuth2 class GoClioEuOAuth2(GoClioOAuth2): name = 'goclioeu' AUTHORIZATION_URL = 'https://app.goclio.eu/oauth/authorize/' ACCESS_TOKEN_URL = 'https://app.goclio.eu/oauth/token/' def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://app.goclio.eu/api/v2/users/who_am_i', params={'access_token': access_token} ) python-social-auth-0.2.21/social/backends/line.py0000644000175500017550000000607112754357263021425 0ustar debacledebacle# vim:fileencoding=utf-8 import requests import json from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthFailed from social.utils import handle_http_errors class LineOAuth2(BaseOAuth2): name = 'line' AUTHORIZATION_URL = 'https://access.line.me/dialog/oauth/weblogin' ACCESS_TOKEN_URL = 'https://api.line.me/v1/oauth/accessToken' BASE_API_URL = 'https://api.line.me' USER_INFO_URL = BASE_API_URL + '/v1/profile' ACCESS_TOKEN_METHOD = 'POST' STATE_PARAMETER = True REDIRECT_STATE = True ID_KEY = 'mid' EXTRA_DATA = [ ('mid', 'id'), ('expire', 'expire'), ('refreshToken', 'refresh_token') ] def auth_params(self, state=None): client_id, client_secret = self.get_key_and_secret() return { 'client_id': client_id, 'redirect_uri': self.get_redirect_uri(), 'response_type': self.RESPONSE_TYPE } def process_error(self, data): error_code = data.get('errorCode') or \ data.get('statusCode') or \ data.get('error') error_message = data.get('errorMessage') or \ data.get('statusMessage') or \ data.get('error_desciption') if error_code is not None or error_message is not None: raise AuthFailed(self, error_message or error_code) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" client_id, client_secret = self.get_key_and_secret() code = self.data.get('code') self.process_error(self.data) try: response = self.request_access_token( self.access_token_url(), method=self.ACCESS_TOKEN_METHOD, params={ 'requestToken': code, 'channelSecret': client_secret } ) self.process_error(response) return self.do_auth(response['accessToken'], response=response, *args, **kwargs) except requests.HTTPError as err: self.process_error(json.loads(err.response.content)) def get_user_details(self, response): response.update({ 'fullname': response.get('displayName'), 'picture_url': response.get('pictureUrl') }) return response def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response.""" return response.get(self.ID_KEY) def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" try: response = self.get_json( self.USER_INFO_URL, headers={ "Authorization": "Bearer {}".format(access_token) } ) self.process_error(response) return response except requests.HTTPError as err: self.process_error(err.response.json()) python-social-auth-0.2.21/social/backends/tumblr.py0000644000175500017550000000212412754357263021776 0ustar debacledebacle""" Tumblr OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/tumblr.html """ from social.utils import first from social.backends.oauth import BaseOAuth1 class TumblrOAuth(BaseOAuth1): name = 'tumblr' ID_KEY = 'name' AUTHORIZATION_URL = 'http://www.tumblr.com/oauth/authorize' REQUEST_TOKEN_URL = 'http://www.tumblr.com/oauth/request_token' REQUEST_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'http://www.tumblr.com/oauth/access_token' def get_user_id(self, details, response): return response['response']['user'][self.ID_KEY] 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']} blog = first(lambda blog: blog['primary'], user_info['blogs']) if blog: data['fullname'] = blog['title'] return data def user_data(self, access_token): return self.get_json('http://api.tumblr.com/v2/user/info', auth=self.oauth_auth(access_token)) python-social-auth-0.2.21/social/backends/spotify.py0000644000175500017550000000303612754357263022171 0ustar debacledebacle""" Spotify backend, docs at: https://developer.spotify.com/spotify-web-api/ https://developer.spotify.com/spotify-web-api/authorization-guide/ """ import base64 from social.backends.oauth import BaseOAuth2 class SpotifyOAuth2(BaseOAuth2): """Spotify OAuth2 authentication backend""" name = 'spotify' ID_KEY = 'id' AUTHORIZATION_URL = 'https://accounts.spotify.com/authorize' ACCESS_TOKEN_URL = 'https://accounts.spotify.com/api/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ' ' REDIRECT_STATE = False EXTRA_DATA = [ ('refresh_token', 'refresh_token'), ] def auth_headers(self): auth_str = '{0}:{1}'.format(*self.get_key_and_secret()) b64_auth_str = base64.urlsafe_b64encode(auth_str.encode()).decode() return { 'Authorization': 'Basic {0}'.format(b64_auth_str) } def get_user_details(self, response): """Return user details from Spotify account""" fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': response.get('id'), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.spotify.com/v1/me', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.21/social/backends/nk.py0000644000175500017550000000524312754357263021106 0ustar debacledebaclefrom urllib import urlencode import six from requests_oauthlib import OAuth1 from social.backends.oauth import BaseOAuth2 class NKOAuth2(BaseOAuth2): """NK OAuth authentication backend""" name = 'nk' AUTHORIZATION_URL = 'https://nk.pl/oauth2/login' ACCESS_TOKEN_URL = 'https://nk.pl/oauth2/token' SCOPE_SEPARATOR = ',' ACCESS_TOKEN_METHOD = 'POST' SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER' EXTRA_DATA = [ ('id', 'id'), ] def get_user_details(self, response): """Return user details from NK account""" entry = response['entry'] return { 'username': entry.get('displayName'), 'email': entry['emails'][0]['value'], 'first_name': entry.get('displayName').split(' ')[0], 'id': entry.get('id') } 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), 'scope': self.get_scope_argument() } def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response.""" return details.get(self.ID_KEY) def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'http://opensocial.nk-net.pl/v09/social/rest/people/@me?' + urlencode({ 'nk_token': access_token, 'fields': 'name,surname,avatar,localization,age,gender,emails,birthdate' }) return self.get_json( url, auth=self.oauth_auth(access_token) ) def oauth_auth(self, token=None, oauth_verifier=None, signature_type=SIGNATURE_TYPE_AUTH_HEADER): key, secret = self.get_key_and_secret() oauth_verifier = oauth_verifier or self.data.get('oauth_verifier') token = token or {} # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() return OAuth1(key, secret, resource_owner_key=None, resource_owner_secret=None, callback_uri=self.get_redirect_uri(state), verifier=oauth_verifier, signature_type=signature_type, decoding=decoding) python-social-auth-0.2.21/social/backends/amazon.py0000644000175500017550000000304112754357263021755 0ustar debacledebacle""" Amazon OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/amazon.html """ import ssl from social.backends.oauth import BaseOAuth2 class AmazonOAuth2(BaseOAuth2): name = 'amazon' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://www.amazon.com/ap/oa' ACCESS_TOKEN_URL = 'https://api.amazon.com/auth/o2/token' DEFAULT_SCOPE = ['profile'] REDIRECT_STATE = False ACCESS_TOKEN_METHOD = 'POST' SSL_PROTOCOL = ssl.PROTOCOL_TLSv1 EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('user_id', 'user_id'), ('postal_code', 'postal_code') ] def get_user_details(self, response): """Return user details from amazon account""" name = response.get('name') or '' fullname, first_name, last_name = self.get_user_names(name) return {'username': name, 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Grab user profile information from amazon.""" response = self.get_json('https://www.amazon.com/ap/user/profile', params={'access_token': access_token}) if 'Profile' in response: response = { 'user_id': response['Profile']['CustomerId'], 'name': response['Profile']['Name'], 'email': response['Profile']['PrimaryEmail'] } return response python-social-auth-0.2.21/social/backends/uber.py0000644000175500017550000000246212754357263021433 0ustar debacledebacle""" Uber OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/uber.html """ from social.backends.oauth import BaseOAuth2 class UberOAuth2(BaseOAuth2): name = 'uber' ID_KEY='uuid' SCOPE_SEPARATOR = ' ' AUTHORIZATION_URL = 'https://login.uber.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://login.uber.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' def auth_complete_credentials(self): return self.get_key_and_secret() def get_user_details(self, response): """Return user details from Uber account""" email = response.get('email', '') fullname, first_name, last_name = self.get_user_names( '', response.get('first_name', ''), response.get('last_name', '') ) return {'username': email, 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" response = kwargs.pop('response') return self.get_json('https://api.uber.com/v1/me', headers={ 'Authorization': '{0} {1}'.format(response.get('token_type'), access_token) }) python-social-auth-0.2.21/social/backends/weibo.py0000644000175500017550000000422412754357263021601 0ustar debacledebacle# coding:utf-8 # author:hepochen@gmail.com https://github.com/hepochen """ Weibo OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/weibo.html """ from social.backends.oauth import BaseOAuth2 class WeiboOAuth2(BaseOAuth2): """Weibo (of sina) OAuth authentication backend""" name = 'weibo' ID_KEY = 'uid' AUTHORIZATION_URL = 'https://api.weibo.com/oauth2/authorize' REQUEST_TOKEN_URL = 'https://api.weibo.com/oauth2/request_token' ACCESS_TOKEN_URL = 'https://api.weibo.com/oauth2/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('id', 'id'), ('name', 'username'), ('profile_image_url', 'profile_image_url'), ('gender', 'gender') ] 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= """ if self.setting('DOMAIN_AS_USERNAME'): username = response.get('domain', '') else: username = response.get('name', '') fullname, first_name, last_name = self.get_user_names( first_name=response.get('screen_name', '') ) return {'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def get_uid(self, access_token): """Return uid by access_token""" data = self.get_json( 'https://api.weibo.com/oauth2/get_token_info', method='POST', params={'access_token': access_token} ) return data['uid'] def user_data(self, access_token, response=None, *args, **kwargs): """Return user data""" # If user id was not retrieved in the response, then get it directly # from weibo get_token_info endpoint uid = response and response.get('uid') or self.get_uid(access_token) user_data = self.get_json( 'https://api.weibo.com/2/users/show.json', params={'access_token': access_token, 'uid': uid} ) user_data['uid'] = uid return user_data python-social-auth-0.2.21/social/backends/lastfm.py0000644000175500017550000000354012754357263021762 0ustar debacledebacleimport hashlib from social.utils import handle_http_errors from social.backends.base import BaseAuth class LastFmAuth(BaseAuth): """ Last.Fm authentication backend. Requires two settings: SOCIAL_AUTH_LASTFM_KEY SOCIAL_AUTH_LASTFM_SECRET Don't forget to set the Last.fm callback to something sensible like http://your.site/lastfm/complete """ name = 'lastfm' AUTH_URL = 'http://www.last.fm/api/auth/?api_key={api_key}' EXTRA_DATA = [ ('key', 'session_key') ] def auth_url(self): return self.AUTH_URL.format(api_key=self.setting('KEY')) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" key, secret = self.get_key_and_secret() token = self.data['token'] signature = hashlib.md5(''.join( ('api_key', key, 'methodauth.getSession', 'token', token, secret) ).encode()).hexdigest() response = self.get_json('http://ws.audioscrobbler.com/2.0/', data={ 'method': 'auth.getSession', 'api_key': key, 'token': token, 'api_sig': signature, 'format': 'json' }, method='POST') kwargs.update({'response': response['session'], 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response.""" return response.get('name') def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names(response['name']) return { 'username': response['name'], 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } python-social-auth-0.2.21/social/backends/twilio.py0000644000175500017550000000255012754357263022003 0ustar debacledebacle""" Twilio auth backend, docs at: http://psa.matiasaguirre.net/docs/backends/twilio.html """ from re import sub from social.p3 import urlencode from social.backends.base import BaseAuth class TwilioAuth(BaseAuth): name = 'twilio' ID_KEY = 'AccountSid' def get_user_details(self, response): """Return twilio details, Twilio only provides AccountSID as parameters.""" # /complete/twilio/?AccountSid=ACc65ea16c9ebd4d4684edf814995b27e return {'username': response['AccountSid'], 'email': '', 'fullname': '', 'first_name': '', 'last_name': ''} def auth_url(self): """Return authorization redirect url.""" key, secret = self.get_key_and_secret() callback = self.strategy.absolute_uri(self.redirect_uri) callback = sub(r'^https', 'http', callback) query = urlencode({'cb': callback}) return 'https://www.twilio.com/authorize/{0}?{1}'.format(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, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.21/social/backends/dribbble.py0000644000175500017550000000425112754357263022241 0ustar debacledebacle""" Dribbble OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/dribbble.html http://developer.dribbble.com/v1/oauth/ """ from social.backends.oauth import BaseOAuth2 class DribbbleOAuth2(BaseOAuth2): """Dribbble OAuth authentication backend""" name = 'dribbble' AUTHORIZATION_URL = 'https://dribbble.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://dribbble.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('id', 'id'), ('name', 'name'), ('html_url', 'html_url'), ('avatar_url', 'avatar_url'), ('bio', 'bio'), ('location', 'location'), ('links', 'links'), ('buckets_count', 'buckets_count'), ('comments_received_count', 'comments_received_count'), ('followers_count', 'followers_count'), ('followings_count', 'followings_count'), ('likes_count', 'likes_count'), ('likes_received_count', 'likes_received_count'), ('projects_count', 'projects_count'), ('rebounds_received_count', 'rebounds_received_count'), ('shots_count', 'shots_count'), ('teams_count', 'teams_count'), ('pro', 'pro'), ('buckets_url', 'buckets_url'), ('followers_url', 'followers_url'), ('following_url', 'following_url'), ('likes_url', 'shots_url'), ('teams_url', 'teams_url'), ('created_at', 'created_at'), ('updated_at', 'updated_at'), ] def get_user_details(self, response): """Return user details from Dribbble account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': response.get('username'), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.dribbble.com/v1/user', headers={ 'Authorization': 'Bearer {0}'.format(access_token) }) python-social-auth-0.2.21/social/backends/dailymotion.py0000644000175500017550000000156712754357263023033 0ustar debacledebacle""" DailyMotion OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/dailymotion.html """ from social.backends.oauth import BaseOAuth2 class DailymotionOAuth2(BaseOAuth2): """Dailymotion OAuth authentication backend""" name = 'dailymotion' EXTRA_DATA = [('id', 'id')] ID_KEY = 'username' AUTHORIZATION_URL = 'https://api.dailymotion.com/oauth/authorize' REQUEST_TOKEN_URL = 'https://api.dailymotion.com/oauth/token' ACCESS_TOKEN_URL = 'https://api.dailymotion.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' def get_user_details(self, response): return {'username': response.get('screenname')} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json('https://api.dailymotion.com/me/', params={'access_token': access_token}) python-social-auth-0.2.21/social/backends/runkeeper.py0000644000175500017550000000341712754357263022477 0ustar debacledebacle""" RunKeeper OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/runkeeper.html """ from social.backends.oauth import BaseOAuth2 class RunKeeperOAuth2(BaseOAuth2): """RunKeeper OAuth authentication backend""" name = 'runkeeper' AUTHORIZATION_URL = 'https://runkeeper.com/apps/authorize' ACCESS_TOKEN_URL = 'https://runkeeper.com/apps/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('userID', 'id'), ] def get_user_id(self, details, response): return response['userID'] def get_user_details(self, response): """Parse username from profile link""" username = None profile_url = response.get('profile') if len(profile_url): profile_url_parts = profile_url.split('http://runkeeper.com/user/') if len(profile_url_parts) > 1 and len(profile_url_parts[1]): username = profile_url_parts[1] fullname, first_name, last_name = self.get_user_names( fullname=response.get('name') ) return {'username': username, 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): # We need to use the /user endpoint to get the user id, the /profile # endpoint contains name, user name, location, gender user_data = self._user_data(access_token, '/user') profile_data = self._user_data(access_token, '/profile') return dict(user_data, **profile_data) def _user_data(self, access_token, path): url = 'https://api.runkeeper.com{0}'.format(path) return self.get_json(url, params={'access_token': access_token}) python-social-auth-0.2.21/social/backends/weixin.py0000644000175500017550000001414512754357263022002 0ustar debacledebacle# -*- coding: utf-8 -*- # author:duoduo3369@gmail.com https://github.com/duoduo369 """ Weixin OAuth2 backend """ import urllib from requests import HTTPError from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthCanceled, AuthUnknownError class WeixinOAuth2(BaseOAuth2): """Weixin OAuth authentication backend""" name = 'weixin' ID_KEY = 'openid' AUTHORIZATION_URL = 'https://open.weixin.qq.com/connect/qrconnect' ACCESS_TOKEN_URL = 'https://api.weixin.qq.com/sns/oauth2/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('nickname', 'username'), ('headimgurl', 'profile_image_url'), ] def get_user_details(self, response): """Return user details from Weixin. API URL is: https://api.weixin.qq.com/sns/userinfo """ if self.setting('DOMAIN_AS_USERNAME'): username = response.get('domain', '') else: username = response.get('nickname', '') return { 'username': username, 'profile_image_url': response.get('headimgurl', '') } def user_data(self, access_token, *args, **kwargs): data = self.get_json('https://api.weixin.qq.com/sns/userinfo', params={ 'access_token': access_token, 'openid': kwargs['response']['openid'] }) nickname = data.get('nickname') if nickname: # weixin api has some encode bug, here need handle data['nickname'] = nickname.encode( 'raw_unicode_escape' ).decode('utf-8') return data def auth_params(self, state=None): appid, secret = self.get_key_and_secret() params = { 'appid': appid, '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_complete_params(self, state=None): appid, secret = self.get_key_and_secret() return { 'grant_type': 'authorization_code', # request auth code 'code': self.data.get('code', ''), # server response code 'appid': appid, 'secret': secret, 'redirect_uri': self.get_redirect_uri(state) } def refresh_token_params(self, token, *args, **kwargs): appid, secret = self.get_key_and_secret() return { 'refresh_token': token, 'grant_type': 'refresh_token', 'appid': appid, 'secret': secret } def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) try: response = self.request_access_token( self.ACCESS_TOKEN_URL, data=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) except HTTPError as err: if err.response.status_code == 400: raise AuthCanceled(self, response=err.response) else: raise except KeyError: raise AuthUnknownError(self) if 'errcode' in response: raise AuthCanceled(self) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) class WeixinOAuth2APP(WeixinOAuth2): """ Weixin OAuth authentication backend Can't use in web, only in weixin app """ name = 'weixinapp' ID_KEY = 'openid' AUTHORIZATION_URL = 'https://open.weixin.qq.com/connect/oauth2/authorize' ACCESS_TOKEN_URL = 'https://api.weixin.qq.com/sns/oauth2/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def auth_url(self): 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, that way we can still verify the # request if the provider doesn't implement the state parameter. # Reuse token if any. name = self.name + '_state' state = self.strategy.session_get(name) if state is None: state = self.state_token() self.strategy.session_set(name, state) else: state = None params = self.auth_params(state) params.update(self.get_scope_argument()) params.update(self.auth_extra_arguments()) params = urllib.urlencode(sorted(params.items())) return '{}#wechat_redirect'.format( self.AUTHORIZATION_URL + '?' + params ) def auth_complete_params(self, state=None): appid, secret = self.get_key_and_secret() return { 'grant_type': 'authorization_code', # request auth code 'code': self.data.get('code', ''), # server response code 'appid': appid, 'secret': secret, } def validate_state(self): return None def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) try: response = self.request_access_token( self.ACCESS_TOKEN_URL, data=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) except HTTPError as err: if err.response.status_code == 400: raise AuthCanceled(self) else: raise except KeyError: raise AuthUnknownError(self) if 'errcode' in response: raise AuthCanceled(self) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) python-social-auth-0.2.21/social/backends/fedora.py0000644000175500017550000000041712754357263021734 0ustar debacledebacle""" Fedora OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/fedora.html """ from social.backends.open_id import OpenIdAuth class FedoraOpenId(OpenIdAuth): name = 'fedora' URL = 'https://id.fedoraproject.org' USERNAME_KEY = 'nickname' python-social-auth-0.2.21/social/backends/angel.py0000644000175500017550000000205112754357263021556 0ustar debacledebacle""" Angel OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/angel.html """ from social.backends.oauth import BaseOAuth2 class AngelOAuth2(BaseOAuth2): name = 'angel' AUTHORIZATION_URL = 'https://angel.co/api/oauth/authorize/' ACCESS_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'https://angel.co/api/oauth/token/' REDIRECT_STATE = False def get_user_details(self, response): """Return user details from Angel account""" username = response['angellist_url'].split('/')[-1] email = response.get('email', '') fullname, first_name, last_name = self.get_user_names(response['name']) return {'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.angel.co/1/me/', params={ 'access_token': access_token }) python-social-auth-0.2.21/social/backends/mendeley.py0000644000175500017550000000430512754357263022276 0ustar debacledebacle""" Mendeley OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/mendeley.html """ from social.backends.oauth import BaseOAuth1, BaseOAuth2 class MendeleyMixin(object): SCOPE_SEPARATOR = '+' EXTRA_DATA = [('profile_id', 'profile_id'), ('name', 'name'), ('bio', 'bio')] def get_user_id(self, details, response): return response['id'] def get_user_details(self, response): """Return user details from Mendeley account""" profile_id = response['id'] name = response['display_name'] bio = response['link'] return {'profile_id': profile_id, 'name': name, 'bio': bio} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" values = self.get_user_data(access_token) values.update(values) return values def get_user_data(self, access_token): raise NotImplementedError('Implement in subclass') class MendeleyOAuth(MendeleyMixin, BaseOAuth1): name = 'mendeley' AUTHORIZATION_URL = 'http://api.mendeley.com/oauth/authorize/' REQUEST_TOKEN_URL = 'http://api.mendeley.com/oauth/request_token/' ACCESS_TOKEN_URL = 'http://api.mendeley.com/oauth/access_token/' def get_user_data(self, access_token): return self.get_json( 'http://api.mendeley.com/oapi/profiles/info/me/', auth=self.oauth_auth(access_token) ) class MendeleyOAuth2(MendeleyMixin, BaseOAuth2): name = 'mendeley-oauth2' AUTHORIZATION_URL = 'https://api-oauth2.mendeley.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://api-oauth2.mendeley.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' DEFAULT_SCOPE = ['all'] REDIRECT_STATE = False EXTRA_DATA = MendeleyMixin.EXTRA_DATA + [ ('refresh_token', 'refresh_token'), ('expires_in', 'expires_in'), ('token_type', 'token_type'), ] def get_user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.mendeley.com/profiles/me/', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.21/social/backends/google.py0000644000175500017550000001755712754357263021765 0ustar debacledebacle""" Google OpenId, OAuth2, OAuth1, Google+ Sign-in backends, docs at: http://psa.matiasaguirre.net/docs/backends/google.html """ from social.utils import handle_http_errors from social.backends.open_id import OpenIdAuth, OpenIdConnectAuth from social.backends.oauth import BaseOAuth2, BaseOAuth1 from social.exceptions import AuthMissingParameter class BaseGoogleAuth(object): def get_user_id(self, details, response): """Use google email as unique id""" if self.setting('USE_UNIQUE_USER_ID', False): return response['id'] else: return details['email'] def get_user_details(self, response): """Return user details from Google API account""" if 'email' in response: email = response['email'] elif 'emails' in response: email = response['emails'][0]['value'] else: email = '' if isinstance(response.get('name'), dict): names = response.get('name') or {} name, given_name, family_name = ( response.get('displayName', ''), names.get('givenName', ''), names.get('familyName', '') ) else: name, given_name, family_name = ( response.get('name', ''), response.get('given_name', ''), response.get('family_name', '') ) fullname, first_name, last_name = self.get_user_names( name, given_name, family_name ) return {'username': email.split('@', 1)[0], 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} class BaseGoogleOAuth2API(BaseGoogleAuth): def get_scope(self): """Return list with needed access scope""" scope = self.setting('SCOPE', []) if not self.setting('IGNORE_DEFAULT_SCOPE', False): default_scope = [] if self.setting('USE_DEPRECATED_API', False): default_scope = self.DEPRECATED_DEFAULT_SCOPE else: default_scope = self.DEFAULT_SCOPE scope = scope + (default_scope or []) return scope def user_data(self, access_token, *args, **kwargs): """Return user data from Google API""" if self.setting('USE_DEPRECATED_API', False): url = 'https://www.googleapis.com/oauth2/v1/userinfo' else: url = 'https://www.googleapis.com/plus/v1/people/me' return self.get_json(url, params={ 'access_token': access_token, 'alt': 'json' }) def revoke_token_params(self, token, uid): return {'token': token} def revoke_token_headers(self, token, uid): return {'Content-type': 'application/json'} class GoogleOAuth2(BaseGoogleOAuth2API, BaseOAuth2): """Google OAuth2 authentication backend""" name = 'google-oauth2' REDIRECT_STATE = False AUTHORIZATION_URL = 'https://accounts.google.com/o/oauth2/auth' ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REVOKE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/revoke' REVOKE_TOKEN_METHOD = 'GET' # The order of the default scope is important DEFAULT_SCOPE = ['openid', 'email', 'profile'] DEPRECATED_DEFAULT_SCOPE = [ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile' ] EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('expires_in', 'expires'), ('token_type', 'token_type', True) ] class GooglePlusAuth(BaseGoogleOAuth2API, BaseOAuth2): name = 'google-plus' REDIRECT_STATE = False STATE_PARAMETER = False AUTHORIZATION_URL = 'https://accounts.google.com/o/oauth2/auth' ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REVOKE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/revoke' REVOKE_TOKEN_METHOD = 'GET' DEFAULT_SCOPE = [ 'https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/plus.me', ] DEPRECATED_DEFAULT_SCOPE = [ 'https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile' ] EXTRA_DATA = [ ('id', 'user_id'), ('refresh_token', 'refresh_token', True), ('expires_in', 'expires'), ('access_type', 'access_type', True), ('code', 'code') ] def auth_complete_params(self, state=None): params = super(GooglePlusAuth, self).auth_complete_params(state) if self.data.get('access_token'): # Don't add postmessage if this is plain server-side workflow params['redirect_uri'] = 'postmessage' return params @handle_http_errors def auth_complete(self, *args, **kwargs): if 'access_token' in self.data: # Client-side workflow token = self.data.get('access_token') response = self.get_json( 'https://www.googleapis.com/oauth2/v1/tokeninfo', params={'access_token': token} ) self.process_error(response) return self.do_auth(token, response=response, *args, **kwargs) elif 'code' in self.data: # Server-side workflow response = self.request_access_token( self.ACCESS_TOKEN_URL, data=self.auth_complete_params(), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) else: raise AuthMissingParameter(self, 'access_token or code') class GoogleOAuth(BaseGoogleAuth, BaseOAuth1): """Google OAuth authorization mechanism""" name = 'google-oauth' 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' DEFAULT_SCOPE = ['https://www.googleapis.com/auth/userinfo#email'] def user_data(self, access_token, *args, **kwargs): """Return user data from Google API""" return self.get_querystring( 'https://www.googleapis.com/userinfo/email', auth=self.oauth_auth(access_token) ) def get_key_and_secret(self): """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 """ key_secret = super(GoogleOAuth, self).get_key_and_secret() if key_secret == (None, None): key_secret = ('anonymous', 'anonymous') return key_secret class GoogleOpenId(OpenIdAuth): name = 'google' URL = 'https://www.google.com/accounts/o8/id' 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 """ return details['email'] class GoogleOpenIdConnect(GoogleOAuth2, OpenIdConnectAuth): name = 'google-openidconnect' ID_TOKEN_ISSUER = "accounts.google.com" def user_data(self, access_token, *args, **kwargs): """Return user data from Google API""" return self.get_json( 'https://www.googleapis.com/plus/v1/people/me/openIdConnect', params={'access_token': access_token, 'alt': 'json'} ) python-social-auth-0.2.21/social/backends/readability.py0000644000175500017550000000250712754357263022767 0ustar debacledebacle""" Readability OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/readability.html """ from social.backends.oauth import BaseOAuth1 READABILITY_API = 'https://www.readability.com/api/rest/v1' class ReadabilityOAuth(BaseOAuth1): """Readability OAuth authentication backend""" name = 'readability' ID_KEY = 'username' AUTHORIZATION_URL = '{0}/oauth/authorize/'.format(READABILITY_API) REQUEST_TOKEN_URL = '{0}/oauth/request_token/'.format(READABILITY_API) ACCESS_TOKEN_URL = '{0}/oauth/access_token/'.format(READABILITY_API) 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): fullname, first_name, last_name = self.get_user_names( first_name=response['first_name'], last_name=response['last_name'] ) return {'username': response['username'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token): return self.get_json(READABILITY_API + '/users/_current', auth=self.oauth_auth(access_token)) python-social-auth-0.2.21/social/backends/mixcloud.py0000644000175500017550000000170112754357263022315 0ustar debacledebacle""" Mixcloud OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/mixcloud.html """ from social.backends.oauth import BaseOAuth2 class MixcloudOAuth2(BaseOAuth2): name = 'mixcloud' ID_KEY = 'username' AUTHORIZATION_URL = 'https://www.mixcloud.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://www.mixcloud.com/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names(response['name']) return {'username': response['username'], 'email': None, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): return self.get_json('https://api.mixcloud.com/me/', params={'access_token': access_token, 'alt': 'json'}) python-social-auth-0.2.21/social/backends/professionali.py0000644000175500017550000000356312754357263023356 0ustar debacledebacle# -*- coding: utf-8 -*- """ Professionaly OAuth 2.0 support. This contribution adds support for professionaly.ru OAuth 2.0. Username is retrieved from the identity returned by server. """ from time import time from social.utils import parse_qs from social.backends.oauth import BaseOAuth2 class ProfessionaliOAuth2(BaseOAuth2): name = 'professionali' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://api.professionali.ru/oauth/authorize.html' ACCESS_TOKEN_URL = 'https://api.professionali.ru/oauth/getToken.json' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('avatar_big', 'avatar_big'), ('link', 'link') ] def get_user_details(self, response): first_name, last_name = map(response.get, ('firstname', 'lastname')) email = '' if self.setting('FAKE_EMAIL'): email = '{0}@professionali.ru'.format(time()) return { 'username': '{0}_{1}'.format(last_name, first_name), 'first_name': first_name, 'last_name': last_name, 'email': email } def user_data(self, access_token, response, *args, **kwargs): url = 'https://api.professionali.ru/v6/users/get.json' fields = list(set(['firstname', 'lastname', 'avatar_big', 'link'] + self.setting('EXTRA_DATA', []))) params = { 'fields': ','.join(fields), 'access_token': access_token, 'ids[]': response['user_id'] } try: return self.get_json(url, params)[0] except (TypeError, KeyError, IOError, ValueError, IndexError): return None def get_json(self, url, *args, **kwargs): return self.request(url, verify=False, *args, **kwargs).json() def get_querystring(self, url, *args, **kwargs): return parse_qs(self.request(url, verify=False, *args, **kwargs).text) python-social-auth-0.2.21/social/backends/mapmyfitness.py0000644000175500017550000000276512754357263023223 0ustar debacledebacle""" MapMyFitness OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/mapmyfitness.html """ from social.backends.oauth import BaseOAuth2 class MapMyFitnessOAuth2(BaseOAuth2): """MapMyFitness OAuth authentication backend""" name = 'mapmyfitness' AUTHORIZATION_URL = 'https://www.mapmyfitness.com/v7.0/oauth2/authorize' ACCESS_TOKEN_URL = \ 'https://oauth2-api.mapmyapi.com/v7.0/oauth2/access_token' REQUEST_TOKEN_METHOD = 'POST' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('refresh_token', 'refresh_token'), ] def auth_headers(self): key = self.get_key_and_secret()[0] return { 'Api-Key': key } def get_user_id(self, details, response): return response['id'] def get_user_details(self, response): first = response.get('first_name', '') last = response.get('last_name', '') full = (first + last).strip() return { 'username': response['username'], 'email': response['email'], 'fullname': full, 'first_name': first, 'last_name': last, } def user_data(self, access_token, *args, **kwargs): key = self.get_key_and_secret()[0] url = 'https://oauth2-api.mapmyapi.com/v7.0/user/self/' headers = { 'Authorization': 'Bearer {0}'.format(access_token), 'Api-Key': key } return self.get_json(url, headers=headers) python-social-auth-0.2.21/social/backends/jawbone.py0000644000175500017550000000535212754357263022124 0ustar debacledebacle""" Jawbone OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/jawbone.html """ from social.utils import handle_http_errors from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthCanceled, AuthUnknownError class JawboneOAuth2(BaseOAuth2): name = 'jawbone' AUTHORIZATION_URL = 'https://jawbone.com/auth/oauth2/auth' ACCESS_TOKEN_URL = 'https://jawbone.com/auth/oauth2/token' SCOPE_SEPARATOR = ' ' REDIRECT_STATE = False def get_user_id(self, details, response): return response['data']['xid'] def get_user_details(self, response): """Return user details from Jawbone account""" data = response['data'] fullname, first_name, last_name = self.get_user_names( first_name=data.get('first', ''), last_name=data.get('last', '') ) return { 'username': first_name + ' ' + last_name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'dob': data.get('dob', ''), 'gender': data.get('gender', ''), 'height': data.get('height', ''), 'weight': data.get('weight', '') } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://jawbone.com/nudge/api/users/@me', headers={'Authorization': 'Bearer ' + access_token}, ) def process_error(self, data): error = data.get('error') if error: if error == 'access_denied': raise AuthCanceled(self) else: raise AuthUnknownError(self, 'Jawbone error was {0}'.format( error )) return super(JawboneOAuth2, self).process_error(data) 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, } @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) response = self.request_access_token( self.ACCESS_TOKEN_URL, params=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) python-social-auth-0.2.21/social/backends/coursera.py0000644000175500017550000000270312754357263022317 0ustar debacledebacle""" Coursera OAuth2 backend, docs at: https://tech.coursera.org/app-platform/oauth2/ """ from social.backends.oauth import BaseOAuth2 class CourseraOAuth2(BaseOAuth2): """Coursera OAuth2 authentication backend""" name = 'coursera' ID_KEY = 'username' AUTHORIZATION_URL = 'https://accounts.coursera.org/oauth2/v1/auth' ACCESS_TOKEN_URL = 'https://accounts.coursera.org/oauth2/v1/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False SCOPE_SEPARATOR = ',' DEFAULT_SCOPE = ['view_profile'] def _get_username_from_response(self, response): elements = response.get('elements', []) for element in elements: if 'id' in element: return element.get('id') return None def get_user_details(self, response): """Return user details from Coursera account""" return {'username': self._get_username_from_response(response)} def get_user_id(self, details, response): """Return a username prepared in get_user_details as uid""" return details.get(self.ID_KEY) def user_data(self, access_token, *args, **kwargs): """Load user data from the service""" return self.get_json( 'https://api.coursera.org/api/externalBasicProfiles.v1?q=me', headers=self.get_auth_header(access_token) ) def get_auth_header(self, access_token): return {'Authorization': 'Bearer {0}'.format(access_token)} python-social-auth-0.2.21/social/backends/moves.py0000644000175500017550000000176312754357263021632 0ustar debacledebacle""" Moves OAuth2 backend, docs at: https://dev.moves-app.com/docs/authentication Written by Avi Alkalay Certified to work with Django 1.6 """ from social.backends.oauth import BaseOAuth2 class MovesOAuth2(BaseOAuth2): """Moves OAuth authentication backend""" name = 'moves' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://api.moves-app.com/oauth/v1/authorize' ACCESS_TOKEN_URL = 'https://api.moves-app.com/oauth/v1/access_token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('expires_in', 'expires'), ] def get_user_details(self, response): """Return user details Moves account""" return {'username': response.get('user_id')} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.moves-app.com/api/1.1/user/profile', params={'access_token': access_token}) python-social-auth-0.2.21/social/backends/xing.py0000644000175500017550000000275712754357263021452 0ustar debacledebacle""" XING OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/xing.html """ from social.backends.oauth import BaseOAuth1 class XingOAuth(BaseOAuth1): """Xing OAuth authentication backend""" name = 'xing' AUTHORIZATION_URL = 'https://api.xing.com/v1/authorize' REQUEST_TOKEN_URL = 'https://api.xing.com/v1/request_token' ACCESS_TOKEN_URL = 'https://api.xing.com/v1/access_token' SCOPE_SEPARATOR = '+' EXTRA_DATA = [ ('id', 'id'), ('user_id', 'user_id') ] def get_user_details(self, response): """Return user details from Xing account""" email = response.get('email', '') fullname, first_name, last_name = self.get_user_names( first_name=response['first_name'], last_name=response['last_name'] ) return {'username': first_name + last_name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" profile = self.get_json( 'https://api.xing.com/v1/users/me.json', auth=self.oauth_auth(access_token) )['users'][0] return { 'user_id': profile['id'], 'id': profile['id'], 'first_name': profile['first_name'], 'last_name': profile['last_name'], 'email': profile['active_email'] } python-social-auth-0.2.21/social/backends/qq.py0000644000175500017550000000422612754357263021117 0ustar debacledebacle""" Created on May 13, 2014 @author: Yong Zhang (zyfyfe@gmail.com) """ import json from social.utils import parse_qs from social.backends.oauth import BaseOAuth2 class QQOAuth2(BaseOAuth2): name = 'qq' ID_KEY = 'openid' AUTHORIZE_URL = 'https://graph.qq.com/oauth2.0/authorize' ACCESS_TOKEN_URL = 'https://graph.qq.com/oauth2.0/token' AUTHORIZATION_URL = 'https://graph.qq.com/oauth2.0/authorize' OPENID_URL = 'https://graph.qq.com/oauth2.0/me' REDIRECT_STATE = False EXTRA_DATA = [ ('nickname', 'username'), ('figureurl_qq_1', 'profile_image_url'), ('gender', 'gender') ] def get_user_details(self, response): """ Return user detail from QQ account sometimes nickname will duplicate with another qq account, to avoid this issue it's possible to use openid as username. """ if self.setting('USE_OPENID_AS_USERNAME', False): username = response.get('openid', '') else: username = response.get('nickname', '') fullname, first_name, last_name = self.get_user_names( first_name=response.get('nickname', '') ) return { 'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def get_openid(self, access_token): response = self.request(self.OPENID_URL, params={ 'access_token': access_token }) data = json.loads(response.content[10:-3]) return data['openid'] def user_data(self, access_token, *args, **kwargs): openid = self.get_openid(access_token) response = self.get_json( 'https://graph.qq.com/user/get_user_info', params={ 'access_token': access_token, 'oauth_consumer_key': self.setting('SOCIAL_AUTH_QQ_KEY'), 'openid': openid } ) response['openid'] = openid return response def request_access_token(self, url, data, *args, **kwargs): response = self.request(url, params=data, *args, **kwargs) return parse_qs(response.content) python-social-auth-0.2.21/social/backends/goclio.py0000644000175500017550000000233712754357263021753 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 class GoClioOAuth2(BaseOAuth2): name = 'goclio' AUTHORIZATION_URL = 'https://app.goclio.com/oauth/authorize/' ACCESS_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'https://app.goclio.com/oauth/token/' REDIRECT_STATE = False STATE_PARAMETER = False def get_user_details(self, response): """Return user details from GoClio account""" user = response.get('user', {}) username = user.get('id', None) email = user.get('email', None) first_name, last_name = (user.get('first_name', None), user.get('last_name', None)) fullname = '%s %s' % (first_name, last_name) return {'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://app.goclio.com/api/v2/users/who_am_i', params={'access_token': access_token} ) def get_user_id(self, details, response): return response.get('user', {}).get('id') python-social-auth-0.2.21/social/backends/upwork.py0000644000175500017550000000257612754357263022033 0ustar debacledebacle""" Upwork OAuth1 backend """ from social.backends.oauth import BaseOAuth1 class UpworkOAuth(BaseOAuth1): """Upwork OAuth authentication backend""" name = 'upwork' ID_KEY = 'id' AUTHORIZATION_URL = 'https://www.upwork.com/services/api/auth' REQUEST_TOKEN_URL = 'https://www.upwork.com/api/auth/v1/oauth/token/request' REQUEST_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'https://www.upwork.com/api/auth/v1/oauth/token/access' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_URI_PARAMETER_NAME = 'oauth_callback' def get_user_details(self, response): """Return user details from Upwork account""" info = response.get('info', {}) auth_user = response.get('auth_user', {}) first_name = auth_user.get('first_name') last_name = auth_user.get('last_name') fullname = '{} {}'.format(first_name, last_name) profile_url = info.get('profile_url', '') username = profile_url.rsplit('/')[-1].replace('~', '') return { 'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://www.upwork.com/api/auth/v1/info.json', auth=self.oauth_auth(access_token) ) python-social-auth-0.2.21/social/backends/taobao.py0000644000175500017550000000157412754357263021746 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 class TAOBAOAuth(BaseOAuth2): """Taobao OAuth authentication mechanism""" name = 'taobao' ID_KEY = 'taobao_user_id' ACCESS_TOKEN_METHOD = 'POST' AUTHORIZATION_URL = 'https://oauth.taobao.com/authorize' ACCESS_TOKEN_URL = 'https://oauth.taobao.com/token' def user_data(self, access_token, *args, **kwargs): """Return user data provided""" try: return self.get_json('https://eco.taobao.com/router/rest', params={ 'method': 'taobao.user.get', 'fomate': 'json', 'v': '2.0', 'access_token': access_token }) except ValueError: return None def get_user_details(self, response): """Return user details from Taobao account""" return {'username': response.get('taobao_user_nick')} python-social-auth-0.2.21/social/backends/stocktwits.py0000644000175500017550000000251512754357263022713 0ustar debacledebacle""" Stocktwits OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/stocktwits.html """ from social.backends.oauth import BaseOAuth2 class StocktwitsOAuth2(BaseOAuth2): """Stockwiths OAuth2 backend""" name = 'stocktwits' AUTHORIZATION_URL = 'https://api.stocktwits.com/api/2/oauth/authorize' ACCESS_TOKEN_URL = 'https://api.stocktwits.com/api/2/oauth/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' DEFAULT_SCOPE = ['read', 'publish_messages', 'publish_watch_lists', 'follow_users', 'follow_stocks'] def get_user_id(self, details, response): return response['user']['id'] def get_user_details(self, response): """Return user details from Stocktwits account""" fullname, first_name, last_name = self.get_user_names( response['user']['name'] ) return {'username': response['user']['username'], 'email': '', # not supplied 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.stocktwits.com/api/2/account/verify.json', params={'access_token': access_token} ) python-social-auth-0.2.21/social/backends/orbi.py0000644000175500017550000000240312754357263021424 0ustar debacledebacle""" Orbi OAuth2 backend """ from social.backends.oauth import BaseOAuth2 class OrbiOAuth2(BaseOAuth2): """Orbi OAuth2 authentication backend""" name = 'orbi' AUTHORIZATION_URL = 'https://login.orbi.kr/oauth/authorize' ACCESS_TOKEN_URL = 'https://login.orbi.kr/oauth/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('imin', 'imin'), ('nick', 'nick'), ('photo', 'photo'), ('sex', 'sex'), ('birth', 'birth'), ] def get_user_id(self, details, response): return response.get('id') def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( response.get('name', ''), response.get('first_name', ''), response.get('last_name', '') ) return { 'username': response.get('username', response.get('name')), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, } def user_data(self, access_token, *args, **kwargs): """Load user data from orbi""" return self.get_json('https://login.orbi.kr/oauth/user/get', params={ 'access_token': access_token }) python-social-auth-0.2.21/social/backends/coding.py0000644000175500017550000000272712754357263021745 0ustar debacledebacle""" Coding OAuth2 backend, docs at: """ from six.moves.urllib.parse import urljoin from social.backends.oauth import BaseOAuth2 class CodingOAuth2(BaseOAuth2): """Coding OAuth authentication backend""" name = 'coding' API_URL = 'https://coding.net/api/' AUTHORIZATION_URL = 'https://coding.net/oauth_authorize.html' ACCESS_TOKEN_URL = 'https://coding.net/api/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' DEFAULT_SCOPE = ['user'] REDIRECT_STATE = False def api_url(self): return self.API_URL def get_user_details(self, response): """Return user details from Github account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': response.get('name'), 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" data = self._user_data(access_token) if data.get('code') != 0: # 获取失败 pass return data.get('data') def _user_data(self, access_token, path=None): url = urljoin( self.api_url(), 'account/current_user{0}'.format(path or '') ) return self.get_json(url, params={'access_token': access_token}) python-social-auth-0.2.21/social/backends/trello.py0000644000175500017550000000275112754357263022000 0ustar debacledebacle""" Trello OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/trello.html """ from social.backends.oauth import BaseOAuth1 class TrelloOAuth(BaseOAuth1): """Trello OAuth authentication backend""" name = 'trello' ID_KEY = 'username' AUTHORIZATION_URL = 'https://trello.com/1/OAuthAuthorizeToken' REQUEST_TOKEN_URL = 'https://trello.com/1/OAuthGetRequestToken' ACCESS_TOKEN_URL = 'https://trello.com/1/OAuthGetAccessToken' EXTRA_DATA = [ ('username', 'username'), ('email', 'email'), ('fullName', 'fullName') ] def get_user_details(self, response): """Return user details from Trello account""" fullname, first_name, last_name = self.get_user_names( response.get('fullName') ) return {'username': response.get('username'), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token): """Return user data provided""" url = 'https://trello.com/1/members/me' try: return self.get_json(url, auth=self.oauth_auth(access_token)) except ValueError: return None def auth_extra_arguments(self): return { 'name': self.setting('APP_NAME', ''), # trello default expiration is '30days' 'expiration': self.setting('EXPIRATION', 'never') } python-social-auth-0.2.21/social/backends/legacy.py0000644000175500017550000000275712754357263021751 0ustar debacledebaclefrom social.backends.base import BaseAuth from social.exceptions import AuthMissingParameter class LegacyAuth(BaseAuth): def get_user_id(self, details, response): return details.get(self.ID_KEY) or \ response.get(self.ID_KEY) def auth_url(self): return self.setting('FORM_URL') def auth_html(self): return self.strategy.render_html(tpl=self.setting('FORM_HTML')) def uses_redirect(self): return self.setting('FORM_URL') and not \ self.setting('FORM_HTML') def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" if self.ID_KEY not in self.data: raise AuthMissingParameter(self, self.ID_KEY) kwargs.update({'response': self.data, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def get_user_details(self, response): """Return user details""" email = response.get('email', '') username = response.get('username', '') fullname, first_name, last_name = self.get_user_names( response.get('fullname', ''), response.get('first_name', ''), response.get('last_name', '') ) if email and not username: username = email.split('@', 1)[0] return { 'username': username, 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } python-social-auth-0.2.21/social/backends/slack.py0000644000175500017550000000455612754357263021601 0ustar debacledebacle""" Slack OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/slack.html https://api.slack.com/docs/oauth """ import re from social.backends.oauth import BaseOAuth2 class SlackOAuth2(BaseOAuth2): """Slack OAuth authentication backend""" name = 'slack' AUTHORIZATION_URL = 'https://slack.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://slack.com/api/oauth.access' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' REDIRECT_STATE = False EXTRA_DATA = [ ('id', 'id'), ('name', 'name'), ('real_name', 'real_name') ] def get_user_details(self, response): """Return user details from Slack account""" # Build the username with the team $username@$team_url # Necessary to get unique names for all of slack username = response.get('user') if self.setting('USERNAME_WITH_TEAM', True): match = re.search(r'//([^.]+)\.slack\.com', response['url']) username = '{0}@{1}'.format(username, match.group(1)) out = {'username': username} if 'profile' in response: out.update({ 'email': response['profile'].get('email'), 'fullname': response['profile'].get('real_name'), 'first_name': response['profile'].get('first_name'), 'last_name': response['profile'].get('last_name') }) return out def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" # Has to be two calls, because the users.info requires a username, # And we want the team information. Check auth.test details at: # https://api.slack.com/methods/auth.test auth_test = self.get_json('https://slack.com/api/auth.test', params={ 'token': access_token }) # https://api.slack.com/methods/users.info user_info = self.get_json('https://slack.com/api/users.info', params={ 'token': access_token, 'user': auth_test.get('user_id') }) if user_info.get('user'): # Capture the user data, if available based on the scope auth_test.update(user_info['user']) # Clean up user_id vs id auth_test['id'] = auth_test['user_id'] auth_test.pop('ok', None) auth_test.pop('user_id', None) return auth_test python-social-auth-0.2.21/social/backends/pixelpin.py0000644000175500017550000000225112754357263022322 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 class PixelPinOAuth2(BaseOAuth2): """PixelPin OAuth authentication backend""" name = 'pixelpin-oauth2' ID_KEY = 'id' AUTHORIZATION_URL = 'https://login.pixelpin.co.uk/OAuth2/Flogin.aspx' ACCESS_TOKEN_URL = 'https://ws3.pixelpin.co.uk/index.php/api/token' ACCESS_TOKEN_METHOD = 'POST' REQUIRES_EMAIL_VALIDATION = False EXTRA_DATA = [ ('id', 'id'), ] def get_user_details(self, response): """Return user details from PixelPin account""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('firstName'), last_name=response.get('lastName') ) return {'username': response.get('firstName'), 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://ws3.pixelpin.co.uk/index.php/api/userdata', params={'access_token': access_token} ) python-social-auth-0.2.21/social/backends/disqus.py0000644000175500017550000000342312754357263022004 0ustar debacledebacle""" Disqus OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/disqus.html """ from social.backends.oauth import BaseOAuth2 class DisqusOAuth2(BaseOAuth2): name = 'disqus' AUTHORIZATION_URL = 'https://disqus.com/api/oauth/2.0/authorize/' ACCESS_TOKEN_URL = 'https://disqus.com/api/oauth/2.0/access_token/' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' 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=None, *args, **kwargs): meta_response = dict(response, **response.get('response', {})) return super(DisqusOAuth2, self).extra_data(user, uid, meta_response, details, *args, **kwargs) def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" key, secret = self.get_key_and_secret() return self.get_json( 'https://disqus.com/api/3.0/users/details.json', params={'access_token': access_token, 'api_secret': secret} ) python-social-auth-0.2.21/social/backends/strava.py0000644000175500017550000000347212754357263022000 0ustar debacledebacle""" Strava OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/strava.html """ from social.backends.oauth import BaseOAuth2 class StravaOAuth(BaseOAuth2): name = 'strava' AUTHORIZATION_URL = 'https://www.strava.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://www.strava.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' # Strava doesn't check for parameters in redirect_uri and directly appends # the auth parameters to it, ending with an URL like: # http://example.com/complete/strava?redirect_state=xxx?code=xxx&state=xxx # Check issue #259 for details. REDIRECT_STATE = False REVOKE_TOKEN_URL = 'https://www.strava.com/oauth/deauthorize' def get_user_id(self, details, response): return response['athlete']['id'] def get_user_details(self, response): """Return user details from Strava account""" # because there is no usernames on strava username = response['athlete']['id'] email = response['athlete'].get('email', '') fullname, first_name, last_name = self.get_user_names( first_name=response['athlete'].get('firstname', ''), last_name=response['athlete'].get('lastname', ''), ) return {'username': str(username), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://www.strava.com/api/v3/athlete', params={'access_token': access_token}) def revoke_token_params(self, token, uid): params = super(StravaOAuth, self).revoke_token_params(token, uid) params['access_token'] = token return params python-social-auth-0.2.21/social/backends/ubuntu.py0000644000175500017550000000060112754357263022011 0ustar debacledebacle""" Ubuntu One OpenId backend """ from social.backends.open_id import OpenIdAuth class UbuntuOpenId(OpenIdAuth): name = 'ubuntu' URL = 'https://login.ubuntu.com' def get_user_id(self, details, response): """ Return user unique id provided by service. For Ubuntu One the nickname should be original. """ return details['nickname'] python-social-auth-0.2.21/social/backends/shopify.py0000644000175500017550000000646412754357263022165 0ustar debacledebacle""" Shopify OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/shopify.html """ import imp import six from social.utils import handle_http_errors from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthFailed, AuthCanceled class ShopifyOAuth2(BaseOAuth2): """Shopify OAuth2 authentication backend""" name = 'shopify' ID_KEY = 'shop' EXTRA_DATA = [ ('shop', 'shop'), ('website', 'website'), ('expires', 'expires') ] REDIRECT_STATE = False @property def shopifyAPI(self): if not hasattr(self, '_shopify_api'): fp, pathname, description = imp.find_module('shopify') self._shopify_api = imp.load_module('shopify', fp, pathname, description) return self._shopify_api def get_user_details(self, response): """Use the shopify store name as the username""" return { 'username': six.text_type(response.get('shop', '')).replace( '.myshopify.com', '' ) } def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return access_token and extra defined names to store in extra_data field""" data = super(ShopifyOAuth2, self).extra_data(user, uid, response, details, *args, **kwargs) session = self.shopifyAPI.Session(self.data.get('shop').strip()) # Get, and store the permanent token token = session.request_token(data['access_token']) data['access_token'] = token return dict(data) def auth_url(self): key, secret = self.get_key_and_secret() self.shopifyAPI.Session.setup(api_key=key, secret=secret) scope = self.get_scope() state = self.state_token() self.strategy.session_set(self.name + '_state', state) redirect_uri = self.get_redirect_uri(state) session = self.shopifyAPI.Session(self.data.get('shop').strip()) return session.create_permission_url( scope=scope, redirect_uri=redirect_uri ) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" self.process_error(self.data) access_token = None key, secret = self.get_key_and_secret() try: shop_url = self.data.get('shop') self.shopifyAPI.Session.setup(api_key=key, secret=secret) shopify_session = self.shopifyAPI.Session(shop_url, self.data) access_token = shopify_session.token except self.shopifyAPI.ValidationException: raise AuthCanceled(self) else: 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({ 'backend': self, 'response': { 'shop': shop_url, 'website': 'http://{0}'.format(website), 'access_token': access_token } }) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.21/social/backends/battlenet.py0000644000175500017550000000343212754357263022456 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 # This provides a backend for python-social-auth. This should not be confused # with officially battle.net offerings. This piece of code is not officially # affiliated with Blizzard Entertainment, copyrights to their respective # owners. See http://us.battle.net/en/forum/topic/13979588015 for more details. class BattleNetOAuth2(BaseOAuth2): """ battle.net Oauth2 backend""" name = 'battlenet-oauth2' ID_KEY = 'accountId' REDIRECT_STATE = False AUTHORIZATION_URL = 'https://eu.battle.net/oauth/authorize' ACCESS_TOKEN_URL = 'https://eu.battle.net/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REVOKE_TOKEN_METHOD = 'GET' DEFAULT_SCOPE = ['wow.profile'] EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('expires_in', 'expires'), ('token_type', 'token_type', True) ] def get_characters(self, access_token): """ Fetches the character list from the battle.net API. Returns list of characters or empty list if the request fails. """ params = {'access_token': access_token} if self.setting('API_LOCALE'): params['locale'] = self.setting('API_LOCALE') response = self.get_json( 'https://eu.api.battle.net/wow/user/characters', params=params ) return response.get('characters') or [] def get_user_details(self, response): """ Return user details from Battle.net account """ return {'battletag': response.get('battletag')} def user_data(self, access_token, *args, **kwargs): """ Loads user data from service """ return self.get_json( 'https://eu.api.battle.net/account/user', params={'access_token': access_token} ) python-social-auth-0.2.21/social/backends/bitbucket.py0000644000175500017550000000713712754357263022456 0ustar debacledebacle""" Bitbucket OAuth2 and OAuth1 backends, docs at: http://psa.matiasaguirre.net/docs/backends/bitbucket.html """ from social.exceptions import AuthForbidden from social.backends.oauth import BaseOAuth1, BaseOAuth2 class BitbucketOAuthBase(object): ID_KEY = 'uuid' def get_user_id(self, details, response): id_key = self.ID_KEY if self.setting('USERNAME_AS_ID', False): id_key = 'username' return response.get(id_key) def get_user_details(self, response): """Return user details from Bitbucket account""" fullname, first_name, last_name = self.get_user_names( response['display_name'] ) return {'username': response.get('username', ''), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" emails = self._get_emails(access_token) email = None for address in reversed(emails['values']): email = address['email'] if address['is_primary']: break if self.setting('VERIFIED_EMAILS_ONLY', False) and \ not address['is_confirmed']: raise AuthForbidden(self, 'Bitbucket account has no verified email') user = self._get_user(access_token) if email: user['email'] = email return user def _get_user(self, access_token=None): raise NotImplementedError('Implement in subclass') def _get_emails(self, access_token=None): raise NotImplementedError('Implement in subclass') class BitbucketOAuth2(BitbucketOAuthBase, BaseOAuth2): name = 'bitbucket-oauth2' SCOPE_SEPARATOR = ' ' AUTHORIZATION_URL = 'https://bitbucket.org/site/oauth2/authorize' ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('scopes', 'scopes'), ('expires_in', 'expires'), ('token_type', 'token_type'), ('refresh_token', 'refresh_token') ] def auth_complete_credentials(self): return self.get_key_and_secret() def _get_user(self, access_token=None): return self.get_json('https://api.bitbucket.org/2.0/user', params={'access_token': access_token}) def _get_emails(self, access_token=None): return self.get_json('https://api.bitbucket.org/2.0/user/emails', params={'access_token': access_token}) def refresh_token(self, *args, **kwargs): raise NotImplementedError('Refresh tokens for Bitbucket have ' 'not been implemented') class BitbucketOAuth(BitbucketOAuthBase, BaseOAuth1): """Bitbucket OAuth authentication backend""" name = 'bitbucket' AUTHORIZATION_URL = 'https://bitbucket.org/api/1.0/oauth/authenticate' REQUEST_TOKEN_URL = 'https://bitbucket.org/api/1.0/oauth/request_token' ACCESS_TOKEN_URL = 'https://bitbucket.org/api/1.0/oauth/access_token' def oauth_auth(self, *args, **kwargs): return super(BitbucketOAuth, self).oauth_auth(*args, **kwargs) def _get_user(self, access_token=None): return self.get_json('https://api.bitbucket.org/2.0/user', auth=self.oauth_auth(access_token)) def _get_emails(self, access_token=None): return self.get_json('https://api.bitbucket.org/2.0/user/emails', auth=self.oauth_auth(access_token)) python-social-auth-0.2.21/social/backends/loginradius.py0000644000175500017550000000507112754357263023015 0ustar debacledebacle""" LoginRadius BaseOAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/loginradius.html """ from social.backends.oauth import BaseOAuth2 class LoginRadiusAuth(BaseOAuth2): """LoginRadius BaseOAuth2 authentication backend.""" name = 'loginradius' ID_KEY = 'ID' ACCESS_TOKEN_URL = 'https://api.loginradius.com/api/v2/access_token' PROFILE_URL = 'https://api.loginradius.com/api/v2/userprofile' ACCESS_TOKEN_METHOD = 'GET' REDIRECT_STATE = False STATE_PARAMETER = False def uses_redirect(self): """Return False because we return HTML instead.""" return False def auth_html(self): key, secret = self.get_key_and_secret() tpl = self.setting('TEMPLATE', 'loginradius.html') return self.strategy.render_html(tpl=tpl, context={ 'backend': self, 'LOGINRADIUS_KEY': key, 'LOGINRADIUS_REDIRECT_URL': self.get_redirect_uri() }) def request_access_token(self, *args, **kwargs): return self.get_json(params={ 'token': self.data.get('token'), 'secret': self.setting('SECRET') }, *args, **kwargs) def user_data(self, access_token, *args, **kwargs): """Loads user data from service. Implement in subclass.""" return self.get_json( self.PROFILE_URL, params={'access_token': access_token}, data=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) def get_user_details(self, response): """Must return user details in a know internal struct: {'username': , 'email': , 'fullname': , 'first_name': , 'last_name': } """ profile = { 'username': response['NickName'] or '', 'email': response['Email'][0]['Value'] or '', 'fullname': response['FullName'] or '', 'first_name': response['FirstName'] or '', 'last_name': response['LastName'] or '' } return profile def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response. Since LoginRadius handles multiple providers, we need to distinguish them to prevent conflicts.""" return '{0}-{1}'.format(response.get('Provider'), response.get(self.ID_KEY)) python-social-auth-0.2.21/social/backends/eveonline.py0000644000175500017550000000260512754357263022461 0ustar debacledebacle""" EVE Online Single Sign-On (SSO) OAuth2 backend Documentation at https://developers.eveonline.com/resource/single-sign-on """ from social.backends.oauth import BaseOAuth2 class EVEOnlineOAuth2(BaseOAuth2): """EVE Online OAuth authentication backend""" name = 'eveonline' AUTHORIZATION_URL = 'https://login.eveonline.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://login.eveonline.com/oauth/token' ID_KEY = 'CharacterID' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('CharacterID', 'id'), ('ExpiresOn', 'expires'), ('CharacterOwnerHash', 'owner_hash', True), ('refresh_token', 'refresh_token', True), ] def get_user_details(self, response): """Return user details from EVE Online account""" user_data = self.user_data(response['access_token']) fullname, first_name, last_name = self.get_user_names( user_data['CharacterName'] ) return { 'email': '', 'username': fullname, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Get Character data from EVE server""" return self.get_json( 'https://login.eveonline.com/oauth/verify', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.21/social/backends/linkedin.py0000644000175500017550000000730012754357263022267 0ustar debacledebacle""" LinkedIn OAuth1 and OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/linkedin.html """ from social.backends.oauth import BaseOAuth1, BaseOAuth2 class BaseLinkedinAuth(object): EXTRA_DATA = [('id', 'id'), ('first-name', 'first_name', True), ('last-name', 'last_name', True), ('firstName', 'first_name', True), ('lastName', 'last_name', True)] USER_DETAILS = 'https://api.linkedin.com/v1/people/~:({0})' def get_user_details(self, response): """Return user details from Linkedin account""" fullname, first_name, last_name = self.get_user_names( first_name=response['firstName'], last_name=response['lastName'] ) email = response.get('emailAddress', '') return {'username': first_name + last_name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_details_url(self): # use set() since LinkedIn fails when values are duplicated fields_selectors = list(set(['first-name', 'id', 'last-name'] + self.setting('FIELD_SELECTORS', []))) # user sort to ease the tests URL mocking fields_selectors.sort() fields_selectors = ','.join(fields_selectors) return self.USER_DETAILS.format(fields_selectors) def user_data_headers(self): lang = self.setting('FORCE_PROFILE_LANGUAGE') if lang: return { 'Accept-Language': lang if lang is not True else self.strategy.get_language() } class LinkedinOAuth(BaseLinkedinAuth, BaseOAuth1): """Linkedin OAuth authentication backend""" name = 'linkedin' SCOPE_SEPARATOR = '+' AUTHORIZATION_URL = 'https://www.linkedin.com/uas/oauth/authenticate' REQUEST_TOKEN_URL = 'https://api.linkedin.com/uas/oauth/requestToken' ACCESS_TOKEN_URL = 'https://api.linkedin.com/uas/oauth/accessToken' def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( self.user_details_url(), params={'format': 'json'}, auth=self.oauth_auth(access_token), headers=self.user_data_headers() ) def unauthorized_token(self): """Makes first request to oauth. Returns an unauthorized Token.""" scope = self.get_scope() or '' if scope: scope = '?scope=' + self.SCOPE_SEPARATOR.join(scope) return self.request(self.REQUEST_TOKEN_URL + scope, params=self.request_token_extra_arguments(), auth=self.oauth_auth()).text class LinkedinOAuth2(BaseLinkedinAuth, BaseOAuth2): name = 'linkedin-oauth2' SCOPE_SEPARATOR = ' ' AUTHORIZATION_URL = 'https://www.linkedin.com/uas/oauth2/authorization' ACCESS_TOKEN_URL = 'https://www.linkedin.com/uas/oauth2/accessToken' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def user_data(self, access_token, *args, **kwargs): return self.get_json( self.user_details_url(), params={'oauth2_access_token': access_token, 'format': 'json'}, headers=self.user_data_headers() ) def request_access_token(self, *args, **kwargs): # LinkedIn expects a POST request with querystring parameters, despite # the spec http://tools.ietf.org/html/rfc6749#section-4.1.3 kwargs['params'] = kwargs.pop('data') return super(LinkedinOAuth2, self).request_access_token( *args, **kwargs ) python-social-auth-0.2.21/social/backends/username.py0000644000175500017550000000040312754357263022306 0ustar debacledebacle""" Legacy Username backend, docs at: http://psa.matiasaguirre.net/docs/backends/username.html """ from social.backends.legacy import LegacyAuth class UsernameAuth(LegacyAuth): name = 'username' ID_KEY = 'username' EXTRA_DATA = ['username'] python-social-auth-0.2.21/social/backends/digitalocean.py0000644000175500017550000000264012754357263023117 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 class DigitalOceanOAuth(BaseOAuth2): """ DigitalOcean OAuth authentication backend. Docs: https://developers.digitalocean.com/documentation/oauth/ """ name = 'digitalocean' AUTHORIZATION_URL = 'https://cloud.digitalocean.com/v1/oauth/authorize' ACCESS_TOKEN_URL = 'https://cloud.digitalocean.com/v1/oauth/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ' ' EXTRA_DATA = [ ('expires_in', 'expires_in') ] def get_user_id(self, details, response): """Return user unique id provided by service""" return response['account'].get('uuid') def get_user_details(self, response): """Return user details from DigitalOcean account""" fullname, first_name, last_name = self.get_user_names( response.get('name') or '') return {'username': response['account'].get('email'), 'email': response['account'].get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, token, *args, **kwargs): """Loads user data from service""" url = 'https://api.digitalocean.com/v2/account' auth_header = {"Authorization": "Bearer %s" % token} try: return self.get_json(url, headers=auth_header) except ValueError: return None python-social-auth-0.2.21/social/backends/azuread.py0000644000175500017550000001103212754357263022122 0ustar debacledebacle""" Copyright (c) 2015 Microsoft Open Technologies, Inc. All rights reserved. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ """ Azure AD OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/azuread.html """ import time from jwt import DecodeError, ExpiredSignature, decode as jwt_decode from social.exceptions import AuthTokenError from social.backends.oauth import BaseOAuth2 class AzureADOAuth2(BaseOAuth2): name = 'azuread-oauth2' SCOPE_SEPARATOR = ' ' AUTHORIZATION_URL = 'https://login.microsoftonline.com/common/oauth2/authorize' ACCESS_TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False DEFAULT_SCOPE = ['openid', 'profile', 'user_impersonation'] EXTRA_DATA = [ ('access_token', 'access_token'), ('id_token', 'id_token'), ('refresh_token', 'refresh_token'), ('expires_in', 'expires'), ('expires_on', 'expires_on'), ('not_before', 'not_before'), ('given_name', 'first_name'), ('family_name', 'last_name'), ('token_type', 'token_type') ] def get_user_id(self, details, response): """Use upn as unique id""" return response.get('upn') def get_user_details(self, response): """Return user details from Azure AD account""" fullname, first_name, last_name = ( response.get('name', ''), response.get('given_name', ''), response.get('family_name', '') ) return {'username': fullname, 'email': response.get('upn'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): response = kwargs.get('response') id_token = response.get('id_token') try: decoded_id_token = jwt_decode(id_token, verify=False) except (DecodeError, ExpiredSignature) as de: raise AuthTokenError(self, de) return decoded_id_token def auth_extra_arguments(self): """Return extra arguments needed on auth process. The defaults can be overriden by GET parameters.""" extra_arguments = {} resource = self.setting('RESOURCE') if resource: extra_arguments = {'resource': resource} return extra_arguments def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return access_token and extra defined names to store in extra_data field""" data = super(AzureADOAuth2, self).extra_data(user, uid, response, details, *args, **kwargs) data['resource'] = self.setting('RESOURCE') return data def refresh_token_params(self, token, *args, **kwargs): return { 'client_id': self.setting('KEY'), 'client_secret': self.setting('SECRET'), 'refresh_token': token, 'grant_type': 'refresh_token', 'resource': self.setting('RESOURCE') } def get_auth_token(self, user_id): """Return the access token for the given user, after ensuring that it has not expired, or refreshing it if so.""" user = self.get_user(user_id=user_id) access_token = user.social_user.access_token expires_on = user.social_user.extra_data['expires_on'] if expires_on <= int(time.time()): new_token_response = self.refresh_token(token=access_token) access_token = new_token_response['access_token'] return access_token python-social-auth-0.2.21/social/backends/mailru.py0000644000175500017550000000323512754357263021766 0ustar debacledebacle""" Mail.ru OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/mailru.html """ from hashlib import md5 from social.p3 import unquote from social.backends.oauth import BaseOAuth2 class MailruOAuth2(BaseOAuth2): """Mail.ru authentication backend""" name = 'mailru-oauth2' ID_KEY = 'uid' AUTHORIZATION_URL = 'https://connect.mail.ru/oauth/authorize' ACCESS_TOKEN_URL = 'https://connect.mail.ru/oauth/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [('refresh_token', 'refresh_token'), ('expires_in', 'expires')] def get_user_details(self, response): """Return user details from Mail.ru request""" fullname, first_name, last_name = self.get_user_names( first_name=unquote(response['first_name']), last_name=unquote(response['last_name']) ) return {'username': unquote(response['nick']), 'email': unquote(response['email']), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data from Mail.ru REST API""" key, secret = self.get_key_and_secret() data = {'method': 'users.getInfo', 'session_key': access_token, 'app_id': key, 'secure': '1'} param_list = sorted(list(item + '=' + data[item] for item in data)) data['sig'] = md5( (''.join(param_list) + secret).encode('utf-8') ).hexdigest() return self.get_json('http://www.appsmail.ru/platform/api', params=data)[0] python-social-auth-0.2.21/social/backends/yahoo.py0000644000175500017550000001356012754357263021616 0ustar debacledebacle""" Yahoo OpenId, OAuth1 and OAuth2 backends, docs at: http://psa.matiasaguirre.net/docs/backends/yahoo.html """ from requests.auth import HTTPBasicAuth from social.utils import handle_http_errors from social.backends.open_id import OpenIdAuth from social.backends.oauth import BaseOAuth2, BaseOAuth1 class YahooOpenId(OpenIdAuth): """Yahoo OpenID authentication backend""" name = 'yahoo' URL = 'http://me.yahoo.com' class YahooOAuth(BaseOAuth1): """Yahoo OAuth authentication backend. DEPRECATED""" name = 'yahoo-oauth' ID_KEY = 'guid' AUTHORIZATION_URL = 'https://api.login.yahoo.com/oauth/v2/request_auth' REQUEST_TOKEN_URL = \ 'https://api.login.yahoo.com/oauth/v2/get_request_token' ACCESS_TOKEN_URL = 'https://api.login.yahoo.com/oauth/v2/get_token' EXTRA_DATA = [ ('guid', 'id'), ('access_token', 'access_token'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Yahoo Profile""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('givenName'), last_name=response.get('familyName') ) emails = [email for email in response.get('emails', []) if email.get('handle')] emails.sort(key=lambda e: e.get('primary', False), reverse=True) return {'username': response.get('nickname'), 'email': emails[0]['handle'] if emails else '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'https://social.yahooapis.com/v1/user/{0}/profile?format=json' return self.get_json( url.format(self._get_guid(access_token)), auth=self.oauth_auth(access_token) )['profile'] 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 """ return self.get_json( 'https://social.yahooapis.com/v1/me/guid?format=json', auth=self.oauth_auth(access_token) )['guid']['value'] class YahooOAuth2(BaseOAuth2): """Yahoo OAuth2 authentication backend""" name = 'yahoo-oauth2' ID_KEY = 'guid' AUTHORIZATION_URL = 'https://api.login.yahoo.com/oauth2/request_auth' ACCESS_TOKEN_URL = 'https://api.login.yahoo.com/oauth2/get_token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('xoauth_yahoo_guid', 'id'), ('access_token', 'access_token'), ('expires_in', 'expires'), ('refresh_token', 'refresh_token'), ('token_type', 'token_type'), ] def get_user_names(self, first_name, last_name): if first_name or last_name: return ' '.join((first_name, last_name)), first_name, last_name return None, None, None def get_user_details(self, response): """ Return user details from Yahoo Profile. To Get user email you need the profile private read permission. """ fullname, first_name, last_name = self.get_user_names( first_name=response.get('givenName'), last_name=response.get('familyName') ) emails = [email for email in response.get('emails', []) if 'handle' in email] emails.sort(key=lambda e: e.get('primary', False), reverse=True) email = emails[0]['handle'] if emails else response.get('guid', '') return { 'username': response.get('nickname'), 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'https://social.yahooapis.com/v1/user/{0}/profile?format=json' \ .format(kwargs['response']['xoauth_yahoo_guid']) return self.get_json(url, headers={ 'Authorization': 'Bearer {0}'.format(access_token) }, method='GET')['profile'] @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) response = self.request_access_token( self.ACCESS_TOKEN_URL, auth=HTTPBasicAuth(*self.get_key_and_secret()), data=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) def refresh_token_params(self, token, *args, **kwargs): return { 'refresh_token': token, 'grant_type': 'refresh_token', 'redirect_uri': 'oob', # out of bounds } def refresh_token(self, token, *args, **kwargs): params = self.refresh_token_params(token, *args, **kwargs) url = self.REFRESH_TOKEN_URL or self.ACCESS_TOKEN_URL method = self.REFRESH_TOKEN_METHOD key = 'params' if method == 'GET' else 'data' request_args = { 'headers': self.auth_headers(), 'method': method, key: params } request = self.request( url, auth=HTTPBasicAuth(*self.get_key_and_secret()), **request_args ) return self.process_refresh_token_response(request, *args, **kwargs) def auth_complete_params(self, state=None): return { 'grant_type': 'authorization_code', # request auth code 'code': self.data.get('code', ''), # server response code 'redirect_uri': self.get_redirect_uri(state) } python-social-auth-0.2.21/social/backends/clef.py0000644000175500017550000000335012754357263021404 0ustar debacledebacle""" Clef OAuth support. This contribution adds support for Clef OAuth service. The settings SOCIAL_AUTH_CLEF_KEY and SOCIAL_AUTH_CLEF_SECRET must be defined with the values given by Clef application registration process. """ from social.backends.oauth import BaseOAuth2 class ClefOAuth2(BaseOAuth2): """Clef OAuth authentication backend""" name = 'clef' AUTHORIZATION_URL = 'https://clef.io/iframes/qr' ACCESS_TOKEN_URL = 'https://clef.io/api/v1/authorize' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' def auth_params(self, *args, **kwargs): params = super(ClefOAuth2, self).auth_params(*args, **kwargs) params['app_id'] = params.pop('client_id') params['redirect_url'] = params.pop('redirect_uri') return params def get_user_id(self, response, details): return details.get('info').get('id') def get_user_details(self, response): """Return user details from Github account""" info = response.get('info') fullname, first_name, last_name = self.get_user_names( first_name=info.get('first_name'), last_name=info.get('last_name') ) email = info.get('email', '') if email: username = email.split('@', 1)[0] else: username = info.get('id') return { 'username': username, 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'phone_number': info.get('phone_number', '') } def user_data(self, access_token, *args, **kwargs): return self.get_json('https://clef.io/api/v1/info', params={'access_token': access_token}) python-social-auth-0.2.21/social/backends/itembase.py0000644000175500017550000000661012754357263022266 0ustar debacledebacleimport time from social.backends.oauth import BaseOAuth2 from social.utils import handle_http_errors class ItembaseOAuth2(BaseOAuth2): name = 'itembase' ID_KEY = 'uuid' AUTHORIZATION_URL = 'https://accounts.itembase.com/oauth/v2/auth' ACCESS_TOKEN_URL = 'https://accounts.itembase.com/oauth/v2/token' USER_DETAILS_URL = 'https://users.itembase.com/v1/me' ACTIVATION_ENDPOINT = 'https://solutionservice.itembase.com/activate' DEFAULT_SCOPE = ['user.minimal'] EXTRA_DATA = [ ('access_token', 'access_token'), ('token_type', 'token_type'), ('refresh_token', 'refresh_token'), ('expires_in', 'expires_in'), # seconds to expiration ('expires', 'expires'), # expiration timestamp in UTC ('uuid', 'uuid'), ('username', 'username'), ('email', 'email'), ('first_name', 'first_name'), ('middle_name', 'middle_name'), ('last_name', 'last_name'), ('name_format', 'name_format'), ('locale', 'locale'), ('preferred_currency', 'preferred_currency'), ] def add_expires(self, data): data['expires'] = int(time.time()) + data.get('expires_in', 0) return data def extra_data(self, user, uid, response, details=None, *args, **kwargs): data = BaseOAuth2.extra_data(self, user, uid, response, details=details, *args, **kwargs) return self.add_expires(data) def process_refresh_token_response(self, response, *args, **kwargs): data = BaseOAuth2.process_refresh_token_response(self, response, *args, **kwargs) return self.add_expires(data) def get_user_details(self, response): """Return user details from Itembase account""" return response def user_data(self, access_token, *args, **kwargs): return self.get_json(self.USER_DETAILS_URL, headers={ 'Authorization': 'Bearer {0}'.format(access_token) }) def activation_data(self, response): # returns activation_data dict with activation_url inside # see http://developers.itembase.com/authentication/activation return self.get_json(self.ACTIVATION_ENDPOINT, headers={ 'Authorization': 'Bearer {0}'.format(response['access_token']) }) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" state = self.validate_state() self.process_error(self.data) # itembase needs GET request with params instead of just data response = self.request_access_token( self.access_token_url(), params=self.auth_complete_params(state), headers=self.auth_headers(), auth=self.auth_complete_credentials(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) class ItembaseOAuth2Sandbox(ItembaseOAuth2): name = 'itembase-sandbox' AUTHORIZATION_URL = 'http://sandbox.accounts.itembase.io/oauth/v2/auth' ACCESS_TOKEN_URL = 'http://sandbox.accounts.itembase.io/oauth/v2/token' USER_DETAILS_URL = 'http://sandbox.users.itembase.io/v1/me' ACTIVATION_ENDPOINT = 'http://sandbox.solutionservice.itembase.io/activate' python-social-auth-0.2.21/social/backends/base.py0000644000175500017550000002303312754357263021405 0ustar debacledebaclefrom requests import request, ConnectionError from social.utils import SSLHttpAdapter, module_member, parse_qs, user_agent from social.exceptions import AuthFailed class BaseAuth(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 # Django auth ID_KEY = None EXTRA_DATA = None REQUIRES_EMAIL_VALIDATION = False SEND_USER_AGENT = False SSL_PROTOCOL = None def __init__(self, strategy=None, redirect_uri=None): self.strategy = strategy self.redirect_uri = redirect_uri self.data = {} if strategy: self.data = self.strategy.request_data() self.redirect_uri = self.strategy.absolute_uri( self.redirect_uri ) def setting(self, name, default=None): """Return setting value from strategy""" return self.strategy.setting(name, default=default, backend=self) def start(self): # Clean any partial pipeline info before starting the process self.strategy.clean_partial_pipeline() if self.uses_redirect(): return self.strategy.redirect(self.auth_url()) else: return self.strategy.html(self.auth_html()) def complete(self, *args, **kwargs): return self.auth_complete(*args, **kwargs) 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 process_error(self, data): """Process data for errors, raise exception if needed. Call this method on any override of auth_complete.""" pass 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 'backend' not in kwargs or kwargs['backend'].name != self.name or \ 'strategy' not in kwargs or 'response' not in kwargs: return None self.strategy = self.strategy or kwargs.get('strategy') self.redirect_uri = self.redirect_uri or kwargs.get('redirect_uri') self.data = self.strategy.request_data() pipeline = self.strategy.get_pipeline() kwargs.setdefault('is_new', False) if 'pipeline_index' in kwargs: pipeline = pipeline[kwargs['pipeline_index']:] return self.pipeline(pipeline, *args, **kwargs) def pipeline(self, pipeline, pipeline_index=0, *args, **kwargs): out = self.run_pipeline(pipeline, pipeline_index, *args, **kwargs) if not isinstance(out, dict): return out user = out.get('user') if user: user.social_user = out.get('social') user.is_new = out.get('is_new') return user def disconnect(self, *args, **kwargs): pipeline = self.strategy.get_disconnect_pipeline() if 'pipeline_index' in kwargs: pipeline = pipeline[kwargs['pipeline_index']:] kwargs['name'] = self.name kwargs['user_storage'] = self.strategy.storage.user return self.run_pipeline(pipeline, *args, **kwargs) def run_pipeline(self, pipeline, pipeline_index=0, *args, **kwargs): out = kwargs.copy() out.setdefault('strategy', self.strategy) out.setdefault('backend', out.pop(self.name, None) or self) out.setdefault('request', self.strategy.request_data()) out.setdefault('details', {}) for idx, name in enumerate(pipeline): out['pipeline_index'] = pipeline_index + idx func = module_member(name) result = func(*args, **out) or {} if not isinstance(result, dict): return result out.update(result) self.strategy.clean_partial_pipeline() return out def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return default extra data to store in extra_data field""" data = {} for entry in (self.EXTRA_DATA or []) + self.setting('EXTRA_DATA', []): if not isinstance(entry, (list, tuple)): entry = (entry,) size = len(entry) if size >= 1 and size <= 3: if size == 3: name, alias, discard = entry elif size == 2: (name, alias), discard = entry, False elif size == 1: name = alias = entry[0] discard = False value = response.get(name) or details.get(name) if discard and not value: continue data[alias] = value return data def auth_allowed(self, response, details): """Return True if the user should be allowed to authenticate, by default check if email is whitelisted (if there's a whitelist)""" emails = self.setting('WHITELISTED_EMAILS', []) domains = self.setting('WHITELISTED_DOMAINS', []) email = details.get('email') allowed = True if email and (emails or domains): domain = email.split('@', 1)[1] allowed = email in emails or domain in domains return allowed def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response.""" return response.get(self.ID_KEY) 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') def get_user_names(self, fullname='', first_name='', last_name=''): # Avoid None values fullname = fullname or '' first_name = first_name or '' last_name = last_name or '' if fullname and not (first_name or last_name): try: first_name, last_name = fullname.split(' ', 1) except ValueError: first_name = first_name or fullname or '' last_name = last_name or '' fullname = fullname or ' '.join((first_name, last_name)) return fullname.strip(), first_name.strip(), last_name.strip() 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. """ from social.strategies.utils import get_current_strategy strategy = self.strategy or get_current_strategy() return strategy.get_user(user_id) def continue_pipeline(self, *args, **kwargs): """Continue previous halted pipeline""" kwargs.update({'backend': self, 'strategy': self.strategy}) return self.authenticate(*args, **kwargs) def auth_extra_arguments(self): """Return extra arguments needed on auth process. The defaults can be overridden by GET parameters.""" extra_arguments = self.setting('AUTH_EXTRA_ARGUMENTS', {}).copy() extra_arguments.update((key, self.data[key]) for key in extra_arguments if key in self.data) return extra_arguments def uses_redirect(self): """Return True if this provider uses redirect url method, otherwise return false.""" return True def request(self, url, method='GET', *args, **kwargs): kwargs.setdefault('headers', {}) if self.setting('VERIFY_SSL') is not None: kwargs.setdefault('verify', self.setting('VERIFY_SSL')) kwargs.setdefault('timeout', self.setting('REQUESTS_TIMEOUT') or self.setting('URLOPEN_TIMEOUT')) if self.SEND_USER_AGENT and 'User-Agent' not in kwargs['headers']: kwargs['headers']['User-Agent'] = user_agent() try: if self.SSL_PROTOCOL: session = SSLHttpAdapter.ssl_adapter_session(self.SSL_PROTOCOL) response = session.request(method, url, *args, **kwargs) else: response = request(method, url, *args, **kwargs) except ConnectionError as err: raise AuthFailed(self, str(err)) response.raise_for_status() return response def get_json(self, url, *args, **kwargs): return self.request(url, *args, **kwargs).json() def get_querystring(self, url, *args, **kwargs): return parse_qs(self.request(url, *args, **kwargs).text) def get_key_and_secret(self): """Return tuple with Consumer Key and Consumer Secret for current service provider. Must return (key, secret), order *must* be respected. """ return self.setting('KEY'), self.setting('SECRET') python-social-auth-0.2.21/social/backends/yandex.py0000644000175500017550000000567212754357263021774 0ustar debacledebacle""" 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 social.p3 import urlsplit from social.backends.open_id import OpenIdAuth from social.backends.oauth import BaseOAuth2 class YandexOpenId(OpenIdAuth): """Yandex OpenID authentication backend""" name = 'yandex-openid' URL = 'http://openid.yandex.ru' 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(YandexOpenId, 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 YandexOAuth2(BaseOAuth2): """Legacy Yandex OAuth2 authentication backend""" name = 'yandex-oauth2' AUTHORIZATION_URL = 'https://oauth.yandex.com/authorize' ACCESS_TOKEN_URL = 'https://oauth.yandex.com/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( response.get('real_name') or response.get('display_name') or '' ) return {'username': response.get('display_name'), 'email': response.get('default_email') or response.get('emails', [''])[0], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): return self.get_json('https://login.yandex.ru/info', params={'oauth_token': access_token, 'format': 'json'}) class YaruOAuth2(BaseOAuth2): name = 'yaru' AUTHORIZATION_URL = 'https://oauth.yandex.com/authorize' ACCESS_TOKEN_URL = 'https://oauth.yandex.com/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( response.get('real_name') or response.get('display_name') or '' ) return {'username': response.get('display_name'), 'email': response.get('default_email') or response.get('emails', [''])[0], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): return self.get_json('https://login.yandex.ru/info', params={'oauth_token': access_token, 'format': 'json'}) python-social-auth-0.2.21/social/backends/pushbullet.py0000644000175500017550000000151512754357263022663 0ustar debacledebacleimport base64 from social.backends.oauth import BaseOAuth2 class PushbulletOAuth2(BaseOAuth2): """pushbullet OAuth authentication backend""" name = 'pushbullet' EXTRA_DATA = [('id', 'id')] ID_KEY = 'username' AUTHORIZATION_URL = 'https://www.pushbullet.com/authorize' REQUEST_TOKEN_URL = 'https://api.pushbullet.com/oauth2/token' ACCESS_TOKEN_URL = 'https://api.pushbullet.com/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' STATE_PARAMETER = False def get_user_details(self, response): return {'username': response.get('access_token')} def get_user_id(self, details, response): auth = 'Basic {0}'.format(base64.b64encode(details['username'])) return self.get_json('https://api.pushbullet.com/v2/users/me', headers={'Authorization': auth})['iden'] python-social-auth-0.2.21/social/backends/oauth.py0000644000175500017550000004154412754357263021622 0ustar debacledebacleimport six from requests_oauthlib import OAuth1 from oauthlib.oauth1 import SIGNATURE_TYPE_AUTH_HEADER from social.p3 import urlencode, unquote from social.utils import url_add_parameters, parse_qs, handle_http_errors from social.exceptions import AuthFailed, AuthCanceled, AuthUnknownError, \ AuthMissingParameter, AuthStateMissing, \ AuthStateForbidden, AuthTokenError from social.backends.base import BaseAuth class OAuthAuth(BaseAuth): """OAuth authentication backend base class. 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. URLs settings: AUTHORIZATION_URL Authorization service url ACCESS_TOKEN_URL Access token URL """ AUTHORIZATION_URL = '' ACCESS_TOKEN_URL = '' ACCESS_TOKEN_METHOD = 'GET' REVOKE_TOKEN_URL = None REVOKE_TOKEN_METHOD = 'POST' ID_KEY = 'id' SCOPE_PARAMETER_NAME = 'scope' DEFAULT_SCOPE = None SCOPE_SEPARATOR = ' ' REDIRECT_STATE = False STATE_PARAMETER = False def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return access_token and extra defined names to store in extra_data field""" data = super(OAuthAuth, self).extra_data(user, uid, response, details, *args, **kwargs) data['access_token'] = response.get('access_token', '') or \ kwargs.get('access_token') return data def state_token(self): """Generate csrf token to include as state parameter.""" return self.strategy.random_string(32) def get_or_create_state(self): 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, that way we can still verify the # request if the provider doesn't implement the state parameter. # Reuse token if any. name = self.name + '_state' state = self.strategy.session_get(name) if state is None: state = self.state_token() self.strategy.session_set(name, state) else: state = None return state def get_session_state(self): return self.strategy.session_get(self.name + '_state') def get_request_state(self): request_state = self.data.get('state') or \ self.data.get('redirect_state') if request_state and isinstance(request_state, list): request_state = request_state[0] return request_state 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.get_session_state() request_state = self.get_request_state() if not request_state: raise AuthMissingParameter(self, 'state') elif not state: raise AuthStateMissing(self, 'state') elif not request_state == state: raise AuthStateForbidden(self) else: return state def get_redirect_uri(self, state=None): """Build redirect 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 get_scope(self): """Return list with needed access scope""" scope = self.setting('SCOPE', []) if not self.setting('IGNORE_DEFAULT_SCOPE', False): scope = scope + (self.DEFAULT_SCOPE or []) 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 {} def authorization_url(self): return self.AUTHORIZATION_URL def access_token_url(self): return self.ACCESS_TOKEN_URL def revoke_token_url(self, token, uid): return self.REVOKE_TOKEN_URL def revoke_token_params(self, token, uid): return {} def revoke_token_headers(self, token, uid): return {} def process_revoke_token_response(self, response): return response.status_code == 200 def revoke_token(self, token, uid): if self.REVOKE_TOKEN_URL: url = self.revoke_token_url(token, uid) params = self.revoke_token_params(token, uid) headers = self.revoke_token_headers(token, uid) data = urlencode(params) if self.REVOKE_TOKEN_METHOD != 'GET' \ else None response = self.request(url, params=params, headers=headers, data=data, method=self.REVOKE_TOKEN_METHOD) return self.process_revoke_token_response(response) class BaseOAuth1(OAuthAuth): """Consumer based mechanism OAuth authentication, fill the needed parameters to communicate properly with authentication service. URLs settings: REQUEST_TOKEN_URL Request token URL """ REQUEST_TOKEN_URL = '' REQUEST_TOKEN_METHOD = 'GET' OAUTH_TOKEN_PARAMETER_NAME = 'oauth_token' REDIRECT_URI_PARAMETER_NAME = 'redirect_uri' UNATHORIZED_TOKEN_SUFIX = 'unauthorized_token_name' def auth_url(self): """Return redirect url""" token = self.set_unauthorized_token() return self.oauth_authorization_request(token) def process_error(self, data): if 'oauth_problem' in data: if data['oauth_problem'] == 'user_refused': raise AuthCanceled(self, 'User refused the access') raise AuthUnknownError(self, 'Error was ' + data['oauth_problem']) @handle_http_errors def auth_complete(self, *args, **kwargs): """Return user, might be logged in""" # Multiple unauthorized tokens are supported (see #521) self.process_error(self.data) self.validate_state() token = self.get_unauthorized_token() access_token = self.access_token(token) return self.do_auth(access_token, *args, **kwargs) @handle_http_errors def do_auth(self, access_token, *args, **kwargs): """Finish the auth process once the access_token was retrieved""" if not isinstance(access_token, dict): access_token = parse_qs(access_token) data = self.user_data(access_token) if data is not None and 'access_token' not in data: data['access_token'] = access_token kwargs.update({'response': data, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def get_unauthorized_token(self): name = self.name + self.UNATHORIZED_TOKEN_SUFIX unauthed_tokens = self.strategy.session_get(name, []) if not unauthed_tokens: raise AuthTokenError(self, 'Missing unauthorized token') data_token = self.data.get(self.OAUTH_TOKEN_PARAMETER_NAME) if data_token is None: raise AuthTokenError(self, 'Missing unauthorized token') token = None for utoken in unauthed_tokens: orig_utoken = utoken if not isinstance(utoken, dict): utoken = parse_qs(utoken) if utoken.get(self.OAUTH_TOKEN_PARAMETER_NAME) == data_token: self.strategy.session_set(name, list(set(unauthed_tokens) - set([orig_utoken]))) token = utoken break else: raise AuthTokenError(self, 'Incorrect tokens') return token def set_unauthorized_token(self): token = self.unauthorized_token() name = self.name + self.UNATHORIZED_TOKEN_SUFIX tokens = self.strategy.session_get(name, []) + [token] self.strategy.session_set(name, tokens) return token def request_token_extra_arguments(self): """Return extra arguments needed on request-token process""" return self.setting('REQUEST_TOKEN_EXTRA_ARGUMENTS', {}) def unauthorized_token(self): """Return request for unauthorized token (first stage)""" params = self.request_token_extra_arguments() params.update(self.get_scope_argument()) key, secret = self.get_key_and_secret() # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() response = self.request( self.REQUEST_TOKEN_URL, params=params, auth=OAuth1(key, secret, callback_uri=self.get_redirect_uri(state), decoding=decoding), method=self.REQUEST_TOKEN_METHOD ) content = response.content if response.encoding or response.apparent_encoding: content = content.decode(response.encoding or response.apparent_encoding) else: content = response.content.decode() return content def oauth_authorization_request(self, token): """Generate OAuth request to authorize token.""" if not isinstance(token, dict): token = parse_qs(token) params = self.auth_extra_arguments() or {} params.update(self.get_scope_argument()) params[self.OAUTH_TOKEN_PARAMETER_NAME] = token.get( self.OAUTH_TOKEN_PARAMETER_NAME ) state = self.get_or_create_state() params[self.REDIRECT_URI_PARAMETER_NAME] = self.get_redirect_uri(state) return '{0}?{1}'.format(self.authorization_url(), urlencode(params)) def oauth_auth(self, token=None, oauth_verifier=None, signature_type=SIGNATURE_TYPE_AUTH_HEADER): key, secret = self.get_key_and_secret() oauth_verifier = oauth_verifier or self.data.get('oauth_verifier') if token: resource_owner_key = token.get('oauth_token') resource_owner_secret = token.get('oauth_token_secret') if not resource_owner_key: raise AuthTokenError(self, 'Missing oauth_token') if not resource_owner_secret: raise AuthTokenError(self, 'Missing oauth_token_secret') else: resource_owner_key = None resource_owner_secret = None # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() return OAuth1(key, secret, resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret, callback_uri=self.get_redirect_uri(state), verifier=oauth_verifier, signature_type=signature_type, decoding=decoding) def oauth_request(self, token, url, params=None, method='GET'): """Generate OAuth request, setups callback url""" return self.request(url, method=method, params=params, auth=self.oauth_auth(token)) def access_token(self, token): """Return request for access token value""" return self.get_querystring(self.access_token_url(), auth=self.oauth_auth(token), method=self.ACCESS_TOKEN_METHOD) class BaseOAuth2(OAuthAuth): """Base class for OAuth2 providers. OAuth2 draft details at: http://tools.ietf.org/html/draft-ietf-oauth-v2-10 """ REFRESH_TOKEN_URL = None REFRESH_TOKEN_METHOD = 'POST' RESPONSE_TYPE = 'code' REDIRECT_STATE = True STATE_PARAMETER = True 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""" state = self.get_or_create_state() params = self.auth_params(state) params.update(self.get_scope_argument()) params.update(self.auth_extra_arguments()) params = urlencode(params) if not self.REDIRECT_STATE: # redirect_uri matching is strictly enforced, so match the # providers value exactly. params = unquote(params) return '{0}?{1}'.format(self.authorization_url(), params) 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) } def auth_complete_credentials(self): return None def auth_headers(self): return {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'} def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return access_token, token_type, and extra defined names to store in extra_data field""" data = super(BaseOAuth2, self).extra_data(user, uid, response, details=details, *args, **kwargs) data['token_type'] = response.get('token_type') or \ kwargs.get('token_type') return data def request_access_token(self, *args, **kwargs): return self.get_json(*args, **kwargs) def process_error(self, data): if data.get('error'): if data['error'] == 'denied' or data['error'] == 'access_denied': raise AuthCanceled(self, data.get('error_description', '')) raise AuthFailed(self, data.get('error_description') or data['error']) elif 'denied' in data: raise AuthCanceled(self, data['denied']) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" state = self.validate_state() self.process_error(self.data) response = self.request_access_token( self.access_token_url(), data=self.auth_complete_params(state), headers=self.auth_headers(), auth=self.auth_complete_credentials(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) @handle_http_errors 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 {}) if 'access_token' not in response: response['access_token'] = access_token kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def refresh_token_params(self, token, *args, **kwargs): client_id, client_secret = self.get_key_and_secret() return { 'refresh_token': token, 'grant_type': 'refresh_token', 'client_id': client_id, 'client_secret': client_secret } def process_refresh_token_response(self, response, *args, **kwargs): return response.json() def refresh_token(self, token, *args, **kwargs): params = self.refresh_token_params(token, *args, **kwargs) url = self.refresh_token_url() method = self.REFRESH_TOKEN_METHOD key = 'params' if method == 'GET' else 'data' request_args = {'headers': self.auth_headers(), 'method': method, key: params} request = self.request(url, **request_args) return self.process_refresh_token_response(request, *args, **kwargs) def refresh_token_url(self): return self.REFRESH_TOKEN_URL or self.access_token_url() python-social-auth-0.2.21/social/backends/openstreetmap.py0000644000175500017550000000360012754357263023357 0ustar debacledebacle""" OpenStreetMap OAuth support. This adds support for OpenStreetMap OAuth service. An application must be registered first on OpenStreetMap and the settings SOCIAL_AUTH_OPENSTREETMAP_KEY and SOCIAL_AUTH_OPENSTREETMAP_SECRET must be defined with the corresponding values. More info: http://wiki.openstreetmap.org/wiki/OAuth """ from xml.dom import minidom from social.backends.oauth import BaseOAuth1 class OpenStreetMapOAuth(BaseOAuth1): """OpenStreetMap OAuth authentication backend""" name = 'openstreetmap' AUTHORIZATION_URL = 'http://www.openstreetmap.org/oauth/authorize' REQUEST_TOKEN_URL = 'http://www.openstreetmap.org/oauth/request_token' ACCESS_TOKEN_URL = 'http://www.openstreetmap.org/oauth/access_token' EXTRA_DATA = [ ('id', 'id'), ('avatar', 'avatar'), ('account_created', 'account_created') ] def get_user_details(self, response): """Return user details from OpenStreetMap account""" return { 'username': response['username'], 'email': '', 'fullname': '', 'first_name': '', 'last_name': '' } def user_data(self, access_token, *args, **kwargs): """Return user data provided""" response = self.oauth_request( access_token, 'http://api.openstreetmap.org/api/0.6/user/details' ) try: dom = minidom.parseString(response.content) except ValueError: return None user = dom.getElementsByTagName('user')[0] try: avatar = dom.getElementsByTagName('img')[0].getAttribute('href') except IndexError: avatar = None return { 'id': user.getAttribute('id'), 'username': user.getAttribute('display_name'), 'account_created': user.getAttribute('account_created'), 'avatar': avatar } python-social-auth-0.2.21/social/backends/belgiumeid.py0000644000175500017550000000052312754357263022600 0ustar debacledebacle""" Belgium EID OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/belgium_eid.html """ from social.backends.open_id import OpenIdAuth class BelgiumEIDOpenId(OpenIdAuth): """Belgium e-ID OpenID authentication backend""" name = 'belgiumeid' URL = 'https://www.e-contract.be/eid-idp/endpoints/openid/auth' python-social-auth-0.2.21/social/backends/untappd.py0000644000175500017550000000734012754357263022151 0ustar debacledebacleimport requests from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthFailed from social.utils import handle_http_errors class UntappdOAuth2(BaseOAuth2): """Untappd OAuth2 authentication backend""" name = 'untappd' AUTHORIZATION_URL = 'https://untappd.com/oauth/authenticate/' ACCESS_TOKEN_URL = 'https://untappd.com/oauth/authorize/' BASE_API_URL = 'https://api.untappd.com' USER_INFO_URL = BASE_API_URL + '/v4/user/info/' ACCESS_TOKEN_METHOD = 'GET' STATE_PARAMETER = False REDIRECT_STATE = False EXTRA_DATA = [ ('id', 'id'), ('bio', 'bio'), ('date_joined', 'date_joined'), ('location', 'location'), ('url', 'url'), ('user_avatar', 'user_avatar'), ('user_avatar_hd', 'user_avatar_hd'), ('user_cover_photo', 'user_cover_photo') ] def auth_params(self, state=None): client_id, client_secret = self.get_key_and_secret() params = { 'client_id': client_id, 'redirect_url': self.get_redirect_uri(), 'response_type': self.RESPONSE_TYPE } return params def process_error(self, data): """ All errors from Untappd are contained in the 'meta' key of the response. """ response_code = data.get('meta', {}).get('http_code') if response_code is not None and response_code != requests.codes.ok: raise AuthFailed(self, data['meta']['error_detail']) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" client_id, client_secret = self.get_key_and_secret() code = self.data.get('code') self.process_error(self.data) # Untapped sends the access token request with URL parameters, # not a body response = self.request_access_token( self.access_token_url(), method=self.ACCESS_TOKEN_METHOD, params={ 'response_type': 'code', 'code': code, 'client_id': client_id, 'client_secret': client_secret, 'redirect_url': self.get_redirect_uri() } ) self.process_error(response) # Both the access_token and the rest of the response are # buried in the 'response' key return self.do_auth( response['response']['access_token'], response=response['response'], *args, **kwargs ) def get_user_details(self, response): """Return user details from an Untappd account""" # Start with the user data as it was returned user_data = response['user'] # Make a few updates to match expected key names user_data.update({ 'username': user_data.get('user_name'), 'email': user_data.get('settings', {}).get('email_address', ''), 'first_name': user_data.get('first_name'), 'last_name': user_data.get('last_name'), 'fullname': user_data.get('first_name') + ' ' + user_data.get('last_name') }) return user_data def get_user_id(self, details, response): """ Return a unique ID for the current user, by default from server response. """ return response['user'].get(self.ID_KEY) def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" response = self.get_json(self.USER_INFO_URL, params={ 'access_token': access_token, 'compact': 'true' }) self.process_error(response) # The response data is buried in the 'response' key return response['response'] python-social-auth-0.2.21/social/backends/thisismyjam.py0000644000175500017550000000230312754357263023031 0ustar debacledebacle""" ThisIsMyJam OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/thisismyjam.html """ from social.backends.oauth import BaseOAuth1 class ThisIsMyJamOAuth1(BaseOAuth1): """ThisIsMyJam OAuth1 authentication backend""" name = 'thisismyjam' REQUEST_TOKEN_URL = 'http://www.thisismyjam.com/oauth/request_token' AUTHORIZATION_URL = 'http://www.thisismyjam.com/oauth/authorize' ACCESS_TOKEN_URL = 'http://www.thisismyjam.com/oauth/access_token' REDIRECT_URI_PARAMETER_NAME = 'oauth_callback' def get_user_details(self, response): """Return user details from ThisIsMyJam account""" info = response.get('person') fullname, first_name, last_name = self.get_user_names( info.get('fullname') ) return { 'username': info.get('name'), 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('http://api.thisismyjam.com/1/verify.json', auth=self.oauth_auth(access_token)) python-social-auth-0.2.21/social/backends/qiita.py0000644000175500017550000000423312754357263021603 0ustar debacledebacle""" Qiita OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/qiita.html http://qiita.com/api/v2/docs#get-apiv2oauthauthorize """ import json from social.backends.oauth import BaseOAuth2 class QiitaOAuth2(BaseOAuth2): """Qiita OAuth authentication backend""" name = 'qiita' AUTHORIZATION_URL = 'https://qiita.com/api/v2/oauth/authorize' ACCESS_TOKEN_URL = 'https://qiita.com/api/v2/access_tokens' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ' ' REDIRECT_STATE = True EXTRA_DATA = [ ('description', 'description'), ('facebook_id', 'facebook_id'), ('followees_count', 'followees_count'), ('followers_count', 'followers_count'), ('github_login_name', 'github_login_name'), ('id', 'id'), ('items_count', 'items_count'), ('linkedin_id', 'linkedin_id'), ('location', 'location'), ('name', 'name'), ('organization', 'organization'), ('profile_image_url', 'profile_image_url'), ('twitter_screen_name', 'twitter_screen_name'), ('website_url', 'website_url'), ] def auth_complete_params(self, state=None): data = super(QiitaOAuth2, self).auth_complete_params(state) if "grant_type" in data: del data["grant_type"] if "redirect_uri" in data: del data["redirect_uri"] return json.dumps(data) def auth_headers(self): return {'Content-Type': 'application/json'} def request_access_token(self, *args, **kwargs): data = super(QiitaOAuth2, self).request_access_token(*args, **kwargs) data.update({'access_token': data['token']}) return data def get_user_details(self, response): """Return user details from Qiita account""" return { 'username': response['id'], 'fullname': response['name'], } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://qiita.com/api/v2/authenticated_user', headers={ 'Authorization': 'Bearer {0}'.format(access_token) }) python-social-auth-0.2.21/social/backends/sketchfab.py0000644000175500017550000000251612754357263022430 0ustar debacledebacle""" Sketchfab OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/sketchfab.html https://sketchfab.com/developers/oauth """ from social.backends.oauth import BaseOAuth2 class SketchfabOAuth2(BaseOAuth2): name = 'sketchfab' ID_KEY = 'uid' AUTHORIZATION_URL = 'https://sketchfab.com/oauth2/authorize/' ACCESS_TOKEN_URL = 'https://sketchfab.com/oauth2/token/' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False REQUIRES_EMAIL_VALIDATION = False EXTRA_DATA = [ ('username', 'username'), ('apiToken', 'apiToken') ] def get_user_details(self, response): """Return user details from Sketchfab account""" user_data = response email = user_data.get('email', '') username = user_data['username'] name = user_data.get('displayName', '') fullname, first_name, last_name = self.get_user_names(name) return {'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://sketchfab.com/v2/users/me', headers={ 'Authorization': 'Bearer {0}'.format(access_token) }) python-social-auth-0.2.21/social/backends/persona.py0000644000175500017550000000346512754357263022151 0ustar debacledebacle""" Mozilla Persona authentication backend, docs at: http://psa.matiasaguirre.net/docs/backends/persona.html """ from social.utils import handle_http_errors from social.backends.base import BaseAuth from social.exceptions import AuthFailed, AuthMissingParameter class PersonaAuth(BaseAuth): """BrowserID authentication backend""" name = 'persona' 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': 'browserid.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=None, *args, **kwargs): """Return users extra data""" return {'audience': response['audience'], 'issuer': response['issuer']} @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" if 'assertion' not in self.data: raise AuthMissingParameter(self, 'assertion') response = self.get_json('https://browserid.org/verify', data={ 'assertion': self.data['assertion'], 'audience': self.strategy.request_host() }, method='POST') if response.get('status') == 'failure': raise AuthFailed(self) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.21/social/backends/salesforce.py0000644000175500017550000000346112754357263022624 0ustar debacledebaclefrom social.backends.oauth import BaseOAuth2 from social.p3 import urlencode class SalesforceOAuth2(BaseOAuth2): """Salesforce OAuth2 authentication backend""" name = 'salesforce-oauth2' AUTHORIZATION_URL = \ 'https://login.salesforce.com/services/oauth2/authorize' ACCESS_TOKEN_URL = 'https://login.salesforce.com/services/oauth2/token' REVOKE_TOKEN_URL = 'https://login.salesforce.com/services/oauth2/revoke' ACCESS_TOKEN_METHOD = 'POST' REFRESH_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ' ' EXTRA_DATA = [ ('id', 'id'), ('instance_url', 'instance_url'), ('issued_at', 'issued_at'), ('signature', 'signature'), ('refresh_token', 'refresh_token'), ] def get_user_details(self, response): """Return user details from a Salesforce account""" return { 'username': response.get('username'), 'email': response.get('email') or '', 'first_name': response.get('first_name'), 'last_name': response.get('last_name'), 'fullname': response.get('display_name') } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" user_id_url = kwargs.get('response').get('id') url = user_id_url + '?' + urlencode({'access_token': access_token}) try: return self.get_json(url) except ValueError: return None class SalesforceOAuth2Sandbox(SalesforceOAuth2): """Salesforce OAuth2 authentication testing backend""" name = 'salesforce-oauth2-sandbox' AUTHORIZATION_URL = 'https://test.salesforce.com/services/oauth2/authorize' ACCESS_TOKEN_URL = 'https://test.salesforce.com/services/oauth2/token' REVOKE_TOKEN_URL = 'https://test.salesforce.com/services/oauth2/revoke' python-social-auth-0.2.21/social/backends/nationbuilder.py0000644000175500017550000000312212754357263023327 0ustar debacledebacle""" NationBuilder OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/nationbuilder.html """ from social.backends.oauth import BaseOAuth2 class NationBuilderOAuth2(BaseOAuth2): """NationBuilder OAuth2 authentication backend""" name = 'nationbuilder' AUTHORIZATION_URL = 'https://{slug}.nationbuilder.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://{slug}.nationbuilder.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires') ] def authorization_url(self): return self.AUTHORIZATION_URL.format(slug=self.slug) def access_token_url(self): return self.ACCESS_TOKEN_URL.format(slug=self.slug) @property def slug(self): return self.setting('SLUG') def get_user_details(self, response): """Return user details from Github account""" email = response.get('email') or '' username = email.split('@')[0] if email else '' return {'username': username, 'email': email, 'fullname': response.get('full_name') or '', 'first_name': response.get('first_name') or '', 'last_name': response.get('last_name') or ''} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'https://{slug}.nationbuilder.com/api/v1/people/me'.format( slug=self.slug ) return self.get_json(url, params={ 'access_token': access_token })['person'] python-social-auth-0.2.21/social/backends/instagram.py0000644000175500017550000000402512754357263022460 0ustar debacledebacle""" Instagram OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/instagram.html """ import hmac from hashlib import sha256 from social.backends.oauth import BaseOAuth2 class InstagramOAuth2(BaseOAuth2): name = 'instagram' AUTHORIZATION_URL = 'https://api.instagram.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://api.instagram.com/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' def get_user_id(self, details, response): # Sometimes Instagram returns 'user', sometimes 'data', but API docs # says 'data' http://instagram.com/developer/endpoints/users/#get_users user = response.get('user') or response.get('data') or {} return user.get('id') def get_user_details(self, response): """Return user details from Instagram account""" # Sometimes Instagram returns 'user', sometimes 'data', but API docs # says 'data' http://instagram.com/developer/endpoints/users/#get_users user = response.get('user') or response.get('data') or {} username = user['username'] email = user.get('email', '') fullname, first_name, last_name = self.get_user_names( user.get('full_name', '') ) return {'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" key, secret = self.get_key_and_secret() params = {'access_token': access_token} sig = self._generate_sig("/users/self", params, secret) params['sig'] = sig return self.get_json('https://api.instagram.com/v1/users/self', params=params) def _generate_sig(self, endpoint, params, secret): sig = endpoint for key in sorted(params.keys()): sig += '|%s=%s' % (key, params[key]) return hmac.new(secret.encode(), sig.encode(), sha256).hexdigest() python-social-auth-0.2.21/social/backends/appsfuel.py0000644000175500017550000000273612754357263022321 0ustar debacledebacle""" Appsfueld OAuth2 backend (with sandbox mode support), docs at: http://psa.matiasaguirre.net/docs/backends/appsfuel.html """ from social.backends.oauth import BaseOAuth2 class AppsfuelOAuth2(BaseOAuth2): name = 'appsfuel' ID_KEY = 'user_id' AUTHORIZATION_URL = 'http://app.appsfuel.com/content/permission' ACCESS_TOKEN_URL = 'https://api.appsfuel.com/v1/live/oauth/token' ACCESS_TOKEN_METHOD = 'POST' USER_DETAILS_URL = 'https://api.appsfuel.com/v1/live/user' def get_user_details(self, response): """Return user details from Appsfuel account""" email = response.get('email', '') username = email.split('@')[0] if email else '' fullname, first_name, last_name = self.get_user_names( response.get('display_name', '') ) return { 'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json(self.USER_DETAILS_URL, params={ 'access_token': access_token }) class AppsfuelOAuth2Sandbox(AppsfuelOAuth2): name = 'appsfuel-sandbox' AUTHORIZATION_URL = 'https://api.appsfuel.com/v1/sandbox/choose' ACCESS_TOKEN_URL = 'https://api.appsfuel.com/v1/sandbox/oauth/token' USER_DETAILS_URL = 'https://api.appsfuel.com/v1/sandbox/user' python-social-auth-0.2.21/social/backends/email.py0000644000175500017550000000042612754357263021563 0ustar debacledebacle""" Legacy Email backend, docs at: http://psa.matiasaguirre.net/docs/backends/email.html """ from social.backends.legacy import LegacyAuth class EmailAuth(LegacyAuth): name = 'email' ID_KEY = 'email' REQUIRES_EMAIL_VALIDATION = True EXTRA_DATA = ['email'] python-social-auth-0.2.21/social/backends/pinterest.py0000644000175500017550000000275312754357263022516 0ustar debacledebacle# -*- coding: utf-8 -*- """ Pinterest OAuth2 backend, docs at: https://developers.pinterest.com/docs/api/authentication/ """ from __future__ import unicode_literals import ssl from social.backends.oauth import BaseOAuth2 class PinterestOAuth2(BaseOAuth2): name = 'pinterest' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://api.pinterest.com/oauth/' ACCESS_TOKEN_URL = 'https://api.pinterest.com/v1/oauth/token' REDIRECT_STATE = False ACCESS_TOKEN_METHOD = 'POST' SSL_PROTOCOL = ssl.PROTOCOL_TLSv1 def user_data(self, access_token, *args, **kwargs): response = self.get_json('https://api.pinterest.com/v1/me/', params={'access_token': access_token}) if 'data' in response: username = response['data']['url'].strip('/').split('/')[-1] response = { 'user_id': response['data']['id'], 'first_name': response['data']['first_name'], 'last_name': response['data']['last_name'], 'username': username, } return response def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( first_name=response['first_name'], last_name=response['last_name']) return {'username': response.get('username'), 'email': None, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} python-social-auth-0.2.21/social/backends/kakao.py0000644000175500017550000000240512754357263021561 0ustar debacledebacle""" Kakao OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/kakao.html """ from social.backends.oauth import BaseOAuth2 class KakaoOAuth2(BaseOAuth2): """Kakao OAuth authentication backend""" name = 'kakao' AUTHORIZATION_URL = 'https://kauth.kakao.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://kauth.kakao.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_id(self, details, response): return response['id'] def get_user_details(self, response): """Return user details from Kakao account""" nickname = response['properties']['nickname'] return { 'username': nickname, 'email': '', 'fullname': '', 'first_name': '', 'last_name': '' } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://kapi.kakao.com/v1/user/me', params={'access_token': access_token}) def auth_complete_params(self, state=None): return { 'grant_type': 'authorization_code', 'code': self.data.get('code', ''), 'client_id': self.get_key_and_secret()[0], } python-social-auth-0.2.21/social/backends/behance.py0000644000175500017550000000305512754357263022062 0ustar debacledebacle""" Behance OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/behance.html """ from social.backends.oauth import BaseOAuth2 class BehanceOAuth2(BaseOAuth2): """Behance OAuth authentication backend""" name = 'behance' AUTHORIZATION_URL = 'https://www.behance.net/v2/oauth/authenticate' ACCESS_TOKEN_URL = 'https://www.behance.net/v2/oauth/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = '|' EXTRA_DATA = [('username', 'username')] REDIRECT_STATE = False 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'] fullname, first_name, last_name = self.get_user_names( user['display_name'], user['first_name'], user['last_name'] ) return {'username': user['username'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': ''} def extra_data(self, user, uid, response, details=None, *args, **kwargs): # 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 data = response.copy() data.update(response['user']) return super(BehanceOAuth2, self).extra_data(user, uid, data, details, *args, **kwargs) python-social-auth-0.2.21/social/backends/rdio.py0000644000175500017550000000464412754357263021437 0ustar debacledebacle""" Rdio OAuth1 and OAuth2 backends, docs at: http://psa.matiasaguirre.net/docs/backends/rdio.html """ from social.backends.oauth import BaseOAuth1, BaseOAuth2, OAuthAuth RDIO_API = 'https://www.rdio.com/api/1/' class BaseRdio(OAuthAuth): ID_KEY = 'key' def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( fullname=response['displayName'], first_name=response['firstName'], last_name=response['lastName'] ) return { 'username': response['username'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } class RdioOAuth1(BaseRdio, BaseOAuth1): """Rdio OAuth authentication backend""" name = 'rdio-oauth1' 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' EXTRA_DATA = [ ('key', 'rdio_id'), ('icon', 'rdio_icon_url'), ('url', 'rdio_profile_url'), ('username', 'rdio_username'), ('streamRegion', 'rdio_stream_region'), ] def user_data(self, access_token, *args, **kwargs): """Return user data provided""" params = {'method': 'currentUser', 'extras': 'username,displayName,streamRegion'} request = self.oauth_request(access_token, RDIO_API, params, method='POST') return self.get_json(request.url, method='POST', data=request.to_postdata())['result'] class RdioOAuth2(BaseRdio, BaseOAuth2): name = 'rdio-oauth2' AUTHORIZATION_URL = 'https://www.rdio.com/oauth2/authorize' ACCESS_TOKEN_URL = 'https://www.rdio.com/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' 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), ] def user_data(self, access_token, *args, **kwargs): return self.get_json(RDIO_API, method='POST', data={ 'method': 'currentUser', 'extras': 'username,displayName,streamRegion', 'access_token': access_token })['result'] python-social-auth-0.2.21/social/backends/launchpad.py0000644000175500017550000000032312754357263022427 0ustar debacledebacle""" Launchpad OpenId backend """ from social.backends.open_id import OpenIdAuth class LaunchpadOpenId(OpenIdAuth): name = 'launchpad' URL = 'https://login.launchpad.net' USERNAME_KEY = 'nickname' python-social-auth-0.2.21/social/backends/reddit.py0000644000175500017550000000334012754357263021745 0ustar debacledebacle""" Reddit OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/reddit.html """ import base64 from social.backends.oauth import BaseOAuth2 class RedditOAuth2(BaseOAuth2): """Reddit OAuth2 authentication backend""" name = 'reddit' AUTHORIZATION_URL = 'https://ssl.reddit.com/api/v1/authorize' ACCESS_TOKEN_URL = 'https://ssl.reddit.com/api/v1/access_token' ACCESS_TOKEN_METHOD = 'POST' REFRESH_TOKEN_METHOD = 'POST' REDIRECT_STATE = False SCOPE_SEPARATOR = ',' DEFAULT_SCOPE = ['identity'] SEND_USER_AGENT = True EXTRA_DATA = [ ('id', 'id'), ('name', 'username'), ('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': ''} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://oauth.reddit.com/api/v1/me.json', headers={'Authorization': 'bearer ' + access_token} ) def auth_headers(self): return { 'Authorization': b'Basic ' + base64.urlsafe_b64encode( '{0}:{1}'.format(*self.get_key_and_secret()).encode() ) } def refresh_token_params(self, token, redirect_uri=None, *args, **kwargs): params = super(RedditOAuth2, self).refresh_token_params(token) params['redirect_uri'] = self.redirect_uri or redirect_uri return params python-social-auth-0.2.21/social/backends/vk.py0000644000175500017550000001663312754357263021123 0ustar debacledebacle# -*- coding: utf-8 -*- """ VK.com OpenAPI, OAuth2 and Iframe application OAuth2 backends, docs at: http://psa.matiasaguirre.net/docs/backends/vk.html """ from time import time from hashlib import md5 from social.utils import parse_qs from social.backends.base import BaseAuth from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthTokenRevoked, AuthException class VKontakteOpenAPI(BaseAuth): """VK.COM OpenAPI authentication backend""" name = 'vk-openapi' ID_KEY = 'id' def get_user_details(self, response): """Return user details from VK.com request""" nickname = response.get('nickname') or '' fullname, first_name, last_name = self.get_user_names( first_name=response.get('first_name', [''])[0], last_name=response.get('last_name', [''])[0] ) return { 'username': response['id'] if len(nickname) == 0 else nickname, 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): return self.data def auth_html(self): """Returns local VK authentication page, not necessary for VK to authenticate. """ ctx = {'VK_APP_ID': self.setting('APP_ID'), 'VK_COMPLETE_URL': self.redirect_uri} local_html = self.setting('LOCAL_HTML', 'vkontakte.html') return self.strategy.render_html(tpl=local_html, context=ctx) def auth_complete(self, *args, **kwargs): """Performs check of authentication in VKontakte, returns User if succeeded""" session_value = self.strategy.session_get( 'vk_app_' + self.setting('APP_ID') ) if 'id' not in self.data or not session_value: raise ValueError('VK.com authentication is not completed') mapping = parse_qs(session_value) check_str = ''.join(item + '=' + mapping[item] for item in ['expire', 'mid', 'secret', 'sid']) key, secret = self.get_key_and_secret() hash = md5((check_str + secret).encode('utf-8')).hexdigest() if hash != mapping['sig'] or int(mapping['expire']) < time(): raise ValueError('VK.com authentication failed: Invalid Hash') kwargs.update({'backend': self, 'response': self.user_data(mapping['mid'])}) return self.strategy.authenticate(*args, **kwargs) def uses_redirect(self): """VK.com 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 VKOAuth2(BaseOAuth2): """VKOAuth2 authentication backend""" name = 'vk-oauth2' ID_KEY = 'user_id' AUTHORIZATION_URL = 'http://oauth.vk.com/authorize' ACCESS_TOKEN_URL = 'https://oauth.vk.com/access_token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('id', 'id'), ('expires_in', 'expires') ] def get_user_details(self, response): """Return user details from VK.com account""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('first_name'), last_name=response.get('last_name') ) return {'username': response.get('screen_name'), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" request_data = ['first_name', 'last_name', 'screen_name', 'nickname', 'photo'] + self.setting('EXTRA_DATA', []) fields = ','.join(set(request_data)) data = vk_api(self, 'users.get', { 'access_token': access_token, 'fields': fields, }) if data and 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 or {} class VKAppOAuth2(VKOAuth2): """VK.com Application Authentication support""" name = 'vk-app' def user_profile(self, user_id, access_token=None): request_data = ['first_name', 'last_name', 'screen_name', 'nickname', 'photo'] + self.setting('EXTRA_DATA', []) fields = ','.join(set(request_data)) data = {'uids': user_id, 'fields': fields} if access_token: data['access_token'] = access_token profiles = vk_api(self, 'getProfiles', data).get('response') if profiles: return profiles[0] def auth_complete(self, *args, **kwargs): required_params = ('is_app_user', 'viewer_id', 'access_token', 'api_id') if not all(param in self.data for param in required_params): return None auth_key = self.data.get('auth_key') # Verify signature, if present key, secret = self.get_key_and_secret() if auth_key: check_key = md5('_'.join([key, self.data.get('viewer_id'), secret]).encode('utf-8')).hexdigest() if check_key != auth_key: raise ValueError('VK.com authentication failed: invalid ' 'auth key') user_check = self.setting('USERMODE') user_id = self.data.get('viewer_id') if user_check is not None: user_check = int(user_check) if user_check == 1: is_user = self.data.get('is_app_user') elif user_check == 2: is_user = vk_api(self, 'isAppUser', {'uid': user_id}).get('response', 0) if not int(is_user): return None auth_data = { 'auth': self, 'backend': self, 'request': self.strategy.request_data(), 'response': { 'user_id': user_id, } } auth_data['response'].update(self.user_profile(user_id)) return self.strategy.authenticate(*args, **auth_data) def vk_api(backend, method, data): """ Calls VK.com OpenAPI method, check: https://vk.com/apiclub http://goo.gl/yLcaa """ # We need to perform server-side call if no access_token data['v'] = backend.setting('API_VERSION', '3.0') if 'access_token' not in data: key, secret = backend.get_key_and_secret() if 'api_id' not in data: data['api_id'] = key data['method'] = method data['format'] = 'json' url = 'http://api.vk.com/api.php' param_list = sorted(list(item + '=' + data[item] for item in data)) data['sig'] = md5( (''.join(param_list) + secret).encode('utf-8') ).hexdigest() else: url = 'https://api.vk.com/method/' + method try: return backend.get_json(url, params=data) except (TypeError, KeyError, IOError, ValueError, IndexError): return None python-social-auth-0.2.21/social/p3.py0000644000175500017550000000112712754357263017243 0ustar debacledebacle# Python3 support, keep import hacks here import six if six.PY3: from urllib.parse import parse_qs, urlparse, urlunparse, quote, \ urlsplit, urlencode, unquote from io import StringIO else: try: from urlparse import parse_qs except ImportError: # fall back for Python 2.5 from cgi import parse_qs from urlparse import urlparse, urlunparse, urlsplit from urllib import urlencode, unquote, quote from StringIO import StringIO # Placate pyflakes parse_qs, urlparse, urlunparse, quote, urlsplit, urlencode, unquote, StringIO python-social-auth-0.2.21/social/store.py0000644000175500017550000000527012754357263020060 0ustar debacledebacleimport time try: import cPickle as pickle except ImportError: import pickle from openid.store.interface import OpenIDStore as BaseOpenIDStore from openid.store.nonce import SKEW class OpenIdStore(BaseOpenIDStore): """Storage class""" def __init__(self, strategy): """Init method""" super(OpenIdStore, self).__init__() self.strategy = strategy self.storage = strategy.storage self.assoc = self.storage.association self.nonce = self.storage.nonce self.max_nonce_age = 6 * 60 * 60 # Six hours def storeAssociation(self, server_url, association): """Store new assocition if doesn't exist""" self.assoc.store(server_url, association) def removeAssociation(self, server_url, handle): """Remove association""" associations_ids = list(dict(self.assoc.oids(server_url, handle)).keys()) if associations_ids: self.assoc.remove(associations_ids) def expiresIn(self, assoc): if hasattr(assoc, 'getExpiresIn'): return assoc.getExpiresIn() else: # python3-openid 3.0.2 return assoc.expiresIn def getAssociation(self, server_url, handle=None): """Return stored assocition""" associations, expired = [], [] for assoc_id, association in self.assoc.oids(server_url, handle): expires = self.expiresIn(association) if expires > 0: associations.append(association) elif expires == 0: expired.append(assoc_id) if expired: # clear expired associations self.assoc.remove(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 self.nonce.use(server_url, timestamp, salt) class OpenIdSessionWrapper(dict): pickle_instances = ( '_yadis_services__openid_consumer_', '_openid_consumer_last_token' ) def __getitem__(self, name): value = super(OpenIdSessionWrapper, self).__getitem__(name) if name in self.pickle_instances: value = pickle.loads(value) return value def __setitem__(self, name, value): if name in self.pickle_instances: value = pickle.dumps(value, 0) super(OpenIdSessionWrapper, self).__setitem__(name, value) def get(self, name, default=None): try: return self[name] except KeyError: return default python-social-auth-0.2.21/social/storage/0000755000175500017550000000000012754357263020012 5ustar debacledebaclepython-social-auth-0.2.21/social/storage/sqlalchemy_orm.py0000644000175500017550000001614012754357263023405 0ustar debacledebacle"""SQLAlchemy models for Social Auth""" import base64 import six import json try: import transaction except ImportError: transaction = None from sqlalchemy import Column, Integer, String from sqlalchemy.exc import IntegrityError from sqlalchemy.types import PickleType, Text from sqlalchemy.schema import UniqueConstraint from sqlalchemy.ext.mutable import MutableDict from social.storage.base import UserMixin, AssociationMixin, NonceMixin, \ CodeMixin, BaseStorage # JSON type field class JSONType(PickleType): impl = Text def __init__(self, *args, **kwargs): kwargs['pickler'] = json super(JSONType, self).__init__(*args, **kwargs) class SQLAlchemyMixin(object): COMMIT_SESSION = True @classmethod def _session(cls): raise NotImplementedError('Implement in subclass') @classmethod def _query(cls): return cls._session().query(cls) @classmethod def _new_instance(cls, model, *args, **kwargs): return cls._save_instance(model(*args, **kwargs)) @classmethod def _save_instance(cls, instance): cls._session().add(instance) if cls.COMMIT_SESSION: cls._session().commit() cls._session().flush() else: cls._flush() return instance @classmethod def _flush(cls): try: cls._session().flush() except AssertionError: if transaction: with transaction.manager as manager: manager.commit() else: cls._session().commit() def save(self): self._save_instance(self) class SQLAlchemyUserMixin(SQLAlchemyMixin, UserMixin): """Social Auth association model""" __tablename__ = 'social_auth_usersocialauth' __table_args__ = (UniqueConstraint('provider', 'uid'),) id = Column(Integer, primary_key=True) provider = Column(String(32)) extra_data = Column(MutableDict.as_mutable(JSONType)) uid = None user_id = None user = None @classmethod def changed(cls, user): cls._save_instance(user) def set_extra_data(self, extra_data=None): if super(SQLAlchemyUserMixin, self).set_extra_data(extra_data): self._save_instance(self) @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): if association_id is not None: qs = cls._query().filter(cls.id != association_id) else: qs = cls._query().filter(cls.provider != backend_name) qs = qs.filter(cls.user == user) if hasattr(user, 'has_usable_password'): # TODO valid_password = user.has_usable_password() else: valid_password = True return valid_password or qs.count() > 0 @classmethod def disconnect(cls, entry): cls._session().delete(entry) cls._flush() @classmethod def user_query(cls): return cls._session().query(cls.user_model()) @classmethod def 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. """ return cls.user_query().filter_by(*args, **kwargs).count() > 0 @classmethod def get_username(cls, user): return getattr(user, 'username', None) @classmethod def create_user(cls, *args, **kwargs): return cls._new_instance(cls.user_model(), *args, **kwargs) @classmethod def get_user(cls, pk): return cls.user_query().get(pk) @classmethod def get_users_by_email(cls, email): return cls.user_query().filter_by(email=email) @classmethod def get_social_auth(cls, provider, uid): if not isinstance(uid, six.string_types): uid = str(uid) try: return cls._query().filter_by(provider=provider, uid=uid)[0] except IndexError: return None @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): qs = cls._query().filter_by(user_id=user.id) if provider: qs = qs.filter_by(provider=provider) if id: qs = qs.filter_by(id=id) return qs @classmethod def create_social_auth(cls, user, uid, provider): if not isinstance(uid, six.string_types): uid = str(uid) return cls._new_instance(cls, user=user, uid=uid, provider=provider) class SQLAlchemyNonceMixin(SQLAlchemyMixin, NonceMixin): __tablename__ = 'social_auth_nonce' __table_args__ = (UniqueConstraint('server_url', 'timestamp', 'salt'),) id = Column(Integer, primary_key=True) server_url = Column(String(255)) timestamp = Column(Integer) salt = Column(String(40)) @classmethod def use(cls, server_url, timestamp, salt): kwargs = {'server_url': server_url, 'timestamp': timestamp, 'salt': salt} try: return cls._query().filter_by(**kwargs)[0] except IndexError: return cls._new_instance(cls, **kwargs) class SQLAlchemyAssociationMixin(SQLAlchemyMixin, AssociationMixin): __tablename__ = 'social_auth_association' __table_args__ = (UniqueConstraint('server_url', 'handle'),) id = Column(Integer, primary_key=True) server_url = Column(String(255)) handle = Column(String(255)) secret = Column(String(255)) # base64 encoded issued = Column(Integer) lifetime = Column(Integer) assoc_type = Column(String(64)) @classmethod def store(cls, server_url, association): # Don't use get_or_create because issued cannot be null try: assoc = cls._query().filter_by(server_url=server_url, handle=association.handle)[0] except IndexError: assoc = cls(server_url=server_url, handle=association.handle) assoc.secret = base64.encodestring(association.secret).decode() assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type cls._save_instance(assoc) @classmethod def get(cls, *args, **kwargs): return cls._query().filter_by(*args, **kwargs) @classmethod def remove(cls, ids_to_delete): cls._query().filter(cls.id.in_(ids_to_delete)).delete( synchronize_session='fetch' ) class SQLAlchemyCodeMixin(SQLAlchemyMixin, CodeMixin): __tablename__ = 'social_auth_code' __table_args__ = (UniqueConstraint('code', 'email'),) id = Column(Integer, primary_key=True) email = Column(String(200)) code = Column(String(32), index=True) @classmethod def get_code(cls, code): return cls._query().filter(cls.code == code).first() class BaseSQLAlchemyStorage(BaseStorage): user = SQLAlchemyUserMixin nonce = SQLAlchemyNonceMixin association = SQLAlchemyAssociationMixin code = SQLAlchemyCodeMixin @classmethod def is_integrity_error(cls, exception): return exception.__class__ is IntegrityError python-social-auth-0.2.21/social/storage/peewee_orm.py0000644000175500017550000001401012754357263022507 0ustar debacledebacleimport six import base64 from peewee import CharField, Model, Proxy, IntegrityError from playhouse.kv import JSONField from social.storage.base import UserMixin, AssociationMixin, NonceMixin, \ CodeMixin, BaseStorage def get_query_by_dict_param(cls, params): query = True for field_name, value in params.iteritems(): query_item = cls._meta.fields[field_name] == value query = query & query_item return query database_proxy = Proxy() class BaseModel(Model): class Meta: database = database_proxy class PeeweeUserMixin(UserMixin, BaseModel): provider = CharField() extra_data = JSONField(null=True) uid = CharField() user = None @classmethod def changed(cls, user): user.save() def set_extra_data(self, extra_data=None): if super(PeeweeUserMixin, self).set_extra_data(extra_data): self.save() @classmethod def username_max_length(cls): username_field = cls.username_field() field = getattr(cls.user_model(), username_field) return field.max_length @classmethod def username_field(cls): return getattr(cls.user_model(), 'USERNAME_FIELD', 'username') @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): if association_id is not None: query = cls.select().where(cls.id != association_id) else: query = cls.select().where(cls.provider != backend_name) query = query.where(cls.user == user) if hasattr(user, 'has_usable_password'): valid_password = user.has_usable_password() else: valid_password = True return valid_password or query.count() > 0 @classmethod def disconnect(cls, entry): entry.delete_instance() @classmethod def user_exists(cls, *args, **kwargs): """ Return True/False if a User instance exists with the given arguments. """ user_model = cls.user_model() query = get_query_by_dict_param(user_model, kwargs) return user_model.select().where(query).count() > 0 @classmethod def get_username(cls, user): return getattr(user, cls.username_field(), None) @classmethod def create_user(cls, *args, **kwargs): username_field = cls.username_field() if 'username' in kwargs and username_field not in kwargs: kwargs[username_field] = kwargs.pop('username') return cls.user_model().create(*args, **kwargs) @classmethod def get_user(cls, pk, **kwargs): if pk: kwargs = {'id': pk} try: return cls.user_model().select().get( get_query_by_dict_param(cls.user_model(), kwargs) ) except cls.user_model().DoesNotExist: return None @classmethod def get_users_by_email(cls, email): user_model = cls.user_model() return user_model.select().where(user_model.email == email) @classmethod def get_social_auth(cls, provider, uid): if not isinstance(uid, six.string_types): uid = str(uid) try: return cls.select().where( cls.provider == provider, cls.uid == uid ).get() except cls.DoesNotExist: return None @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): query = cls.select().where(cls.user == user) if provider: query = query.where(cls.provider == provider) if id: query = query.where(cls.id == id) return list(query) @classmethod def create_social_auth(cls, user, uid, provider): if not isinstance(uid, six.string_types): uid = str(uid) return cls.create(user=user, uid=uid, provider=provider) class PeeweeNonceMixin(NonceMixin, BaseModel): server_url = CharField() timestamp = CharField() salt = CharField() @classmethod def use(cls, server_url, timestamp, salt): return cls.select().get_or_create(cls.server_url == server_url, cls.timestamp == timestamp, cls.salt == salt) class PeeweeAssociationMixin(AssociationMixin, BaseModel): server_url = CharField() handle = CharField() secret = CharField() # base64 encoded issued = CharField() lifetime = CharField() assoc_type = CharField() @classmethod def store(cls, server_url, association): try: assoc = cls.select().get(cls.server_url == server_url, cls.handle == association.handle) except cls.DoesNotExist: assoc = cls(server_url=server_url, handle=association.handle) assoc.secret = base64.encodestring(association.secret) assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type assoc.save() @classmethod def get(cls, *args, **kwargs): query = get_query_by_dict_param(cls, kwargs) return cls.select().where(query) @classmethod def remove(cls, ids_to_delete): cls.select().where(cls.id << ids_to_delete).delete() class PeeweeCodeMixin(CodeMixin, BaseModel): email = CharField() code = CharField() # base64 encoded issued = CharField() @classmethod def get_code(cls, code): try: return cls.select().get(cls.code == code) except cls.DoesNotExist: return None class BasePeeweeStorage(BaseStorage): user = PeeweeUserMixin nonce = PeeweeNonceMixin association = PeeweeAssociationMixin code = PeeweeCodeMixin @classmethod def is_integrity_error(cls, exception): return exception.__class__ is IntegrityError python-social-auth-0.2.21/social/storage/__init__.py0000644000175500017550000000000012754357263022111 0ustar debacledebaclepython-social-auth-0.2.21/social/storage/mongoengine_orm.py0000644000175500017550000001343212754357263023551 0ustar debacledebacleimport base64 import six from mongoengine import DictField, IntField, StringField, \ EmailField, BooleanField from mongoengine.queryset import OperationError from social.storage.base import UserMixin, AssociationMixin, NonceMixin, \ CodeMixin, BaseStorage UNUSABLE_PASSWORD = '!' # Borrowed from django 1.4 class MongoengineUserMixin(UserMixin): """Social Auth association model""" user = None provider = StringField(max_length=32) uid = StringField(max_length=255, unique_with='provider') extra_data = DictField() def str_id(self): return str(self.id) @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): qs = cls.objects if provider: qs = qs.filter(provider=provider) if id: qs = qs.filter(id=id) return qs.filter(user=user.id) @classmethod def create_social_auth(cls, user, uid, provider): if not isinstance(type(uid), six.string_types): uid = str(uid) return cls.objects.create(user=user.id, uid=uid, provider=provider) @classmethod def username_max_length(cls): username_field = cls.username_field() field = getattr(cls.user_model(), username_field) return field.max_length @classmethod def username_field(cls): return getattr(cls.user_model(), 'USERNAME_FIELD', 'username') @classmethod def create_user(cls, *args, **kwargs): kwargs['password'] = UNUSABLE_PASSWORD if 'email' in kwargs: # Empty string makes email regex validation fail kwargs['email'] = kwargs['email'] or None return cls.user_model().objects.create(*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 @classmethod def changed(cls, user): user.save() def set_extra_data(self, extra_data=None): if super(MongoengineUserMixin, self).set_extra_data(extra_data): self.save() @classmethod def disconnect(cls, entry): entry.delete() @classmethod def 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. """ if 'username' in kwargs: kwargs[cls.username_field()] = kwargs.pop('username') return cls.user_model().objects.filter(*args, **kwargs).count() > 0 @classmethod def get_username(cls, user): return getattr(user, cls.username_field(), None) @classmethod def get_user(cls, pk): try: return cls.user_model().objects.get(id=pk) except cls.user_model().DoesNotExist: return None @classmethod def get_users_by_email(cls, email): return cls.user_model().objects.filter(email__iexact=email) @classmethod def get_social_auth(cls, provider, uid): if not isinstance(uid, six.string_types): uid = str(uid) try: return cls.objects.get(provider=provider, uid=uid) except cls.DoesNotExist: return None class MongoengineNonceMixin(NonceMixin): """One use numbers""" server_url = StringField(max_length=255) timestamp = IntField() salt = StringField(max_length=40) @classmethod def use(cls, server_url, timestamp, salt): return cls.objects.get_or_create(server_url=server_url, timestamp=timestamp, salt=salt)[1] class MongoengineAssociationMixin(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) @classmethod def store(cls, server_url, association): # Don't use get_or_create because issued cannot be null try: assoc = cls.objects.get(server_url=server_url, handle=association.handle) except cls.DoesNotExist: assoc = cls(server_url=server_url, handle=association.handle) assoc.secret = base64.encodestring(association.secret) assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type assoc.save() @classmethod def get(cls, *args, **kwargs): return cls.objects.filter(*args, **kwargs) @classmethod def remove(cls, ids_to_delete): cls.objects.filter(pk__in=ids_to_delete).delete() class MongoengineCodeMixin(CodeMixin): email = EmailField() code = StringField(max_length=32) verified = BooleanField(default=False) @classmethod def get_code(cls, code): try: return cls.objects.get(code=code) except cls.DoesNotExist: return None class BaseMongoengineStorage(BaseStorage): user = MongoengineUserMixin nonce = MongoengineNonceMixin association = MongoengineAssociationMixin code = MongoengineCodeMixin @classmethod def is_integrity_error(cls, exception): return exception.__class__ is OperationError and \ 'E11000' in exception.message python-social-auth-0.2.21/social/storage/base.py0000644000175500017550000001752712754357263021312 0ustar debacledebacle"""Models mixins for Social Auth""" import re import time import base64 import uuid import warnings from datetime import datetime, timedelta import six from openid.association import Association as OpenIdAssociation from social.backends.utils import get_backend from social.strategies.utils import get_current_strategy CLEAN_USERNAME_REGEX = re.compile(r'[^\w.@+_-]+', re.UNICODE) class UserMixin(object): user = '' provider = '' uid = None extra_data = None def get_backend(self, strategy=None): strategy = strategy or get_current_strategy() if strategy: return get_backend(strategy.get_backends(), self.provider) def get_backend_instance(self, strategy=None): strategy = strategy or get_current_strategy() Backend = self.get_backend(strategy) if Backend: return Backend(strategy=strategy) @property def access_token(self): """Return access_token stored in extra_data or None""" return self.extra_data.get('access_token') @property def tokens(self): warnings.warn('tokens is deprecated, use access_token instead') return self.access_token def refresh_token(self, strategy, *args, **kwargs): token = self.extra_data.get('refresh_token') or \ self.extra_data.get('access_token') backend = self.get_backend(strategy) if token and backend and hasattr(backend, 'refresh_token'): backend = backend(strategy=strategy) response = backend.refresh_token(token, *args, **kwargs) if self.set_extra_data(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.get('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) def set_extra_data(self, extra_data=None): if extra_data and self.extra_data != extra_data: if self.extra_data and not isinstance( self.extra_data, six.string_types): self.extra_data.update(extra_data) else: self.extra_data = extra_data return True @classmethod def clean_username(cls, value): """Clean username removing any unsupported character""" return CLEAN_USERNAME_REGEX.sub('', value) @classmethod def changed(cls, user): """The given user instance is ready to be saved""" raise NotImplementedError('Implement in subclass') @classmethod def get_username(cls, user): """Return the username for given user""" raise NotImplementedError('Implement in subclass') @classmethod def user_model(cls): """Return the user model""" raise NotImplementedError('Implement in subclass') @classmethod def username_max_length(cls): """Return the max length for username""" raise NotImplementedError('Implement in subclass') @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): """Return if it's safe to disconnect the social account for the given user""" raise NotImplementedError('Implement in subclass') @classmethod def disconnect(cls, entry): """Disconnect the social account for the given user""" raise NotImplementedError('Implement in subclass') @classmethod def 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. """ raise NotImplementedError('Implement in subclass') @classmethod def create_user(cls, *args, **kwargs): """Create a user instance""" raise NotImplementedError('Implement in subclass') @classmethod def get_user(cls, pk): """Return user instance for given id""" raise NotImplementedError('Implement in subclass') @classmethod def get_users_by_email(cls, email): """Return users instances for given email address""" raise NotImplementedError('Implement in subclass') @classmethod def get_social_auth(cls, provider, uid): """Return UserSocialAuth for given provider and uid""" raise NotImplementedError('Implement in subclass') @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): """Return all the UserSocialAuth instances for given user""" raise NotImplementedError('Implement in subclass') @classmethod def create_social_auth(cls, user, uid, provider): """Create a UserSocialAuth instance for given user""" raise NotImplementedError('Implement in subclass') class NonceMixin(object): """One use numbers""" server_url = '' timestamp = 0 salt = '' @classmethod def use(cls, server_url, timestamp, salt): """Create a Nonce instance""" raise NotImplementedError('Implement in subclass') class AssociationMixin(object): """OpenId account association""" server_url = '' handle = '' secret = '' issued = 0 lifetime = 0 assoc_type = '' @classmethod def oids(cls, server_url, handle=None): kwargs = {'server_url': server_url} if handle is not None: kwargs['handle'] = handle return sorted([(assoc.id, cls.openid_association(assoc)) for assoc in cls.get(**kwargs) ], key=lambda x: x[1].issued, reverse=True) @classmethod def openid_association(cls, assoc): secret = assoc.secret if not isinstance(secret, six.binary_type): secret = secret.encode() return OpenIdAssociation(assoc.handle, base64.decodestring(secret), assoc.issued, assoc.lifetime, assoc.assoc_type) @classmethod def store(cls, server_url, association): """Create an Association instance""" raise NotImplementedError('Implement in subclass') @classmethod def get(cls, *args, **kwargs): """Get an Association instance""" raise NotImplementedError('Implement in subclass') @classmethod def remove(cls, ids_to_delete): """Remove an Association instance""" raise NotImplementedError('Implement in subclass') class CodeMixin(object): email = '' code = '' verified = False def verify(self): self.verified = True self.save() @classmethod def generate_code(cls): return uuid.uuid4().hex @classmethod def make_code(cls, email): code = cls() code.email = email code.code = cls.generate_code() code.verified = False code.save() return code @classmethod def get_code(cls, code): raise NotImplementedError('Implement in subclass') class BaseStorage(object): user = UserMixin nonce = NonceMixin association = AssociationMixin code = CodeMixin @classmethod def is_integrity_error(cls, exception): """Check if given exception flags an integrity error in the DB""" raise NotImplementedError('Implement in subclass') python-social-auth-0.2.21/social/storage/django_orm.py0000644000175500017550000001350312754357263022505 0ustar debacledebacle"""Django ORM models for Social Auth""" import base64 import six import sys from django.db import transaction from django.db.utils import IntegrityError from social.storage.base import UserMixin, AssociationMixin, NonceMixin, \ CodeMixin, BaseStorage class DjangoUserMixin(UserMixin): """Social Auth association model""" @classmethod def changed(cls, user): user.save() def set_extra_data(self, extra_data=None): if super(DjangoUserMixin, self).set_extra_data(extra_data): self.save() @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 disconnect(cls, entry): entry.delete() @classmethod def username_field(cls): return getattr(cls.user_model(), 'USERNAME_FIELD', 'username') @classmethod def 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. """ if 'username' in kwargs: kwargs[cls.username_field()] = kwargs.pop('username') return cls.user_model().objects.filter(*args, **kwargs).count() > 0 @classmethod def get_username(cls, user): return getattr(user, cls.username_field(), None) @classmethod def create_user(cls, *args, **kwargs): username_field = cls.username_field() if 'username' in kwargs and username_field not in kwargs: kwargs[username_field] = kwargs.pop('username') try: user = cls.user_model().objects.create_user(*args, **kwargs) except IntegrityError: # User might have been created on a different thread, try and find them. # If we don't, re-raise the IntegrityError. exc_info = sys.exc_info() # If email comes in as None it won't get found in the get if kwargs.get('email', True) is None: kwargs['email'] = '' try: user = cls.user_model().objects.get(*args, **kwargs) except cls.user_model().DoesNotExist: six.reraise(*exc_info) return user @classmethod def get_user(cls, pk=None, **kwargs): if pk: kwargs = {'pk': pk} try: return cls.user_model().objects.get(**kwargs) except cls.user_model().DoesNotExist: return None @classmethod def get_users_by_email(cls, email): user_model = cls.user_model() email_field = getattr(user_model, 'EMAIL_FIELD', 'email') return user_model.objects.filter(**{email_field + '__iexact': email}) @classmethod def get_social_auth(cls, provider, uid): if not isinstance(uid, six.string_types): 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, provider=None, id=None): qs = user.social_auth.all() if provider: qs = qs.filter(provider=provider) if id: qs = qs.filter(id=id) return qs @classmethod def create_social_auth(cls, user, uid, provider): if not isinstance(uid, six.string_types): uid = str(uid) if hasattr(transaction, 'atomic'): # In Django versions that have an "atomic" transaction decorator / context # manager, there's a transaction wrapped around this call. # If the create fails below due to an IntegrityError, ensure that the transaction # stays undamaged by wrapping the create in an atomic. with transaction.atomic(): social_auth = cls.objects.create(user=user, uid=uid, provider=provider) else: social_auth = cls.objects.create(user=user, uid=uid, provider=provider) return social_auth class DjangoNonceMixin(NonceMixin): @classmethod def use(cls, server_url, timestamp, salt): return cls.objects.get_or_create(server_url=server_url, timestamp=timestamp, salt=salt)[1] class DjangoAssociationMixin(AssociationMixin): @classmethod def store(cls, server_url, association): # Don't use get_or_create because issued cannot be null try: assoc = cls.objects.get(server_url=server_url, handle=association.handle) except cls.DoesNotExist: assoc = cls(server_url=server_url, handle=association.handle) assoc.secret = base64.encodestring(association.secret) assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type assoc.save() @classmethod def get(cls, *args, **kwargs): return cls.objects.filter(*args, **kwargs) @classmethod def remove(cls, ids_to_delete): cls.objects.filter(pk__in=ids_to_delete).delete() class DjangoCodeMixin(CodeMixin): @classmethod def get_code(cls, code): try: return cls.objects.get(code=code) except cls.DoesNotExist: return None class BaseDjangoStorage(BaseStorage): user = DjangoUserMixin nonce = DjangoNonceMixin association = DjangoAssociationMixin code = DjangoCodeMixin python-social-auth-0.2.21/social/tests/0000755000175500017550000000000012754357263017510 5ustar debacledebaclepython-social-auth-0.2.21/social/tests/test_storage.py0000644000175500017550000001444112754357263022571 0ustar debacledebacleimport six import random import unittest2 as unittest from social.strategies.base import BaseStrategy from social.storage.base import UserMixin, NonceMixin, AssociationMixin, \ CodeMixin, BaseStorage from social.tests.models import User NOT_IMPLEMENTED_MSG = 'Implement in subclass' class BrokenUser(UserMixin): pass class BrokenAssociation(AssociationMixin): pass class BrokenNonce(NonceMixin): pass class BrokenCode(CodeMixin): pass class BrokenStrategy(BaseStrategy): pass class BrokenStrategyWithSettings(BrokenStrategy): def get_setting(self, name): raise AttributeError() class BrokenStorage(BaseStorage): pass class BrokenUserTests(unittest.TestCase): def setUp(self): self.user = BrokenUser def tearDown(self): self.user = None def test_get_username(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.get_username(User('foobar')) def test_user_model(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.user_model() def test_username_max_length(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.username_max_length() def test_get_user(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.get_user(1) def test_get_social_auth(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.get_social_auth('foo', 1) def test_get_social_auth_for_user(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.get_social_auth_for_user(User('foobar')) def test_create_social_auth(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.create_social_auth(User('foobar'), 1, 'foo') def test_disconnect(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.disconnect(BrokenUser()) class BrokenAssociationTests(unittest.TestCase): def setUp(self): self.association = BrokenAssociation def tearDown(self): self.association = None def test_store(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.association.store('http://foobar.com', BrokenAssociation()) def test_get(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.association.get() def test_remove(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.association.remove([1, 2, 3]) class BrokenNonceTests(unittest.TestCase): def setUp(self): self.nonce = BrokenNonce def tearDown(self): self.nonce = None def test_use(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.nonce.use('http://foobar.com', 1364951922, 'foobar123') class BrokenCodeTest(unittest.TestCase): def setUp(self): self.code = BrokenCode def tearDown(self): self.code = None def test_get_code(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.code.get_code('foobar') class BrokenStrategyTests(unittest.TestCase): def setUp(self): self.strategy = BrokenStrategy(storage=BrokenStorage) def tearDown(self): self.strategy = None def test_redirect(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.redirect('http://foobar.com') def test_get_setting(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.get_setting('foobar') def test_html(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.html('

foobar

') def test_request_data(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.request_data() def test_request_host(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.request_host() def test_session_get(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.session_get('foobar') def test_session_set(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.session_set('foobar', 123) def test_session_pop(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.session_pop('foobar') def test_build_absolute_uri(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.build_absolute_uri('/foobar') def test_render_html_with_tpl(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.render_html('foobar.html', context={}) def test_render_html_with_html(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.render_html(html='

foobar

', context={}) def test_render_html_with_none(self): with self.assertRaisesRegexp(ValueError, 'Missing template or html parameters'): self.strategy.render_html() def test_is_integrity_error(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.storage.is_integrity_error(None) def test_random_string(self): self.assertTrue(isinstance(self.strategy.random_string(), six.string_types)) def test_random_string_without_systemrandom(self): def SystemRandom(): raise NotImplementedError() orig_random = getattr(random, 'SystemRandom', None) random.SystemRandom = SystemRandom strategy = BrokenStrategyWithSettings(storage=BrokenStorage) self.assertTrue(isinstance(strategy.random_string(), six.string_types)) random.SystemRandom = orig_random python-social-auth-0.2.21/social/tests/models.py0000644000175500017550000001276612754357263021361 0ustar debacledebacleimport base64 from social.storage.base import UserMixin, NonceMixin, AssociationMixin, \ CodeMixin, BaseStorage class BaseModel(object): @classmethod def next_id(cls): cls.NEXT_ID += 1 return cls.NEXT_ID - 1 @classmethod def get(cls, key): return cls.cache.get(key) @classmethod def reset_cache(cls): cls.cache = {} class User(BaseModel): NEXT_ID = 1 cache = {} _is_active = True def __init__(self, username, email=None, **extra_user_fields): self.id = User.next_id() self.username = username self.email = email self.password = None self.slug = None self.social = [] self.extra_data = {} self.extra_user_fields = extra_user_fields self.save() def is_active(self): return self._is_active @classmethod def set_active(cls, is_active=True): cls._is_active = is_active def set_password(self, password): self.password = password def save(self): User.cache[self.username] = self class TestUserSocialAuth(UserMixin, BaseModel): NEXT_ID = 1 cache = {} cache_by_uid = {} def __init__(self, user, provider, uid, extra_data=None): self.id = TestUserSocialAuth.next_id() self.user = user self.provider = provider self.uid = uid self.extra_data = extra_data or {} self.user.social.append(self) TestUserSocialAuth.cache_by_uid[uid] = self def save(self): pass @classmethod def reset_cache(cls): cls.cache = {} cls.cache_by_uid = {} @classmethod def changed(cls, user): pass @classmethod def get_username(cls, user): return user.username @classmethod def user_model(cls): return User @classmethod def username_max_length(cls): return 1024 @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): return user.password or len(user.social) > 1 @classmethod def disconnect(cls, entry): cls.cache.pop(entry.id, None) entry.user.social = [s for s in entry.user.social if entry != s] @classmethod def user_exists(cls, username): return User.cache.get(username) is not None @classmethod def create_user(cls, username, email=None, **extra_user_fields): return User(username=username, email=email, **extra_user_fields) @classmethod def get_user(cls, pk): for username, user in User.cache.items(): if user.id == pk: return user @classmethod def get_social_auth(cls, provider, uid): social_user = cls.cache_by_uid.get(uid) if social_user and social_user.provider == provider: return social_user @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): return [usa for usa in user.social if provider in (None, usa.provider) and id in (None, usa.id)] @classmethod def create_social_auth(cls, user, uid, provider): return cls(user=user, provider=provider, uid=uid) @classmethod def get_users_by_email(cls, email): return [user for user in User.cache.values() if user.email == email] class TestNonce(NonceMixin, BaseModel): NEXT_ID = 1 cache = {} def __init__(self, server_url, timestamp, salt): self.id = TestNonce.next_id() self.server_url = server_url self.timestamp = timestamp self.salt = salt @classmethod def use(cls, server_url, timestamp, salt): nonce = TestNonce(server_url, timestamp, salt) TestNonce.cache[server_url] = nonce return nonce class TestAssociation(AssociationMixin, BaseModel): NEXT_ID = 1 cache = {} def __init__(self, server_url, handle): self.id = TestAssociation.next_id() self.server_url = server_url self.handle = handle def save(self): TestAssociation.cache[(self.server_url, self.handle)] = self @classmethod def store(cls, server_url, association): assoc = TestAssociation.cache.get((server_url, association.handle)) if assoc is None: assoc = TestAssociation(server_url=server_url, handle=association.handle) assoc.secret = base64.encodestring(association.secret) assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type assoc.save() @classmethod def get(cls, server_url=None, handle=None): result = [] for assoc in TestAssociation.cache.values(): if server_url and assoc.server_url != server_url: continue if handle and assoc.handle != handle: continue result.append(assoc) return result @classmethod def remove(cls, ids_to_delete): assoc = filter(lambda a: a.id in ids_to_delete, TestAssociation.cache.values()) for a in list(assoc): TestAssociation.cache.pop((a.server_url, a.handle), None) class TestCode(CodeMixin, BaseModel): NEXT_ID = 1 cache = {} @classmethod def get_code(cls, code): for c in cls.cache.values(): if c.code == code: return c class TestStorage(BaseStorage): user = TestUserSocialAuth nonce = TestNonce association = TestAssociation code = TestCode python-social-auth-0.2.21/social/tests/requirements-python3.txt0000644000175500017550000000017712754357263024403 0ustar debacledebaclehttpretty==0.6.5 coverage>=3.6 mock==1.0.1 nose>=1.2.1 rednose>=0.4.1 requests>=1.1.0 PyJWT>=1.0.0,<2.0.0 unittest2py3k==0.5.1 python-social-auth-0.2.21/social/tests/__init__.py0000644000175500017550000000000012754357263021607 0ustar debacledebaclepython-social-auth-0.2.21/social/tests/strategy.py0000644000175500017550000000707112754357263021731 0ustar debacledebaclefrom social.strategies.base import BaseStrategy, BaseTemplateStrategy TEST_URI = 'http://myapp.com' TEST_HOST = 'myapp.com' class Redirect(object): def __init__(self, url): self.url = url class TestTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): return tpl def render_string(self, html, context): return html class TestStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = TestTemplateStrategy def __init__(self, storage, tpl=None): self._request_data = {} self._settings = {} self._session = {} super(TestStrategy, self).__init__(storage, tpl) def redirect(self, url): return Redirect(url) def get_setting(self, name): """Return value for given setting name""" return self._settings[name] def html(self, content): """Return HTTP response with given content""" return content def render_html(self, tpl=None, html=None, context=None): """Render given template or raw html with given context""" return tpl or html def request_data(self, merge=True): """Return current request data (POST or GET)""" return self._request_data def request_host(self): """Return current host value""" return TEST_HOST def request_is_secure(self): """ Is the request using HTTPS? """ return False def request_path(self): """ path of the current request """ return '' def request_port(self): """ Port in use for this request """ return 80 def request_get(self): """ Request GET data """ return self._request_data.copy() def request_post(self): """ Request POST data """ return self._request_data.copy() def session_get(self, name, default=None): """Return session value for given key""" return self._session.get(name, default) def session_set(self, name, value): """Set session value for given key""" self._session[name] = value def session_pop(self, name): """Pop session value for given key""" return self._session.pop(name, None) def build_absolute_uri(self, path=None): """Build absolute URI with given (optional) path""" path = path or '' if path.startswith('http://') or path.startswith('https://'): return path return TEST_URI + path def set_settings(self, values): self._settings.update(values) def set_request_data(self, values, backend): self._request_data.update(values) backend.data = self._request_data def remove_from_request_data(self, name): self._request_data.pop(name, None) def authenticate(self, *args, **kwargs): user = super(TestStrategy, self).authenticate(*args, **kwargs) if isinstance(user, self.storage.user.user_model()): self.session_set('username', user.username) return user def get_pipeline(self): return self.setting('PIPELINE', ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.social_auth.associate_by_email', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details')) python-social-auth-0.2.21/social/tests/test_pipeline.py0000644000175500017550000001655112754357263022736 0ustar debacledebacleimport json from social.exceptions import AuthException from social.tests.models import TestUserSocialAuth, TestStorage, User from social.tests.strategy import TestStrategy from social.tests.actions.actions import BaseActionTest class IntegrityError(Exception): pass class UnknownError(Exception): pass class IntegrityErrorUserSocialAuth(TestUserSocialAuth): @classmethod def create_social_auth(cls, user, uid, provider): raise IntegrityError() @classmethod def get_social_auth(cls, provider, uid): if not hasattr(cls, '_called_times'): cls._called_times = 0 cls._called_times += 1 if cls._called_times == 2: user = list(User.cache.values())[0] return IntegrityErrorUserSocialAuth(user, provider, uid) else: return super(IntegrityErrorUserSocialAuth, cls).get_social_auth( provider, uid ) class IntegrityErrorStorage(TestStorage): user = IntegrityErrorUserSocialAuth @classmethod def is_integrity_error(cls, exception): """Check if given exception flags an integrity error in the DB""" return isinstance(exception, IntegrityError) class UnknownErrorUserSocialAuth(TestUserSocialAuth): @classmethod def create_social_auth(cls, user, uid, provider): raise UnknownError() class UnknownErrorStorage(IntegrityErrorStorage): user = UnknownErrorUserSocialAuth class IntegrityErrorOnLoginTest(BaseActionTest): def setUp(self): self.strategy = TestStrategy(IntegrityErrorStorage) super(IntegrityErrorOnLoginTest, self).setUp() def test_integrity_error(self): self.do_login() class UnknownErrorOnLoginTest(BaseActionTest): def setUp(self): self.strategy = TestStrategy(UnknownErrorStorage) super(UnknownErrorOnLoginTest, self).setUp() def test_unknown_error(self): with self.assertRaises(UnknownError): self.do_login() class EmailAsUsernameTest(BaseActionTest): expected_username = 'foo@bar.com' def test_email_as_username(self): self.strategy.set_settings({ 'SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL': True }) self.do_login() class RandomUsernameTest(BaseActionTest): user_data_body = json.dumps({ 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_random_username(self): self.do_login(after_complete_checks=False) class SluggedUsernameTest(BaseActionTest): expected_username = 'foo-bar' user_data_body = json.dumps({ 'login': 'Foo Bar', 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_random_username(self): self.strategy.set_settings({ 'SOCIAL_AUTH_CLEAN_USERNAMES': False, 'SOCIAL_AUTH_SLUGIFY_USERNAMES': True }) self.do_login() class RepeatedUsernameTest(BaseActionTest): def test_random_username(self): User(username='foobar') self.do_login(after_complete_checks=False) self.assertTrue(self.strategy.session_get('username') .startswith('foobar')) class AssociateByEmailTest(BaseActionTest): def test_multiple_accounts_with_same_email(self): user = User(username='foobar1') user.email = 'foo@bar.com' self.do_login(after_complete_checks=False) self.assertTrue(self.strategy.session_get('username') .startswith('foobar')) class MultipleAccountsWithSameEmailTest(BaseActionTest): def test_multiple_accounts_with_same_email(self): user1 = User(username='foobar1') user2 = User(username='foobar2') user1.email = 'foo@bar.com' user2.email = 'foo@bar.com' with self.assertRaises(AuthException): self.do_login(after_complete_checks=False) class UserPersistsInPartialPipeline(BaseActionTest): def test_user_persists_in_partial_pipeline_kwargs(self): user = User(username='foobar1') user.email = 'foo@bar.com' self.strategy.set_settings({ 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.associate_by_email', 'social.tests.pipeline.set_user_from_kwargs' ) }) self.do_login(after_complete_checks=False) # Handle the partial pipeline self.strategy.session_set('attribute', 'testing') data = self.strategy.session_pop('partial_pipeline') idx, backend, xargs, xkwargs = self.strategy.partial_from_session(data) self.backend.continue_pipeline(pipeline_index=idx, *xargs, **xkwargs) def test_user_persists_in_partial_pipeline(self): user = User(username='foobar1') user.email = 'foo@bar.com' self.strategy.set_settings({ 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.associate_by_email', 'social.tests.pipeline.set_user_from_args' ) }) self.do_login(after_complete_checks=False) # Handle the partial pipeline self.strategy.session_set('attribute', 'testing') data = self.strategy.session_pop('partial_pipeline') idx, backend, xargs, xkwargs = self.strategy.partial_from_session(data) self.backend.continue_pipeline(pipeline_index=idx, *xargs, **xkwargs) python-social-auth-0.2.21/social/tests/README.rst0000644000175500017550000000260312754357263021200 0ustar debacledebacleTesting python-social-auth ========================== Testing the application is fairly simple. Just meet the dependencies and run the testing suite. The testing suite uses HTTPretty_ to mock server responses. It's not a live test against the provider's API. To do it that way, a browser and a tool like Selenium are needed. That's slow, it's prone to errors in some cases, and some of the application examples must be running to perform the testing. Plus, it requires real Key and Secret pairs, in the end it's a mess to test functionality which is the real point. By mocking the server responses, we can test the backend's functionality (and other areas too) easily and quickly. Installing dependencies ----------------------- Go to the tests_ directory and install the dependencies listed in the requirements.txt_. Then run with ``nosetests`` command. Pending ------- At the moment only OAuth1 and OAuth2 backends are being tested, and only login and partial pipeline features are covered by the test. There's still a lot to work on, like: * OpenId backends * Frameworks support * Failure cases (like authentication canceled, etc) * Extra data saving .. _HTTPretty: https://github.com/gabrielfalcao/HTTPretty .. _tests: https://github.com/omab/python-social-auth/tree/master/tests .. _requirements.txt: https://github.com/omab/python-social-auth/blob/master/tests/requirements.txt python-social-auth-0.2.21/social/tests/test_utils.py0000644000175500017550000001514512754357263022267 0ustar debacledebacleimport sys import unittest2 as unittest from mock import Mock from social.utils import sanitize_redirect, user_is_authenticated, \ user_is_active, slugify, build_absolute_uri, \ partial_pipeline_data PY3 = sys.version_info[0] == 3 class SanitizeRedirectTest(unittest.TestCase): def test_none_redirect(self): self.assertEqual(sanitize_redirect(['myapp.com'], None), None) def test_empty_redirect(self): self.assertEqual(sanitize_redirect(['myapp.com'], ''), None) def test_dict_redirect(self): self.assertEqual(sanitize_redirect(['myapp.com'], {}), None) def test_invalid_redirect(self): self.assertEqual(sanitize_redirect(['myapp.com'], {'foo': 'bar'}), None) def test_wrong_path_redirect(self): self.assertEqual( sanitize_redirect(['myapp.com'], 'http://notmyapp.com/path/'), None ) def test_valid_absolute_redirect(self): self.assertEqual( sanitize_redirect(['myapp.com'], 'http://myapp.com/path/'), 'http://myapp.com/path/' ) def test_valid_relative_redirect(self): self.assertEqual(sanitize_redirect(['myapp.com'], '/path/'), '/path/') def test_multiple_hosts(self): allowed_hosts = ['myapp1.com', 'myapp2.com'] for host in allowed_hosts: url = 'http://{}/path/'.format(host) self.assertEqual(sanitize_redirect(allowed_hosts, url), url) def test_multiple_hosts_wrong_host(self): self.assertEqual(sanitize_redirect( ['myapp1.com', 'myapp2.com'], 'http://notmyapp.com/path/'), None) class UserIsAuthenticatedTest(unittest.TestCase): def test_user_is_none(self): self.assertEqual(user_is_authenticated(None), False) def test_user_is_not_none(self): self.assertEqual(user_is_authenticated(object()), True) def test_user_has_is_authenticated(self): class User(object): is_authenticated = True self.assertEqual(user_is_authenticated(User()), True) def test_user_has_is_authenticated_callable(self): class User(object): def is_authenticated(self): return True self.assertEqual(user_is_authenticated(User()), True) class UserIsActiveTest(unittest.TestCase): def test_user_is_none(self): self.assertEqual(user_is_active(None), False) def test_user_is_not_none(self): self.assertEqual(user_is_active(object()), True) def test_user_has_is_active(self): class User(object): is_active = True self.assertEqual(user_is_active(User()), True) def test_user_has_is_active_callable(self): class User(object): def is_active(self): return True self.assertEqual(user_is_active(User()), True) class SlugifyTest(unittest.TestCase): def test_slugify_formats(self): if PY3: self.assertEqual(slugify('FooBar'), 'foobar') self.assertEqual(slugify('Foo Bar'), 'foo-bar') self.assertEqual(slugify('Foo (Bar)'), 'foo-bar') else: self.assertEqual(slugify('FooBar'.decode('utf-8')), 'foobar') self.assertEqual(slugify('Foo Bar'.decode('utf-8')), 'foo-bar') self.assertEqual(slugify('Foo (Bar)'.decode('utf-8')), 'foo-bar') class BuildAbsoluteURITest(unittest.TestCase): def setUp(self): self.host = 'http://foobar.com' def tearDown(self): self.host = None def test_path_none(self): self.assertEqual(build_absolute_uri(self.host), self.host) def test_path_empty(self): self.assertEqual(build_absolute_uri(self.host, ''), self.host) def test_path_http(self): self.assertEqual(build_absolute_uri(self.host, 'http://barfoo.com'), 'http://barfoo.com') def test_path_https(self): self.assertEqual(build_absolute_uri(self.host, 'https://barfoo.com'), 'https://barfoo.com') def test_host_ends_with_slash_and_path_starts_with_slash(self): self.assertEqual(build_absolute_uri(self.host + '/', '/foo/bar'), 'http://foobar.com/foo/bar') def test_absolute_uri(self): self.assertEqual(build_absolute_uri(self.host, '/foo/bar'), 'http://foobar.com/foo/bar') class PartialPipelineData(unittest.TestCase): def test_returns_partial_when_uid_and_email_do_match(self): email = 'foo@example.com' backend = self._backend({'uid': email}) backend.strategy.request_data.return_value = { backend.ID_KEY: email } key, val = ('foo', 'bar') _, xkwargs = partial_pipeline_data(backend, None, *(), **dict([(key, val)])) self.assertTrue(key in xkwargs) self.assertEqual(xkwargs[key], val) self.assertEqual(backend.strategy.clean_partial_pipeline.call_count, 0) def test_clean_pipeline_when_uid_does_not_match(self): backend = self._backend({'uid': 'foo@example.com'}) backend.strategy.request_data.return_value = { backend.ID_KEY: 'bar@example.com' } key, val = ('foo', 'bar') ret = partial_pipeline_data(backend, None, *(), **dict([(key, val)])) self.assertIsNone(ret) self.assertEqual(backend.strategy.clean_partial_pipeline.call_count, 1) def test_kwargs_included_in_result(self): backend = self._backend() key, val = ('foo', 'bar') _, xkwargs = partial_pipeline_data(backend, None, *(), **dict([(key, val)])) self.assertTrue(key in xkwargs) self.assertEqual(xkwargs[key], val) self.assertEqual(backend.strategy.clean_partial_pipeline.call_count, 0) def test_update_user(self): user = object() backend = self._backend(session_kwargs={'user': None}) _, xkwargs = partial_pipeline_data(backend, user) self.assertTrue('user' in xkwargs) self.assertEqual(xkwargs['user'], user) self.assertEqual(backend.strategy.clean_partial_pipeline.call_count, 0) def _backend(self, session_kwargs=None): strategy = Mock() strategy.request = None strategy.request_data.return_value = {} strategy.session_get.return_value = object() strategy.partial_from_session.return_value = \ (0, 'mock-backend', [], session_kwargs or {}) backend = Mock() backend.ID_KEY = 'email' backend.name = 'mock-backend' backend.strategy = strategy return backend python-social-auth-0.2.21/social/tests/backends/0000755000175500017550000000000012754357263021262 5ustar debacledebaclepython-social-auth-0.2.21/social/tests/backends/test_taobao.py0000644000175500017550000000137412754357263024145 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class TaobaoOAuth2Test(OAuth2Test): backend_path = 'social.backends.taobao.TAOBAOAuth' user_data_url = 'https://eco.taobao.com/router/rest' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'w2_expires_in': 0, 'taobao_user_id': '1', 'taobao_user_nick': 'foobar', 'w1_expires_in': 1800, 're_expires_in': 0, 'r2_expires_in': 0, 'expires_in': 86400, 'r1_expires_in': 1800 }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_linkedin.py0000644000175500017550000000203212754357263024465 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test, OAuth2Test class BaseLinkedinTest(object): user_data_url = 'https://api.linkedin.com/v1/people/~:' \ '(first-name,id,last-name)' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'lastName': 'Bar', 'id': '1010101010', 'firstName': 'Foo' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class LinkedinOAuth1Test(BaseLinkedinTest, OAuth1Test): backend_path = 'social.backends.linkedin.LinkedinOAuth' request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) class LinkedinOAuth2Test(BaseLinkedinTest, OAuth2Test): backend_path = 'social.backends.linkedin.LinkedinOAuth2' python-social-auth-0.2.21/social/tests/backends/test_thisismyjam.py0000644000175500017550000000153612754357263025241 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class ThisIsMyJameOAuth1Test(OAuth1Test): backend_path = 'social.backends.thisismyjam.ThisIsMyJamOAuth1' user_data_url = 'http://api.thisismyjam.com/1/verify.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'id': 10101010, 'person': { 'name': 'foobar', 'fullname': 'Foo Bar' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/open_id.py0000644000175500017550000002030112754357263023245 0ustar debacledebacle# -*- coding: utf-8 -*- import datetime import json import sys from calendar import timegm import jwt import requests from openid import oidutil PY3 = sys.version_info[0] == 3 if PY3: from html.parser import HTMLParser HTMLParser # placate pyflakes else: from HTMLParser import HTMLParser from httpretty import HTTPretty sys.path.insert(0, '..') from social.utils import parse_qs, module_member from social.backends.utils import load_backends from social.exceptions import AuthTokenError from social.tests.backends.base import BaseBackendTest from social.tests.models import TestStorage, User, TestUserSocialAuth, \ TestNonce, TestAssociation from social.tests.strategy import TestStrategy # Patch to remove the too-verbose output until a new version is released oidutil.log = lambda *args, **kwargs: None class FormHTMLParser(HTMLParser): form = {} inputs = {} def handle_starttag(self, tag, attrs): attrs = dict(attrs) if tag == 'form': self.form.update(attrs) elif tag == 'input' and 'name' in attrs: self.inputs[attrs['name']] = attrs['value'] class OpenIdTest(BaseBackendTest): backend_path = None backend = None access_token_body = None user_data_body = None user_data_url = '' expected_username = '' settings = None partial_login_settings = None raw_complete_url = '/complete/{0}/' def setUp(self): HTTPretty.enable() Backend = module_member(self.backend_path) self.strategy = TestStrategy(TestStorage) self.complete_url = self.raw_complete_url.format(Backend.name) self.backend = Backend(self.strategy, redirect_uri=self.complete_url) self.strategy.set_settings({ 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( self.backend_path, 'social.tests.backends.test_broken.BrokenBackendAuth' ) }) # Force backends loading to trash PSA cache load_backends( self.strategy.get_setting('SOCIAL_AUTH_AUTHENTICATION_BACKENDS'), force_load=True ) def tearDown(self): self.strategy = None User.reset_cache() TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() HTTPretty.disable() def get_form_data(self, html): parser = FormHTMLParser() parser.feed(html) return parser.form, parser.inputs def openid_url(self): return self.backend.openid_url() def post_start(self): pass def do_start(self): HTTPretty.register_uri(HTTPretty.GET, self.openid_url(), status=200, body=self.discovery_body, content_type='application/xrds+xml') start = self.backend.start() self.post_start() form, inputs = self.get_form_data(start) HTTPretty.register_uri(HTTPretty.POST, form.get('action'), status=200, body=self.server_response) response = requests.post(form.get('action'), data=inputs) self.strategy.set_request_data(parse_qs(response.content), self.backend) HTTPretty.register_uri(HTTPretty.POST, form.get('action'), status=200, body='is_valid:true\n') return self.backend.complete() class OpenIdConnectTestMixin(object): """ Mixin to test OpenID Connect consumers. Inheriting classes should also inherit OAuth2Test. """ client_key = 'a-key' client_secret = 'a-secret-key' issuer = None # id_token issuer id_token_max_age = 600 # seconds def extra_settings(self): settings = super(OpenIdConnectTestMixin, self).extra_settings() settings.update({ 'SOCIAL_AUTH_{0}_KEY'.format(self.name): self.client_key, 'SOCIAL_AUTH_{0}_SECRET'.format(self.name): self.client_secret, 'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name): self.client_secret, 'SOCIAL_AUTH_{0}_ID_TOKEN_MAX_AGE'.format(self.name): self.id_token_max_age }) return settings def access_token_body(self, request, _url, headers): """ Get the nonce from the request parameters, add it to the id_token, and return the complete response. """ nonce = parse_qs(request.body).get('nonce') body = self.prepare_access_token_body(nonce=nonce) return 200, headers, body def get_id_token(self, client_key=None, expiration_datetime=None, issue_datetime=None, nonce=None, issuer=None): """ Return the id_token to be added to the access token body. """ id_token = { 'iss': issuer, 'nonce': nonce, 'aud': client_key, 'azp': client_key, 'exp': expiration_datetime, 'iat': issue_datetime, 'sub': '1234' } return id_token def prepare_access_token_body(self, client_key=None, client_secret=None, expiration_datetime=None, issue_datetime=None, nonce=None, issuer=None): """ Prepares a provider access token response. Arguments: client_id -- (str) OAuth ID for the client that requested authentication. client_secret -- (str) OAuth secret for the client that requested authentication. expiration_time -- (datetime) Date and time after which the response should be considered invalid. """ body = {'access_token': 'foobar', 'token_type': 'bearer'} client_key = client_key or self.client_key client_secret = client_secret or self.client_secret now = datetime.datetime.utcnow() expiration_datetime = expiration_datetime or \ (now + datetime.timedelta(seconds=30)) issue_datetime = issue_datetime or now nonce = nonce or 'a-nonce' issuer = issuer or self.issuer id_token = self.get_id_token( client_key, timegm(expiration_datetime.utctimetuple()), timegm(issue_datetime.utctimetuple()), nonce, issuer) body['id_token'] = jwt.encode(id_token, client_secret, algorithm='HS256').decode('utf-8') return json.dumps(body) def authtoken_raised(self, expected_message_regexp, **access_token_kwargs): self.access_token_body = self.prepare_access_token_body( **access_token_kwargs ) with self.assertRaisesRegexp(AuthTokenError, expected_message_regexp): self.do_login() def test_invalid_secret(self): self.authtoken_raised( 'Token error: Signature verification failed', client_secret='wrong!' ) def test_expired_signature(self): expiration_datetime = datetime.datetime.utcnow() - \ datetime.timedelta(seconds=30) self.authtoken_raised('Token error: Signature has expired', expiration_datetime=expiration_datetime) def test_invalid_issuer(self): self.authtoken_raised('Token error: Invalid issuer', issuer='someone-else') def test_invalid_audience(self): self.authtoken_raised('Token error: Invalid audience', client_key='someone-else') def test_invalid_issue_time(self): issue_datetime = datetime.datetime.utcnow() - \ datetime.timedelta(seconds=self.id_token_max_age + 1) self.authtoken_raised('Token error: Incorrect id_token: iat', issue_datetime=issue_datetime) def test_invalid_nonce(self): self.authtoken_raised( 'Token error: Incorrect id_token: nonce', nonce='something-wrong' ) python-social-auth-0.2.21/social/tests/backends/test_podio.py0000644000175500017550000000323112754357263024004 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class PodioOAuth2Test(OAuth2Test): backend_path = 'social.backends.podio.PodioOAuth2' user_data_url = 'https://api.podio.com/user/status' expected_username = 'user_1010101010' access_token_body = json.dumps({ 'token_type': 'bearer', 'access_token': '11309ea9016a4ad99f1a3bcb9bc7a9d1', 'refresh_token': '52d01df8b9ac46a4a6be1333d9f81ef2', 'expires_in': 28800, 'ref': { 'type': 'user', 'id': 1010101010, } }) user_data_body = json.dumps({ 'user': { 'user_id': 1010101010, 'activated_on': '2012-11-22 09:37:21', 'created_on': '2012-11-21 12:23:47', 'locale': 'en_GB', 'timezone': 'Europe/Copenhagen', 'mail': 'foo@bar.com', 'mails': [ { 'disabled': False, 'mail': 'foobar@example.com', 'primary': False, 'verified': True }, { 'disabled': False, 'mail': 'foo@bar.com', 'primary': True, 'verified': True } ], # more properties ... }, 'profile': { 'last_seen_on': '2013-05-16 12:21:13', 'link': 'https://podio.com/users/1010101010', 'name': 'Foo Bar', # more properties ... } # more properties ... }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_reddit.py0000644000175500017550000000333212754357263024147 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class RedditOAuth2Test(OAuth2Test): backend_path = 'social.backends.reddit.RedditOAuth2' user_data_url = 'https://oauth.reddit.com/api/v1/me.json' expected_username = 'foobar' access_token_body = json.dumps({ 'name': 'foobar', 'created': 1203420772.0, 'access_token': 'foobar-token', 'created_utc': 1203420772.0, 'expires_in': 3600.0, 'link_karma': 34, 'token_type': 'bearer', 'comment_karma': 167, 'over_18': True, 'is_gold': False, 'is_mod': True, 'scope': 'identity', 'has_verified_email': False, 'id': '33bma', 'refresh_token': 'foobar-refresh-token' }) user_data_body = json.dumps({ 'name': 'foobar', 'created': 1203420772.0, 'created_utc': 1203420772.0, 'link_karma': 34, 'comment_karma': 167, 'over_18': True, 'is_gold': False, 'is_mod': True, 'has_verified_email': False, 'id': '33bma' }) refresh_token_body = json.dumps({ 'access_token': 'foobar-new-token', 'token_type': 'bearer', 'expires_in': 3600.0, 'refresh_token': 'foobar-new-refresh-token', 'scope': 'identity' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def refresh_token_arguments(self): uri = self.strategy.build_absolute_uri('/complete/reddit/') return {'redirect_uri': uri} def test_refresh_token(self): user, social = self.do_refresh_token() self.assertEqual(social.extra_data['access_token'], 'foobar-new-token') python-social-auth-0.2.21/social/tests/backends/test_stackoverflow.py0000644000175500017550000000321512754357263025565 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth2Test class StackoverflowOAuth2Test(OAuth2Test): backend_path = 'social.backends.stackoverflow.StackoverflowOAuth2' user_data_url = 'https://api.stackexchange.com/2.1/me' expected_username = 'foobar' access_token_body = urlencode({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'items': [{ 'user_id': 101010, 'user_type': 'registered', 'creation_date': 1278525551, 'display_name': 'foobar', 'profile_image': 'http: //www.gravatar.com/avatar/' '5280f15cedf540b544eecc30fcf3027c?' 'd=identicon&r=PG', 'reputation': 547, 'reputation_change_day': 0, 'reputation_change_week': 0, 'reputation_change_month': 0, 'reputation_change_quarter': 65, 'reputation_change_year': 65, 'age': 22, 'last_access_date': 1363544705, 'last_modified_date': 1354035327, 'is_employee': False, 'link': 'http: //stackoverflow.com/users/101010/foobar', 'location': 'Fooland', 'account_id': 101010, 'badge_counts': { 'gold': 0, 'silver': 3, 'bronze': 6 } }], 'quota_remaining': 9997, 'quota_max': 10000, 'has_more': False }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_behance.py0000644000175500017550000000324412754357263024263 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class BehanceOAuth2Test(OAuth2Test): backend_path = 'social.backends.behance.BehanceOAuth2' access_token_body = json.dumps({ 'access_token': 'foobar', 'valid': 1, 'user': { 'username': 'foobar', 'city': 'Foo City', 'first_name': 'Foo', 'last_name': 'Bar', 'display_name': 'Foo Bar', 'url': 'http://www.behance.net/foobar', 'country': 'Fooland', 'company': '', 'created_on': 1355152329, 'state': '', 'fields': [ 'Programming', 'Web Design', 'Web Development' ], 'images': { '32': 'https://www.behance.net/assets/img/profile/' 'no-image-32.jpg', '50': 'https://www.behance.net/assets/img/profile/' 'no-image-50.jpg', '115': 'https://www.behance.net/assets/img/profile/' 'no-image-138.jpg', '129': 'https://www.behance.net/assets/img/profile/' 'no-image-138.jpg', '138': 'https://www.behance.net/assets/img/profile/' 'no-image-138.jpg', '78': 'https://www.behance.net/assets/img/profile/' 'no-image-78.jpg' }, 'id': 1010101, 'occupation': 'Software Developer' } }) expected_username = 'foobar' def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_instagram.py0000644000175500017550000000337312754357263024666 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class InstagramOAuth2Test(OAuth2Test): backend_path = 'social.backends.instagram.InstagramOAuth2' user_data_url = 'https://api.instagram.com/v1/users/self' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer', 'meta': { 'code': 200 }, 'data': { 'username': 'foobar', 'bio': '', 'website': '', 'profile_picture': 'http://images.instagram.com/profiles/' 'anonymousUser.jpg', 'full_name': '', 'counts': { 'media': 0, 'followed_by': 2, 'follows': 0 }, 'id': '101010101' }, 'user': { 'username': 'foobar', 'bio': '', 'website': '', 'profile_picture': 'http://images.instagram.com/profiles/' 'anonymousUser.jpg', 'full_name': '', 'id': '101010101' } }) user_data_body = json.dumps({ 'meta': { 'code': 200 }, 'data': { 'username': 'foobar', 'bio': '', 'website': '', 'profile_picture': 'http://images.instagram.com/profiles/' 'anonymousUser.jpg', 'full_name': '', 'counts': { 'media': 0, 'followed_by': 2, 'follows': 0 }, 'id': '101010101' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_live.py0000644000175500017550000000167112754357263023637 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class LiveOAuth2Test(OAuth2Test): backend_path = 'social.backends.live.LiveOAuth2' user_data_url = 'https://apis.live.net/v5.0/me' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'first_name': 'Foo', 'last_name': 'Bar', 'name': 'Foo Bar', 'locale': 'en_US', 'gender': None, 'emails': { 'personal': None, 'account': 'foobar@live.com', 'business': None, 'preferred': 'foobar@live.com' }, 'link': 'https://profile.live.com/', 'updated_time': '2013-03-17T05:51:30+0000', 'id': '1010101010101010' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_dailymotion.py0000644000175500017550000000111612754357263025222 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class DailymotionOAuth2Test(OAuth2Test): backend_path = 'social.backends.dailymotion.DailymotionOAuth2' user_data_url = 'https://api.dailymotion.com/me/' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'id': 'foobar', 'screenname': 'foobar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_mapmyfitness.py0000644000175500017550000001145312754357263025416 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class MapMyFitnessOAuth2Test(OAuth2Test): backend_path = 'social.backends.mapmyfitness.MapMyFitnessOAuth2' user_data_url = 'https://oauth2-api.mapmyapi.com/v7.0/user/self/' expected_username = 'FredFlinstone' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'Bearer', 'expires_in': 4000000, 'refresh_token': 'bambaz', 'scope': 'read' }) user_data_body = json.dumps({ 'last_name': 'Flinstone', 'weight': 91.17206637, 'communication': { 'promotions': True, 'newsletter': True, 'system_messages': True }, 'height': 1.778, 'token_type': 'Bearer', 'id': 112233, 'date_joined': '2011-08-26T06:06:19+00:00', 'first_name': 'Fred', 'display_name': 'Fred Flinstone', 'display_measurement_system': 'imperial', 'expires_in': 4000000, '_links': { 'stats': [ { 'href': '/v7.0/user_stats/112233/?' 'aggregate_by_period=month', 'id': '112233', 'name': 'month' }, { 'href': '/v7.0/user_stats/112233/?' 'aggregate_by_period=year', 'id': '112233', 'name': 'year' }, { 'href': '/v7.0/user_stats/112233/?aggregate_by_period=day', 'id': '112233', 'name': 'day' }, { 'href': '/v7.0/user_stats/112233/?' 'aggregate_by_period=week', 'id': '112233', 'name': 'week' }, { 'href': '/v7.0/user_stats/112233/?' 'aggregate_by_period=lifetime', 'id': '112233', 'name': 'lifetime' } ], 'friendships': [ { 'href': '/v7.0/friendship/?from_user=112233' } ], 'privacy': [ { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'profile' }, { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'workout' }, { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'activity_feed' }, { 'href': '/v7.0/privacy_option/1/', 'id': '1', 'name': 'food_log' }, { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'email_search' }, { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'route' } ], 'image': [ { 'href': '/v7.0/user_profile_photo/112233/', 'id': '112233', 'name': 'user_profile_photo' } ], 'documentation': [ { 'href': 'https://www.mapmyapi.com/docs/User' } ], 'workouts': [ { 'href': '/v7.0/workout/?user=112233&' 'order_by=-start_datetime' } ], 'deactivation': [ { 'href': '/v7.0/user_deactivation/' } ], 'self': [ { 'href': '/v7.0/user/112233/', 'id': '112233' } ] }, 'location': { 'country': 'US', 'region': 'NC', 'locality': 'Bedrock', 'address': '150 Dinosaur Ln' }, 'last_login': '2014-02-23T22:36:52+00:00', 'email': 'fredflinstone@gmail.com', 'username': 'FredFlinstone', 'sharing': { 'twitter': False, 'facebook': False }, 'scope': 'read', 'refresh_token': 'bambaz', 'last_initial': 'S.', 'access_token': 'foobar', 'gender': 'M', 'time_zone': 'America/Denver', 'birthdate': '1983-04-15' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_fitbit.py0000644000175500017550000000302512754357263024154 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class FitbitOAuth1Test(OAuth1Test): backend_path = 'social.backends.fitbit.FitbitOAuth1' expected_username = 'foobar' access_token_body = urlencode({ 'oauth_token_secret': 'a-secret', 'encoded_user_id': '101010', 'oauth_token': 'foobar' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_url = 'https://api.fitbit.com/1/user/-/profile.json' user_data_body = json.dumps({ 'user': { 'weightUnit': 'en_US', 'strideLengthWalking': 0, 'displayName': 'foobar', 'weight': 62.6, 'foodsLocale': 'en_US', 'heightUnit': 'en_US', 'locale': 'en_US', 'gender': 'NA', 'memberSince': '2011-12-26', 'offsetFromUTCMillis': -25200000, 'height': 0, 'timezone': 'America/Los_Angeles', 'dateOfBirth': '', 'encodedId': '101010', 'avatar': 'http://www.fitbit.com/images/profile/' 'defaultProfile_100_male.gif', 'waterUnit': 'en_US', 'distanceUnit': 'en_US', 'glucoseUnit': 'en_US', 'strideLengthRunning': 0 } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_strava.py0000644000175500017550000000353212754357263024176 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class StravaOAuthTest(OAuth2Test): backend_path = 'social.backends.strava.StravaOAuth' user_data_url = 'https://www.strava.com/api/v3/athlete' expected_username = '227615' access_token_body = json.dumps({ "access_token": "83ebeabdec09f6670863766f792ead24d61fe3f9", "athlete": { "id": 227615, "resource_state": 3, "firstname": "John", "lastname": "Applestrava", "profile_medium": "http://pics.com/227615/medium.jpg", "profile": "http://pics.com/227615/large.jpg", "city": "San Francisco", "state": "California", "country": "United States", "sex": "M", "friend": "null", "follower": "null", "premium": "true", "created_at": "2008-01-01T17:44:00Z", "updated_at": "2013-09-04T20:00:50Z", "follower_count": 273, "friend_count": 19, "mutual_friend_count": 0, "date_preference": "%m/%d/%Y", "measurement_preference": "feet", "email": "john@applestrava.com", "clubs": [], "bikes": [], "shoes": [] } }) user_data_body = json.dumps({ "id": 227615, "resource_state": 2, "firstname": "John", "lastname": "Applestrava", "profile_medium": "http://pics.com/227615/medium.jpg", "profile": "http://pics.com/227615/large.jpg", "city": "San Francisco", "state": "CA", "country": "United States", "sex": "M", "friend": "null", "follower": "accepted", "premium": "true", "created_at": "2011-03-19T21:59:57Z", "updated_at": "2013-09-05T16:46:54Z", "approve_followers": "false" }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_yandex.py0000644000175500017550000000131512754357263024163 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class YandexOAuth2Test(OAuth2Test): backend_path = 'social.backends.yandex.YandexOAuth2' user_data_url = 'https://login.yandex.ru/info' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'display_name': 'foobar', 'real_name': 'Foo Bar', 'sex': None, 'id': '101010101', 'default_email': 'foobar@yandex.com', 'emails': ['foobar@yandex.com'] }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_arcgis.py0000644000175500017550000000156612754357263024153 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class ArcGISOAuth2Test(OAuth2Test): user_data_url = 'https://www.arcgis.com/sharing/rest/community/self' backend_path = 'social.backends.arcgis.ArcGISOAuth2' expected_username = 'gis@rocks.com' user_data_body = json.dumps({ 'first_name': 'Gis', 'last_name': 'Rocks', 'email': 'gis@rocks.com', 'fullName': 'Gis Rocks', 'username': 'gis@rocks.com' }) access_token_body = json.dumps({ 'access_token': 'CM-gcB85taGhRmoI7l3PSGaXUNsaLkTg-dHH7XtA9Dnlin' \ 'PYKBBrIvFzhd1JtDhh7hEwSv_6eLLcLtUqe3gD6i1yaYYF' \ 'pUQJwy8KEujke5AE87tP9XIoMtp4_l320pUL', 'expires_in': 86400 }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_qiita.py0000644000175500017550000000107612754357263024006 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class QiitaOAuth2Test(OAuth2Test): backend_path = 'social.backends.qiita.QiitaOAuth2' user_data_url = 'https://qiita.com/api/v2/authenticated_user' expected_username = 'foobar' access_token_body = json.dumps({ 'token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'id': 'foobar', 'name': 'Foo Bar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_box.py0000644000175500017550000000436612754357263023474 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class BoxOAuth2Test(OAuth2Test): backend_path = 'social.backends.box.BoxOAuth2' user_data_url = 'https://api.box.com/2.0/users/me' expected_username = 'sean+awesome@box.com' access_token_body = json.dumps({ 'access_token': 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl', 'expires_in': 3600, 'restricted_to': [], 'token_type': 'bearer', 'refresh_token': 'J7rxTiWOHMoSC1isKZKBZWizoRXjkQzig5C6jFgCVJ9bU' 'nsUfGMinKBDLZWP9BgR' }) user_data_body = json.dumps({ 'type': 'user', 'id': '181216415', 'name': 'sean rose', 'login': 'sean+awesome@box.com', 'created_at': '2012-05-03T21:39:11-07:00', 'modified_at': '2012-11-14T11:21:32-08:00', 'role': 'admin', 'language': 'en', 'space_amount': 11345156112, 'space_used': 1237009912, 'max_upload_size': 2147483648, 'tracking_codes': [], 'can_see_managed_users': True, 'is_sync_enabled': True, 'status': 'active', 'job_title': '', 'phone': '6509241374', 'address': '', 'avatar_url': 'https://www.box.com/api/avatar/large/181216415', 'is_exempt_from_device_limits': False, 'is_exempt_from_login_verification': False, 'enterprise': { 'type': 'enterprise', 'id': '17077211', 'name': 'seanrose enterprise' } }) refresh_token_body = json.dumps({ 'access_token': 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl', 'expires_in': 3600, 'restricted_to': [], 'token_type': 'bearer', 'refresh_token': 'J7rxTiWOHMoSC1isKZKBZWizoRXjkQzig5C6jFgCVJ9b' 'UnsUfGMinKBDLZWP9BgR' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def refresh_token_arguments(self): uri = self.strategy.build_absolute_uri('/complete/box/') return {'redirect_uri': uri} def test_refresh_token(self): user, social = self.do_refresh_token() self.assertEqual(social.extra_data['access_token'], 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl') python-social-auth-0.2.21/social/tests/backends/test_deezer.py0000644000175500017550000000231712754357263024154 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class DeezerOAuth2Test(OAuth2Test): backend_path = 'social.backends.deezer.DeezerOAuth2' user_data_url = 'http://api.deezer.com/user/me' expected_username = 'foobar' access_token_body = 'access_token=foobar&expires=0' user_data_body = json.dumps({ 'id': '1', 'name': 'foobar', 'lastname': '', 'firstname': '', 'status': 0, 'birthday': '1970-01-01', 'inscription_date': '2015-01-01', 'gender': 'M', 'link': 'https://www.deezer.com/profile/1', 'picture': 'https://api.deezer.com/user/1/image', 'picture_small': 'https://cdns-images.dzcdn.net/images/user//56x56-000000-80-0-0.jpg', 'picture_medium': 'https://cdns-images.dzcdn.net/images/user//250x250-000000-80-0-0.jpg', 'picture_big': 'https://cdns-images.dzcdn.net/images/user//500x500-000000-80-0-0.jpg', 'country': 'FR', 'lang': 'FR', 'is_kid': False, 'tracklist': 'https://api.deezer.com/user/1/flow', 'type': 'user' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_facebook.py0000644000175500017550000000356312754357263024453 0ustar debacledebacleimport json from social.exceptions import AuthUnknownError, AuthCanceled from social.tests.backends.oauth import OAuth2Test class FacebookOAuth2Test(OAuth2Test): backend_path = 'social.backends.facebook.FacebookOAuth2' user_data_url = 'https://graph.facebook.com/v2.7/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'username': 'foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'verified': True, 'name': 'Foo Bar', 'gender': 'male', 'updated_time': '2013-02-13T14:59:42+0000', 'link': 'http://www.facebook.com/foobar', 'id': '110011001100010' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class FacebookOAuth2WrongUserDataTest(FacebookOAuth2Test): user_data_body = 'null' def test_login(self): with self.assertRaises(AuthUnknownError): self.do_login() def test_partial_pipeline(self): with self.assertRaises(AuthUnknownError): self.do_partial_pipeline() class FacebookOAuth2AuthCancelTest(FacebookOAuth2Test): access_token_status = 400 access_token_body = json.dumps({ 'error': { 'message': "redirect_uri isn't an absolute URI. Check RFC 3986.", 'code': 191, 'type': 'OAuthException', 'fbtrace_id': '123Abc' } }) def test_login(self): with self.assertRaises(AuthCanceled) as cm: self.do_login() self.assertIn('error', cm.exception.response.json()) def test_partial_pipeline(self): with self.assertRaises(AuthCanceled) as cm: self.do_partial_pipeline() self.assertIn('error', cm.exception.response.json()) python-social-auth-0.2.21/social/tests/backends/__init__.py0000644000175500017550000000000012754357263023361 0ustar debacledebaclepython-social-auth-0.2.21/social/tests/backends/test_sketchfab.py0000644000175500017550000000124212754357263024624 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class SketchfabOAuth2Test(OAuth2Test): backend_path = 'social.backends.sketchfab.SketchfabOAuth2' user_data_url = 'https://sketchfab.com/v2/users/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'uid': '42', 'email': 'foo@bar.com', 'displayName': 'foo bar', 'username': 'foobar', 'apiToken': 'XXX' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_naver.py0000644000175500017550000000240612754357263024010 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class NaverOAuth2Test(OAuth2Test): backend_path = 'social.backends.naver.NaverOAuth2' user_data_url = 'https://openapi.naver.com/v1/nid/getUserProfile.xml' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer', }) user_data_content_type = 'text/xml' user_data_body = \ '' \ '' \ '' \ '00' \ 'success' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ 'M' \ '' \ '' \ '' \ '' \ '' def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_tumblr.py0000644000175500017550000000331712754357263024204 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class TumblrOAuth1Test(OAuth1Test): backend_path = 'social.backends.tumblr.TumblrOAuth' user_data_url = 'http://api.tumblr.com/v2/user/info' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'meta': { 'status': 200, 'msg': 'OK' }, 'response': { 'user': { 'following': 1, 'blogs': [{ 'updated': 0, 'description': '', 'drafts': 0, 'title': 'Untitled', 'url': 'http://foobar.tumblr.com/', 'messages': 0, 'tweet': 'N', 'share_likes': True, 'posts': 0, 'primary': True, 'queue': 0, 'admin': True, 'followers': 0, 'ask': False, 'facebook': 'N', 'type': 'public', 'facebook_opengraph_enabled': 'N', 'name': 'foobar' }], 'default_post_format': 'html', 'name': 'foobar', 'likes': 0 } } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_dummy.py0000644000175500017550000000753412754357263024037 0ustar debacledebacleimport json import datetime import time from httpretty import HTTPretty from social.actions import do_disconnect from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthForbidden from social.tests.models import User from social.tests.backends.oauth import OAuth2Test class DummyOAuth2(BaseOAuth2): name = 'dummy' AUTHORIZATION_URL = 'http://dummy.com/oauth/authorize' ACCESS_TOKEN_URL = 'http://dummy.com/oauth/access_token' REVOKE_TOKEN_URL = 'https://dummy.com/oauth/revoke' REVOKE_TOKEN_METHOD = 'GET' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires'), ('empty', 'empty', True), 'url' ] def get_user_details(self, response): """Return user details from Github account""" return {'username': response.get('username'), 'email': response.get('email', ''), 'first_name': response.get('first_name', ''), 'last_name': response.get('last_name', '')} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('http://dummy.com/user', params={ 'access_token': access_token }) class DummyOAuth2Test(OAuth2Test): backend_path = 'social.tests.backends.test_dummy.DummyOAuth2' user_data_url = 'http://dummy.com/user' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'id': 1, 'username': 'foobar', 'url': 'http://dummy.com/user/foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'email': 'foo@bar.com' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_tokens(self): user = self.do_login() self.assertEqual(user.social[0].access_token, 'foobar') def test_revoke_token(self): self.strategy.set_settings({ 'SOCIAL_AUTH_REVOKE_TOKENS_ON_DISCONNECT': True }) self.do_login() user = User.get(self.expected_username) user.password = 'password' HTTPretty.register_uri(self._method(self.backend.REVOKE_TOKEN_METHOD), self.backend.REVOKE_TOKEN_URL, status=200) do_disconnect(self.backend, user) class WhitelistEmailsTest(DummyOAuth2Test): def test_valid_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_WHITELISTED_EMAILS': ['foo@bar.com'] }) self.do_login() def test_invalid_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_WHITELISTED_EMAILS': ['foo2@bar.com'] }) with self.assertRaises(AuthForbidden): self.do_login() class WhitelistDomainsTest(DummyOAuth2Test): def test_valid_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_WHITELISTED_DOMAINS': ['bar.com'] }) self.do_login() def test_invalid_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_WHITELISTED_EMAILS': ['bar2.com'] }) with self.assertRaises(AuthForbidden): self.do_login() DELTA = datetime.timedelta(days=1) class ExpirationTimeTest(DummyOAuth2Test): user_data_body = json.dumps({ 'id': 1, 'username': 'foobar', 'url': 'http://dummy.com/user/foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'email': 'foo@bar.com', 'expires': time.mktime((datetime.datetime.utcnow() + DELTA).timetuple()) }) def test_expires_time(self): user = self.do_login() social = user.social[0] expiration = social.expiration_datetime() self.assertEqual(expiration <= DELTA, True) python-social-auth-0.2.21/social/tests/backends/test_stocktwits.py0000644000175500017550000000317212754357263025114 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class StocktwitsOAuth2Test(OAuth2Test): backend_path = 'social.backends.stocktwits.StocktwitsOAuth2' user_data_url = 'https://api.stocktwits.com/api/2/account/verify.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'response': { 'status': 200 }, 'user': { 'username': 'foobar', 'name': 'Foo Bar', 'classification': [], 'avatar_url': 'http://avatars.stocktwits.net/images/' 'default_avatar_thumb.jpg', 'avatar_url_ssl': 'https://s3.amazonaws.com/st-avatars/images/' 'default_avatar_thumb.jpg', 'id': 101010, 'identity': 'User' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class StocktwitsOAuth2UsernameAlternativeTest(StocktwitsOAuth2Test): user_data_body = json.dumps({ 'response': { 'status': 200 }, 'user': { 'username': 'foobar', 'name': 'Foobar', 'classification': [], 'avatar_url': 'http://avatars.stocktwits.net/images/' 'default_avatar_thumb.jpg', 'avatar_url_ssl': 'https://s3.amazonaws.com/st-avatars/images/' 'default_avatar_thumb.jpg', 'id': 101010, 'identity': 'User' } }) python-social-auth-0.2.21/social/tests/backends/test_clef.py0000644000175500017550000000120212754357263023577 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class ClefOAuth2Test(OAuth2Test): backend_path = 'social.backends.clef.ClefOAuth2' user_data_url = 'https://clef.io/api/v1/info' expected_username = 'test' access_token_body = json.dumps({ 'access_token': 'foobar' }) user_data_body = json.dumps({ 'info': { 'id': '123456789', 'first_name': 'Test', 'last_name': 'User', 'email': 'test@example.com' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_dropbox.py0000644000175500017550000000202212754357263024344 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class DropboxOAuth1Test(OAuth1Test): backend_path = 'social.backends.dropbox.DropboxOAuth' user_data_url = 'https://api.dropbox.com/1/account/info' expected_username = '10101010' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'referral_link': 'https://www.dropbox.com/referrals/foobar', 'display_name': 'Foo Bar', 'uid': 10101010, 'country': 'US', 'quota_info': { 'shared': 138573, 'quota': 2952790016, 'normal': 157327 }, 'email': 'foo@bar.com' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_livejournal.py0000644000175500017550000000717112754357263025233 0ustar debacledebacleimport datetime from httpretty import HTTPretty from social.p3 import urlencode from social.exceptions import AuthMissingParameter from social.tests.backends.open_id import OpenIdTest JANRAIN_NONCE = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') class LiveJournalOpenIdTest(OpenIdTest): backend_path = 'social.backends.livejournal.LiveJournalOpenId' expected_username = 'foobar' discovery_body = ''.join([ '', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://www.livejournal.com/openid/server.bml', 'http://foobar.livejournal.com/', '', '', '' ]) server_response = urlencode({ 'janrain_nonce': JANRAIN_NONCE, 'openid.mode': 'id_res', 'openid.claimed_id': 'http://foobar.livejournal.com/', 'openid.identity': 'http://foobar.livejournal.com/', 'openid.op_endpoint': 'http://www.livejournal.com/openid/server.bml', 'openid.return_to': 'http://myapp.com/complete/livejournal/?' 'janrain_nonce=' + JANRAIN_NONCE, 'openid.response_nonce': JANRAIN_NONCE + 'wGp2rj', 'openid.assoc_handle': '1364932966:ZTiur8sem3r2jzZougMZ:4d1cc3b44e', 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.signed': 'mode,claimed_id,identity,op_endpoint,return_to,' 'response_nonce,assoc_handle', 'openid.sig': 'Z8MOozVPTOBhHG5ZS1NeGofxs1Q=', }) server_bml_body = '\n'.join([ 'assoc_handle:1364935340:ZhruPQ7DJ9eGgUkeUA9A:27f8c32464', 'assoc_type:HMAC-SHA1', 'dh_server_public:WzsRyLomvAV3vwvGUrfzXDgfqnTF+m1l3JWb55fyHO7visPT4tmQ' 'iTjqFFnSVAtAOvQzoViMiZQisxNwnqSK4lYexoez1z6pP5ry3pqxJAEYj60vFGvRztict' 'Eo0brjhmO1SNfjK1ppjOymdykqLpZeaL5fsuLtMCwTnR/JQZVA=', 'enc_mac_key:LiOEVlLJSVUqfNvb5zPd76nEfvc=', 'expires_in:1207060', 'ns:http://specs.openid.net/auth/2.0', 'session_type:DH-SHA1', '' ]) def openid_url(self): return super(LiveJournalOpenIdTest, self).openid_url() + '/data/yadis' def post_start(self): self.strategy.remove_from_request_data('openid_lj_user') def _setup_handlers(self): HTTPretty.register_uri( HTTPretty.POST, 'http://www.livejournal.com/openid/server.bml', headers={'Accept-Encoding': 'identity', 'Content-Type': 'application/x-www-form-urlencoded'}, status=200, body=self.server_bml_body ) HTTPretty.register_uri( HTTPretty.GET, 'http://foobar.livejournal.com/', headers={ 'Accept-Encoding': 'identity', 'Accept': 'text/html; q=0.3,' 'application/xhtml+xml; q=0.5,' 'application/xrds+xml' }, status=200, body=self.discovery_body ) def test_login(self): self.strategy.set_request_data({'openid_lj_user': 'foobar'}, self.backend) self._setup_handlers() self.do_login() def test_partial_pipeline(self): self.strategy.set_request_data({'openid_lj_user': 'foobar'}, self.backend) self._setup_handlers() self.do_partial_pipeline() def test_failed_login(self): self._setup_handlers() with self.assertRaises(AuthMissingParameter): self.do_login() python-social-auth-0.2.21/social/tests/backends/test_dribbble.py0000644000175500017550000000114112754357263024435 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class DribbbleOAuth2Test(OAuth2Test): backend_path = 'social.backends.dribbble.DribbbleOAuth2' user_data_url = 'https://api.dribbble.com/v1/user' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'id': 'foobar', 'username': 'foobar', 'name': 'Foo Bar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_drip.py0000644000175500017550000000126512754357263023635 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class DripOAuthTest(OAuth2Test): backend_path = 'social.backends.drip.DripOAuth' user_data_url = 'https://api.getdrip.com/v2/user' expected_username = 'other@example.com' access_token_body = json.dumps({ 'access_token': '822bbf7cd12243df', 'token_type': 'bearer', 'scope': 'public' }) user_data_body = json.dumps({ 'users': [ { 'email': 'other@example.com', 'name': None } ] }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_utils.py0000644000175500017550000000336012754357263024035 0ustar debacledebacleimport unittest2 as unittest from social.tests.models import TestStorage from social.tests.strategy import TestStrategy from social.backends.utils import load_backends, get_backend from social.backends.github import GithubOAuth2 from social.exceptions import MissingBackend class BaseBackendUtilsTest(unittest.TestCase): def setUp(self): self.strategy = TestStrategy(storage=TestStorage) def tearDown(self): self.strategy = None class LoadBackendsTest(BaseBackendUtilsTest): def test_load_backends(self): loaded_backends = load_backends(( 'social.backends.github.GithubOAuth2', 'social.backends.facebook.FacebookOAuth2', 'social.backends.flickr.FlickrOAuth' ), force_load=True) keys = list(loaded_backends.keys()) keys.sort() self.assertEqual(keys, ['facebook', 'flickr', 'github']) backends = () loaded_backends = load_backends(backends, force_load=True) self.assertEqual(len(list(loaded_backends.keys())), 0) class GetBackendTest(BaseBackendUtilsTest): def test_get_backend(self): backend = get_backend(( 'social.backends.github.GithubOAuth2', 'social.backends.facebook.FacebookOAuth2', 'social.backends.flickr.FlickrOAuth' ), 'github') self.assertEqual(backend, GithubOAuth2) def test_get_missing_backend(self): with self.assertRaisesRegexp(MissingBackend, 'Missing backend "foobar" entry'): get_backend(('social.backends.github.GithubOAuth2', 'social.backends.facebook.FacebookOAuth2', 'social.backends.flickr.FlickrOAuth'), 'foobar') python-social-auth-0.2.21/social/tests/backends/test_foursquare.py0000644000175500017550000001001412754357263025063 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class FoursquareOAuth2Test(OAuth2Test): backend_path = 'social.backends.foursquare.FoursquareOAuth2' user_data_url = 'https://api.foursquare.com/v2/users/self' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'notifications': [{ 'item': { 'unreadCount': 0 }, 'type': 'notificationTray' }], 'meta': { 'errorType': 'deprecated', 'code': 200, 'errorDetail': 'Please provide an API version to avoid future ' 'errors.See http://bit.ly/vywCav' }, 'response': { 'user': { 'photo': 'https://is0.4sqi.net/userpix_thumbs/' 'BYKIT01VN4T4BISN.jpg', 'pings': False, 'homeCity': 'Foo, Bar', 'id': '1010101', 'badges': { 'count': 0, 'items': [] }, 'friends': { 'count': 1, 'groups': [{ 'count': 0, 'items': [], 'type': 'friends', 'name': 'Mutual friends' }, { 'count': 1, 'items': [{ 'bio': '', 'gender': 'male', 'firstName': 'Baz', 'relationship': 'friend', 'photo': 'https://is0.4sqi.net/userpix_thumbs/' 'BYKIT01VN4T4BISN.jpg', 'lists': { 'groups': [{ 'count': 1, 'items': [], 'type': 'created' }] }, 'homeCity': 'Baz, Qux', 'lastName': 'Qux', 'tips': { 'count': 0 }, 'id': '10101010' }], 'type': 'others', 'name': 'Other friends' }] }, 'referralId': 'u-1010101', 'tips': { 'count': 0 }, 'type': 'user', 'todos': { 'count': 0 }, 'bio': '', 'relationship': 'self', 'lists': { 'groups': [{ 'count': 1, 'items': [], 'type': 'created' }] }, 'photos': { 'count': 0, 'items': [] }, 'checkinPings': 'off', 'scores': { 'max': 0, 'checkinsCount': 0, 'goal': 50, 'recent': 0 }, 'checkins': { 'count': 0 }, 'firstName': 'Foo', 'gender': 'male', 'contact': { 'email': 'foo@bar.com' }, 'lastName': 'Bar', 'following': { 'count': 0 }, 'requests': { 'count': 0 }, 'mayorships': { 'count': 0, 'items': [] } } } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_saml.py0000644000175500017550000001132012754357263023624 0ustar debacledebacleimport re import json import sys import unittest2 import requests from os import path from mock import patch from httpretty import HTTPretty try: from onelogin.saml2.utils import OneLogin_Saml2_Utils except ImportError: # Only available for python 2.7 at the moment, so don't worry if this fails pass from social.tests.backends.base import BaseBackendTest from social.exceptions import AuthMissingParameter from social.p3 import urlparse, urlunparse, urlencode, parse_qs DATA_DIR = path.join(path.dirname(__file__), 'data') @unittest2.skipUnless( sys.version_info[:2] == (2, 7), 'python-saml currently depends on 2.7; 3+ support coming soon') @unittest2.skipIf('__pypy__' in sys.builtin_module_names, 'dm.xmlsec not compatible with pypy') class SAMLTest(BaseBackendTest): backend_path = 'social.backends.saml.SAMLAuth' expected_username = 'myself' def extra_settings(self): name = path.join(DATA_DIR, 'saml_config.json') with open(name, 'r') as config_file: config_str = config_file.read() return json.loads(config_str) def setUp(self): """Patch the time so that we can replay canned request/response pairs""" super(SAMLTest, self).setUp() @staticmethod def fixed_time(): return OneLogin_Saml2_Utils.parse_SAML_to_time( '2015-05-09T03:57:22Z' ) now_patch = patch.object(OneLogin_Saml2_Utils, 'now', fixed_time) now_patch.start() self.addCleanup(now_patch.stop) def install_http_intercepts(self, start_url, return_url): # When we request start_url # (https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO...) # we will eventually get a redirect back, with SAML assertion # data in the query string. A pre-recorded correct response # is kept in this .txt file: name = path.join(DATA_DIR, 'saml_response.txt') with open(name, 'r') as response_file: response_url = response_file.read() HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=response_url) HTTPretty.register_uri(HTTPretty.GET, return_url, status=200, body='foobar') def do_start(self): start_url = self.backend.start().url # Modify the start URL to make the SAML request consistent # from test to test: start_url = self.modify_start_url(start_url) # If the SAML Identity Provider recognizes the user, we will # be redirected back to: return_url = self.backend.redirect_uri self.install_http_intercepts(start_url, return_url) response = requests.get(start_url) self.assertTrue(response.url.startswith(return_url)) self.assertEqual(response.text, 'foobar') query_values = dict((k, v[0]) for k, v in parse_qs(urlparse(response.url).query).items()) self.assertNotIn(' ', query_values['SAMLResponse']) self.strategy.set_request_data(query_values, self.backend) return self.backend.complete() def test_metadata_generation(self): """Test that we can generate the metadata without error""" xml, errors = self.backend.generate_metadata_xml() self.assertEqual(len(errors), 0) self.assertEqual(xml[0], '<') def test_login(self): """Test that we can authenticate with a SAML IdP (TestShib)""" # pretend we've started with a URL like /login/saml/?idp=testshib: self.strategy.set_request_data({'idp': 'testshib'}, self.backend) self.do_login() def test_login_no_idp(self): """Logging in without an idp param should raise AuthMissingParameter""" with self.assertRaises(AuthMissingParameter): self.do_start() def modify_start_url(self, start_url): """ Given a SAML redirect URL, parse it and change the ID to a consistent value, so the request is always identical. """ # Parse the SAML Request URL to get the XML being sent to TestShib url_parts = urlparse(start_url) query = dict((k, v[0]) for (k, v) in parse_qs(url_parts.query).iteritems()) xml = OneLogin_Saml2_Utils.decode_base64_and_inflate( query['SAMLRequest'] ) # Modify the XML: xml, changed = re.subn(r'ID="[^"]+"', 'ID="TEST_ID"', xml) self.assertEqual(changed, 1) # Update the URL to use the modified query string: query['SAMLRequest'] = OneLogin_Saml2_Utils.deflate_and_base64_encode( xml ) url_parts = list(url_parts) url_parts[4] = urlencode(query) return urlunparse(url_parts) python-social-auth-0.2.21/social/tests/backends/test_nationbuilder.py0000644000175500017550000002045112754357263025534 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class NationBuilderOAuth2Test(OAuth2Test): backend_path = 'social.backends.nationbuilder.NationBuilderOAuth2' user_data_url = 'https://foobar.nationbuilder.com/api/v1/people/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer', 'created_at': 1422937981, 'expires_in': 2592000 }) user_data_body = json.dumps({ 'person': { 'twitter_followers_count': None, 'last_name': 'Bar', 'rule_violations_count': 0, 'linkedin_id': None, 'recruiter_id': None, 'membership_expires_at': None, 'donations_raised_count': 0, 'last_contacted_at': None, 'prefix': None, 'profile_content_html': None, 'email4': None, 'email2': None, 'availability': None, 'occupation': None, 'user_submitted_address': None, 'could_vote_status': None, 'state_upper_district': None, 'salesforce_id': None, 'van_id': None, 'phone_time': None, 'profile_content': None, 'auto_import_id': None, 'parent_id': None, 'email4_is_bad': False, 'twitter_updated_at': None, 'email3_is_bad': False, 'bio': None, 'party_member': None, 'unsubscribed_at': None, 'fax_number': None, 'last_contacted_by': None, 'active_customer_expires_at': None, 'federal_donotcall': False, 'warnings_count': 0, 'first_supporter_at': '2015-02-02T19:30:28-08:00', 'previous_party': None, 'donations_raised_amount_this_cycle_in_cents': 0, 'call_status_name': None, 'marital_status': None, 'facebook_updated_at': None, 'donations_count': 0, 'note_updated_at': None, 'closed_invoices_count': None, 'profile_headline': None, 'fire_district': None, 'mobile_normalized': None, 'import_id': None, 'last_call_id': None, 'donations_raised_amount_in_cents': 0, 'facebook_address': None, 'is_profile_private': False, 'last_rule_violation_at': None, 'sex': None, 'full_name': 'Foo Bar', 'last_donated_at': None, 'donations_pledged_amount_in_cents': 0, 'primary_email_id': 1, 'media_market_name': None, 'capital_amount_in_cents': 500, 'datatrust_id': None, 'precinct_code': None, 'email3': None, 'religion': None, 'first_prospect_at': None, 'judicial_district': None, 'donations_count_this_cycle': 0, 'work_address': None, 'is_twitter_follower': False, 'email1': 'foobar@gmail.com', 'email': 'foobar@gmail.com', 'contact_status_name': None, 'mobile_opt_in': True, 'twitter_description': None, 'parent': None, 'tags': [], 'first_volunteer_at': None, 'inferred_support_level': None, 'banned_at': None, 'first_invoice_at': None, 'donations_raised_count_this_cycle': 0, 'is_donor': False, 'twitter_location': None, 'email1_is_bad': False, 'legal_name': None, 'language': None, 'registered_at': None, 'call_status_id': None, 'last_invoice_at': None, 'school_sub_district': None, 'village_district': None, 'twitter_name': None, 'membership_started_at': None, 'subnations': [], 'meetup_address': None, 'author_id': None, 'registered_address': None, 'external_id': None, 'twitter_login': None, 'inferred_party': None, 'spent_capital_amount_in_cents': 0, 'suffix': None, 'mailing_address': None, 'is_leaderboardable': True, 'twitter_website': None, 'nbec_guid': None, 'city_district': None, 'church': None, 'is_profile_searchable': True, 'employer': None, 'is_fundraiser': False, 'email_opt_in': True, 'recruits_count': 0, 'email2_is_bad': False, 'county_district': None, 'recruiter': None, 'twitter_friends_count': None, 'facebook_username': None, 'active_customer_started_at': None, 'pf_strat_id': None, 'locale': None, 'twitter_address': None, 'is_supporter': True, 'do_not_call': False, 'profile_image_url_ssl': 'https://d3n8a8pro7vhmx.cloudfront.net' '/assets/icons/buddy.png', 'invoices_amount_in_cents': None, 'username': None, 'donations_amount_in_cents': 0, 'is_volunteer': False, 'civicrm_id': None, 'supranational_district': None, 'precinct_name': None, 'invoice_payments_amount_in_cents': None, 'work_phone_number': None, 'phone': '213.394.4623', 'received_capital_amount_in_cents': 500, 'primary_address': None, 'is_possible_duplicate': False, 'invoice_payments_referred_amount_in_cents': None, 'donations_amount_this_cycle_in_cents': 0, 'priority_level': None, 'first_fundraised_at': None, 'phone_normalized': '2133944623', 'rnc_regid': None, 'twitter_id': None, 'birthdate': None, 'mobile': None, 'federal_district': None, 'donations_to_raise_amount_in_cents': 0, 'support_probability_score': None, 'invoices_count': None, 'nbec_precinct_code': None, 'website': None, 'closed_invoices_amount_in_cents': None, 'home_address': None, 'school_district': None, 'support_level': None, 'demo': None, 'children_count': 0, 'updated_at': '2015-02-02T19:30:28-08:00', 'membership_level_name': None, 'billing_address': None, 'is_ignore_donation_limits': False, 'signup_type': 0, 'precinct_id': None, 'rnc_id': None, 'id': 2, 'ethnicity': None, 'is_survey_question_private': False, 'middle_name': None, 'author': None, 'last_fundraised_at': None, 'state_file_id': None, 'note': None, 'submitted_address': None, 'support_level_changed_at': None, 'party': None, 'contact_status_id': None, 'outstanding_invoices_amount_in_cents': None, 'page_slug': None, 'outstanding_invoices_count': None, 'first_recruited_at': None, 'county_file_id': None, 'first_name': 'Foo', 'facebook_profile_url': None, 'city_sub_district': None, 'has_facebook': False, 'is_deceased': False, 'labour_region': None, 'state_lower_district': None, 'dw_id': None, 'created_at': '2015-02-02T19:30:28-08:00', 'is_prospect': False, 'priority_level_changed_at': None, 'is_mobile_bad': False, 'overdue_invoices_count': None, 'ngp_id': None, 'do_not_contact': False, 'first_donated_at': None, 'turnout_probability_score': None }, 'precinct': None }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_NATIONBUILDER_SLUG': 'foobar' }) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_NATIONBUILDER_SLUG': 'foobar' }) self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_steam.py0000644000175500017550000001244412754357263024011 0ustar debacledebacleimport json import datetime from httpretty import HTTPretty from social.p3 import urlencode from social.exceptions import AuthFailed from social.tests.backends.open_id import OpenIdTest INFO_URL = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' JANRAIN_NONCE = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') class SteamOpenIdTest(OpenIdTest): backend_path = 'social.backends.steam.SteamOpenId' expected_username = 'foobar' discovery_body = ''.join([ '', '', '', '', 'http://specs.openid.net/auth/2.0/server', 'https://steamcommunity.com/openid/login', '', '', '' ]) user_discovery_body = ''.join([ '', '', '', '', 'http://specs.openid.net/auth/2.0/signon ', 'https://steamcommunity.com/openid/login', '', '', '' ]) server_response = urlencode({ 'janrain_nonce': JANRAIN_NONCE, 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.mode': 'id_res', 'openid.op_endpoint': 'https://steamcommunity.com/openid/login', 'openid.claimed_id': 'https://steamcommunity.com/openid/id/123', 'openid.identity': 'https://steamcommunity.com/openid/id/123', 'openid.return_to': 'http://myapp.com/complete/steam/?' 'janrain_nonce=' + JANRAIN_NONCE, 'openid.response_nonce': JANRAIN_NONCE + 'oD4UZ3w9chOAiQXk0AqDipqFYRA=', 'openid.assoc_handle': '1234567890', 'openid.signed': 'signed,op_endpoint,claimed_id,identity,return_to,' 'response_nonce,assoc_handle', 'openid.sig': '1az53vj9SVdiBwhk8%2BFQ68R2plo=', }) player_details = json.dumps({ 'response': { 'players': [{ 'steamid': '123', 'primaryclanid': '1234', 'timecreated': 1360768416, 'personaname': 'foobar', 'personastate': 0, 'communityvisibilitystate': 3, 'profileurl': 'http://steamcommunity.com/profiles/123/', 'avatar': 'http://media.steampowered.com/steamcommunity/' 'public/images/avatars/fe/fef49e7fa7e1997310d7' '05b2a6158ff8dc1cdfeb.jpg', 'avatarfull': 'http://media.steampowered.com/steamcommunity/' 'public/images/avatars/fe/fef49e7fa7e1997310d7' '05b2a6158ff8dc1cdfeb_full.jpg', 'avatarmedium': 'http://media.steampowered.com/steamcommunity/' 'public/images/avatars/fe/fef49e7fa7e1997310d7' '05b2a6158ff8dc1cdfeb_medium.jpg', 'lastlogoff': 1360790014 }] } }) def _login_setup(self, user_url=None): self.strategy.set_settings({ 'SOCIAL_AUTH_STEAM_API_KEY': '123abc' }) HTTPretty.register_uri(HTTPretty.POST, 'https://steamcommunity.com/openid/login', status=200, body=self.server_response) HTTPretty.register_uri( HTTPretty.GET, user_url or 'https://steamcommunity.com/openid/id/123', status=200, body=self.user_discovery_body ) HTTPretty.register_uri(HTTPretty.GET, INFO_URL, status=200, body=self.player_details) def test_login(self): self._login_setup() self.do_login() def test_partial_pipeline(self): self._login_setup() self.do_partial_pipeline() class SteamOpenIdMissingSteamIdTest(SteamOpenIdTest): server_response = urlencode({ 'janrain_nonce': JANRAIN_NONCE, 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.mode': 'id_res', 'openid.op_endpoint': 'https://steamcommunity.com/openid/login', 'openid.claimed_id': 'https://steamcommunity.com/openid/BROKEN', 'openid.identity': 'https://steamcommunity.com/openid/BROKEN', 'openid.return_to': 'http://myapp.com/complete/steam/?' 'janrain_nonce=' + JANRAIN_NONCE, 'openid.response_nonce': JANRAIN_NONCE + 'oD4UZ3w9chOAiQXk0AqDipqFYRA=', 'openid.assoc_handle': '1234567890', 'openid.signed': 'signed,op_endpoint,claimed_id,identity,return_to,' 'response_nonce,assoc_handle', 'openid.sig': '1az53vj9SVdiBwhk8%2BFQ68R2plo=', }) def test_login(self): self._login_setup(user_url='https://steamcommunity.com/openid/BROKEN') with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self._login_setup(user_url='https://steamcommunity.com/openid/BROKEN') with self.assertRaises(AuthFailed): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_mineid.py0000644000175500017550000000111612754357263024137 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class MineIDOAuth2Test(OAuth2Test): backend_path = 'social.backends.mineid.MineIDOAuth2' user_data_url = 'https://www.mineid.org/api/user' expected_username = 'foo@bar.com' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'email': 'foo@bar.com', 'primary_profile': None, }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_wunderlist.py0000644000175500017550000000136512754357263025100 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class WunderlistOAuth2Test(OAuth2Test): backend_path = 'social.backends.wunderlist.WunderlistOAuth2' user_data_url = 'https://a.wunderlist.com/api/v1/user' expected_username = '12345' access_token_body = json.dumps({ 'access_token': 'foobar-token', 'token_type': 'foobar'}) user_data_body = json.dumps({ 'created_at': '2015-01-21T00:56:51.442Z', 'email': 'foo@bar.com', 'id': 12345, 'name': 'foobar', 'revision': 1, 'type': 'user', 'updated_at': '2015-01-21T00:56:51.442Z'}) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_bitbucket.py0000644000175500017550000001312112754357263024645 0ustar debacledebacleimport json from httpretty import HTTPretty from social.p3 import urlencode from social.exceptions import AuthForbidden from social.tests.backends.oauth import OAuth1Test, OAuth2Test class BitbucketOAuthMixin(object): user_data_url = 'https://api.bitbucket.org/2.0/user' expected_username = 'foobar' bb_api_user_emails = 'https://api.bitbucket.org/2.0/user/emails' user_data_body = json.dumps({ u'created_on': u'2012-03-29T18:07:38+00:00', u'display_name': u'Foo Bar', u'links': { u'avatar': {u'href': u'https://bitbucket.org/account/foobar/avatar/32/'}, u'followers': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/followers'}, u'following': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/following'}, u'hooks': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/hooks'}, u'html': {u'href': u'https://bitbucket.org/foobar'}, u'repositories': {u'href': u'https://api.bitbucket.org/2.0/repositories/foobar'}, u'self': {u'href': u'https://api.bitbucket.org/2.0/users/foobar'}}, u'location': u'Fooville, Bar', u'type': u'user', u'username': u'foobar', u'uuid': u'{397621dc-0f78-329f-8d6d-727396248e3f}', u'website': u'http://foobar.com' }) emails_body = json.dumps({ u'page': 1, u'pagelen': 10, u'size': 2, u'values': [ { u'email': u'foo@bar.com', u'is_confirmed': True, u'is_primary': True, u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, u'type': u'email' }, { u'email': u'not@confirme.com', u'is_confirmed': False, u'is_primary': False, u'links': {u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/not@confirmed.com'}}, u'type': u'email' } ] }) class BitbucketOAuth1Test(BitbucketOAuthMixin, OAuth1Test): backend_path = 'social.backends.bitbucket.BitbucketOAuth' request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) def test_login(self): HTTPretty.register_uri(HTTPretty.GET, self.bb_api_user_emails, status=200, body=self.emails_body) self.do_login() def test_partial_pipeline(self): HTTPretty.register_uri(HTTPretty.GET, self.bb_api_user_emails, status=200, body=self.emails_body) self.do_partial_pipeline() class BitbucketOAuth1FailTest(BitbucketOAuth1Test): emails_body = json.dumps({ u'page': 1, u'pagelen': 10, u'size': 1, u'values': [ { u'email': u'foo@bar.com', u'is_confirmed': False, u'is_primary': True, u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, u'type': u'email' } ] }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY': True }) with self.assertRaises(AuthForbidden): super(BitbucketOAuth1FailTest, self).test_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY': True }) with self.assertRaises(AuthForbidden): super(BitbucketOAuth1FailTest, self).test_partial_pipeline() class BitbucketOAuth2Test(BitbucketOAuthMixin, OAuth2Test): backend_path = 'social.backends.bitbucket.BitbucketOAuth2' access_token_body = json.dumps({ 'access_token': 'foobar_access', 'scopes': 'foo_scope', 'expires_in': 3600, 'refresh_token': 'foobar_refresh', 'token_type': 'bearer' }) def test_login(self): HTTPretty.register_uri(HTTPretty.GET, self.bb_api_user_emails, status=200, body=self.emails_body) self.do_login() def test_partial_pipeline(self): HTTPretty.register_uri(HTTPretty.GET, self.bb_api_user_emails, status=200, body=self.emails_body) self.do_partial_pipeline() class BitbucketOAuth2FailTest(BitbucketOAuth2Test): emails_body = json.dumps({ u'page': 1, u'pagelen': 10, u'size': 1, u'values': [ { u'email': u'foo@bar.com', u'is_confirmed': False, u'is_primary': True, u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, u'type': u'email' } ] }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY': True }) with self.assertRaises(AuthForbidden): super(BitbucketOAuth2FailTest, self).test_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY': True }) with self.assertRaises(AuthForbidden): super(BitbucketOAuth2FailTest, self).test_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_pinterest.py0000644000175500017550000000245412754357263024715 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class PinterestOAuth2Test(OAuth2Test): backend_path = 'social.backends.pinterest.PinterestOAuth2' user_data_url = 'https://api.pinterest.com/v1/me/' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'id': '4788400174839062', 'first_name': 'Foo', 'last_name': 'Bar', 'username': 'foobar', }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class PinterestOAuth2BrokenServerResponseTest(OAuth2Test): backend_path = 'social.backends.pinterest.PinterestOAuth2' user_data_url = 'https://api.pinterest.com/v1/me/' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'data': { 'id': '4788400174839062', 'first_name': 'Foo', 'last_name': 'Bar', 'url': 'https://www.pinterest.com/foobar/', } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_yammer.py0000644000175500017550000000764612754357263024202 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class YammerOAuth2Test(OAuth2Test): backend_path = 'social.backends.yammer.YammerOAuth2' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': { 'user_id': 1010101010, 'view_groups': True, 'modify_messages': True, 'network_id': 101010, 'created_at': '2013/03/17 16:39:56 +0000', 'view_members': True, 'authorized_at': '2013/03/17 16:39:56 +0000', 'view_subscriptions': True, 'view_messages': True, 'modify_subscriptions': True, 'token': 'foobar', 'expires_at': None, 'network_permalink': 'foobar.com', 'view_tags': True, 'network_name': 'foobar.com' }, 'user': { 'last_name': 'Bar', 'web_url': 'https://www.yammer.com/foobar/users/foobar', 'expertise': None, 'full_name': 'Foo Bar', 'timezone': 'Pacific Time (US & Canada)', 'mugshot_url': 'https://mug0.assets-yammer.com/mugshot/images/' '48x48/no_photo.png', 'guid': None, 'network_name': 'foobar', 'id': 1010101010, 'previous_companies': [], 'first_name': 'Foo', 'stats': { 'following': 0, 'followers': 0, 'updates': 1 }, 'hire_date': None, 'state': 'active', 'location': None, 'department': 'Software Development', 'type': 'user', 'show_ask_for_photo': True, 'job_title': 'Software Developer', 'interests': None, 'kids_names': None, 'activated_at': '2013/03/17 16:27:50 +0000', 'verified_admin': 'false', 'can_broadcast': 'false', 'schools': [], 'admin': 'false', 'network_domains': ['foobar.com'], 'name': 'foobar', 'external_urls': [], 'url': 'https://www.yammer.com/api/v1/users/1010101010', 'settings': { 'xdr_proxy': 'https://xdrproxy.yammer.com' }, 'summary': None, 'network_id': 101010, 'contact': { 'phone_numbers': [], 'im': { 'username': '', 'provider': '' }, 'email_addresses': [{ 'type': 'primary', 'address': 'foo@bar.com' }], 'has_fake_email': False }, 'birth_date': '', 'mugshot_url_template': 'https://mug0.assets-yammer.com/mugshot/' 'images/{width}x{height}/no_photo.png', 'significant_other': None }, 'network': { 'show_upgrade_banner': False, 'header_text_color': '#FFFFFF', 'is_org_chart_enabled': True, 'name': 'foobar.com', 'is_group_enabled': True, 'header_background_color': '#396B9A', 'created_at': '2012/12/26 16:52:35 +0000', 'profile_fields_config': { 'enable_work_phone': True, 'enable_mobile_phone': True, 'enable_job_title': True }, 'permalink': 'foobar.com', 'paid': False, 'id': 101010, 'is_chat_enabled': True, 'web_url': 'https://www.yammer.com/foobar.com', 'moderated': False, 'community': False, 'type': 'network', 'navigation_background_color': '#38699F', 'navigation_text_color': '#FFFFFF' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_zotero.py0000644000175500017550000000127312754357263024220 0ustar debacledebaclefrom social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class ZoteroOAuth1Test(OAuth1Test): backend_path = 'social.backends.zotero.ZoteroOAuth' expected_username = 'FooBar' access_token_body = urlencode({ 'oauth_token': 'foobar', 'oauth_token_secret': 'rodgsNDK4hLJU1504Atk131G', 'userID': '123456_abcdef', 'username': 'FooBar' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_broken.py0000644000175500017550000000206012754357263024151 0ustar debacledebacleimport unittest2 as unittest from social.backends.base import BaseAuth class BrokenBackendAuth(BaseAuth): name = 'broken' class BrokenBackendTest(unittest.TestCase): def setUp(self): self.backend = BrokenBackendAuth() def tearDown(self): self.backend = None def test_auth_url(self): with self.assertRaisesRegexp(NotImplementedError, 'Implement in subclass'): self.backend.auth_url() def test_auth_html(self): with self.assertRaisesRegexp(NotImplementedError, 'Implement in subclass'): self.backend.auth_html() def test_auth_complete(self): with self.assertRaisesRegexp(NotImplementedError, 'Implement in subclass'): self.backend.auth_complete() def test_get_user_details(self): with self.assertRaisesRegexp(NotImplementedError, 'Implement in subclass'): self.backend.get_user_details(None) python-social-auth-0.2.21/social/tests/backends/test_ngpvan.py0000644000175500017550000002051412754357263024166 0ustar debacledebacle"""Tests for NGP VAN ActionID Backend""" import datetime from httpretty import HTTPretty from social.p3 import urlencode from social.tests.backends.open_id import OpenIdTest JANRAIN_NONCE = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') class NGPVANActionIDOpenIDTest(OpenIdTest): """Test the NGP VAN ActionID OpenID 1.1 Backend""" backend_path = 'social.backends.ngpvan.ActionIDOpenID' expected_username = 'testuser@user.local' discovery_body = ' '.join([ '', '', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/extensions/sreg/1.1', 'http://axschema.org/contact/email', 'https://accounts.ngpvan.com/OpenId/Provider', '', '', 'http://openid.net/signon/1.0', 'http://openid.net/extensions/sreg/1.1', 'http://axschema.org/contact/email', 'https://accounts.ngpvan.com/OpenId/Provider', '', '', '' ]) server_response = urlencode({ 'openid.claimed_id': 'https://accounts.ngpvan.com/user/abcd123', 'openid.identity': 'https://accounts.ngpvan.com/user/abcd123', 'openid.sig': 'Midw8F/rCDwW7vMz3y+vK6rjz6s=', 'openid.signed': 'claimed_id,identity,assoc_handle,op_endpoint,return_' 'to,response_nonce,ns.alias3,alias3.mode,alias3.type.' 'alias1,alias3.value.alias1,alias3.type.alias2,alias3' '.value.alias2,alias3.type.alias3,alias3.value.alias3' ',alias3.type.alias4,alias3.value.alias4,alias3.type.' 'alias5,alias3.value.alias5,alias3.type.alias6,alias3' '.value.alias6,alias3.type.alias7,alias3.value.alias7' ',alias3.type.alias8,alias3.value.alias8,ns.sreg,sreg' '.fullname', 'openid.assoc_handle': '{635790678917902781}{GdSyFA==}{20}', 'openid.op_endpoint': 'https://accounts.ngpvan.com/OpenId/Provider', 'openid.return_to': 'http://myapp.com/complete/actionid-openid/', 'openid.response_nonce': JANRAIN_NONCE + 'MMgBGEre', 'openid.mode': 'id_res', 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.ns.alias3': 'http://openid.net/srv/ax/1.0', 'openid.alias3.mode': 'fetch_response', 'openid.alias3.type.alias1': 'http://openid.net/schema/contact/phone/b' 'usiness', 'openid.alias3.value.alias1': '+12015555555', 'openid.alias3.type.alias2': 'http://openid.net/schema/contact/interne' 't/email', 'openid.alias3.value.alias2': 'testuser@user.local', 'openid.alias3.type.alias3': 'http://openid.net/schema/namePerson/firs' 't', 'openid.alias3.value.alias3': 'John', 'openid.alias3.type.alias4': 'http://openid.net/schema/namePerson/las' 't', 'openid.alias3.value.alias4': 'Smith', 'openid.alias3.type.alias5': 'http://axschema.org/namePerson/first', 'openid.alias3.value.alias5': 'John', 'openid.alias3.type.alias6': 'http://axschema.org/namePerson/last', 'openid.alias3.value.alias6': 'Smith', 'openid.alias3.type.alias7': 'http://axschema.org/namePerson', 'openid.alias3.value.alias7': 'John Smith', 'openid.alias3.type.alias8': 'http://openid.net/schema/namePerson', 'openid.alias3.value.alias8': 'John Smith', 'openid.ns.sreg': 'http://openid.net/extensions/sreg/1.1', 'openid.sreg.fullname': 'John Smith', }) def setUp(self): """Setup the test""" super(NGPVANActionIDOpenIDTest, self).setUp() # Mock out the NGP VAN endpoints HTTPretty.register_uri( HTTPretty.POST, 'https://accounts.ngpvan.com/Home/Xrds', status=200, body=self.discovery_body ) HTTPretty.register_uri( HTTPretty.GET, 'https://accounts.ngpvan.com/user/abcd123', status=200, body=self.discovery_body ) HTTPretty.register_uri( HTTPretty.GET, 'https://accounts.ngpvan.com/OpenId/Provider', status=200, body=self.discovery_body ) def test_login(self): """Test the login flow using python-social-auth's built in test""" self.do_login() def test_partial_pipeline(self): """Test the partial flow using python-social-auth's built in test""" self.do_partial_pipeline() def test_get_ax_attributes(self): """Test that the AX attributes that NGP VAN responds with are present""" records = self.backend.get_ax_attributes() self.assertEqual(records, [ ('http://openid.net/schema/contact/internet/email', 'email'), ('http://openid.net/schema/contact/phone/business', 'phone'), ('http://openid.net/schema/namePerson/first', 'first_name'), ('http://openid.net/schema/namePerson/last', 'last_name'), ('http://openid.net/schema/namePerson', 'fullname'), ]) def test_setup_request(self): """Test the setup_request functionality in the NGP VAN backend""" # We can grab the requested attributes by grabbing the HTML of the # OpenID auth form and pulling out the hidden fields _, inputs = self.get_form_data(self.backend.auth_html()) # Confirm that the only required attribute is email self.assertEqual(inputs['openid.ax.required'], 'ngpvanemail') # Confirm that the 3 optional attributes are requested "if available" self.assertIn('ngpvanphone', inputs['openid.ax.if_available']) self.assertIn('ngpvanfirstname', inputs['openid.ax.if_available']) self.assertIn('ngpvanlastname', inputs['openid.ax.if_available']) # Verify the individual attribute properties self.assertEqual( inputs['openid.ax.type.ngpvanemail'], 'http://openid.net/schema/contact/internet/email' ) self.assertEqual( inputs['openid.ax.type.ngpvanfirstname'], 'http://openid.net/schema/namePerson/first' ) self.assertEqual( inputs['openid.ax.type.ngpvanlastname'], 'http://openid.net/schema/namePerson/last' ) self.assertEqual( inputs['openid.ax.type.ngpvanphone'], 'http://openid.net/schema/contact/phone/business' ) def test_user_data(self): """Ensure that the correct user data is being passed to create_user""" self.strategy.set_settings({ 'USER_FIELDS': [ 'email', 'first_name', 'last_name', 'username', 'phone', 'fullname' ] }) user = self.do_start() self.assertEqual(user.username, u'testuser@user.local') self.assertEqual(user.email, u'testuser@user.local') self.assertEqual(user.extra_user_fields['phone'], u'+12015555555') self.assertEqual(user.extra_user_fields['first_name'], u'John') self.assertEqual(user.extra_user_fields['last_name'], u'Smith') self.assertEqual(user.extra_user_fields['fullname'], u'John Smith') def test_extra_data_phone(self): """Confirm that you can get a phone number via the relevant setting""" self.strategy.set_settings({ 'SOCIAL_AUTH_ACTIONID_OPENID_AX_EXTRA_DATA': [ ('http://openid.net/schema/contact/phone/business', 'phone') ] }) user = self.do_start() self.assertEqual(user.social_user.extra_data['phone'], u'+12015555555') def test_association_uid(self): """Test that the correct association uid is stored in the database""" user = self.do_start() self.assertEqual( user.social_user.uid, 'https://accounts.ngpvan.com/user/abcd123' ) python-social-auth-0.2.21/social/tests/backends/test_readability.py0000644000175500017550000000244212754357263025166 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class ReadabilityOAuth1Test(OAuth1Test): backend_path = 'social.backends.readability.ReadabilityOAuth' user_data_url = 'https://www.readability.com/api/rest/v1/users/_current' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'username': 'foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'has_active_subscription': False, 'tags': [], 'is_publisher': False, 'email_into_address': 'foobar+sharp@inbox.readability.com', 'kindle_email_address': None, 'avatar_url': 'https://secure.gravatar.com/avatar/' '5280f15cedf540b544eecc30fcf3027c?d=' 'https://www.readability.com/media/images/' 'avatar.png&s=36', 'date_joined': '2013-03-18 02:51:02' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_amazon.py0000644000175500017550000000244512754357263024165 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class AmazonOAuth2Test(OAuth2Test): backend_path = 'social.backends.amazon.AmazonOAuth2' user_data_url = 'https://www.amazon.com/ap/user/profile' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'user_id': 'amzn1.account.ABCDE1234', 'email': 'foo@bar.com', 'name': 'Foo Bar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class AmazonOAuth2BrokenServerResponseTest(OAuth2Test): backend_path = 'social.backends.amazon.AmazonOAuth2' user_data_url = 'https://www.amazon.com/ap/user/profile' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'Request-Id': '02GGTU7CWMNFTV3KH3J6', 'Profile': { 'Name': 'Foo Bar', 'CustomerId': 'amzn1.account.ABCDE1234', 'PrimaryEmail': 'foo@bar.com' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_azuread.py0000644000175500017550000000615412754357263024334 0ustar debacledebacle""" Copyright (c) 2015 Microsoft Open Technologies, Inc. All rights reserved. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import json from social.tests.backends.oauth import OAuth2Test class AzureADOAuth2Test(OAuth2Test): backend_path = 'social.backends.azuread.AzureADOAuth2' user_data_url = 'https://graph.windows.net/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer', 'id_token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL' '3N0cy53aW5kb3dzLm5ldC83Mjc0MDZhYy03MDY4LTQ4ZmEtOTJiOS1jMmQ' '2NzIxMWJjNTAvIiwiaWF0IjpudWxsLCJleHAiOm51bGwsImF1ZCI6IjAyO' 'WNjMDEwLWJiNzQtNGQyYi1hMDQwLWY5Y2VkM2ZkMmM3NiIsInN1YiI6In' 'FVOHhrczltSHFuVjZRMzR6aDdTQVpvY2loOUV6cnJJOW1wVlhPSWJWQTg' 'iLCJ2ZXIiOiIxLjAiLCJ0aWQiOiI3Mjc0MDZhYy03MDY4LTQ4ZmEtOTJi' 'OS1jMmQ2NzIxMWJjNTAiLCJvaWQiOiI3ZjhlMTk2OS04YjgxLTQzOGMtO' 'GQ0ZS1hZDZmNTYyYjI4YmIiLCJ1cG4iOiJmb29iYXJAdGVzdC5vbm1pY3' 'Jvc29mdC5jb20iLCJnaXZlbl9uYW1lIjoiZm9vIiwiZmFtaWx5X25hbWU' 'iOiJiYXIiLCJuYW1lIjoiZm9vIGJhciIsInVuaXF1ZV9uYW1lIjoiZm9v' 'YmFyQHRlc3Qub25taWNyb3NvZnQuY29tIiwicHdkX2V4cCI6IjQ3MzMwO' 'TY4IiwicHdkX3VybCI6Imh0dHBzOi8vcG9ydGFsLm1pY3Jvc29mdG9ubG' 'luZS5jb20vQ2hhbmdlUGFzc3dvcmQuYXNweCJ9.3V50dHXTZOHj9UWtkn' '2g7BjX5JxNe8skYlK4PdhiLz4', 'expires_in': 3600, 'expires_on': 1423650396, 'not_before': 1423646496 }) refresh_token_body = json.dumps({ 'access_token': 'foobar-new-token', 'token_type': 'bearer', 'expires_in': 3600, 'refresh_token': 'foobar-new-refresh-token', 'scope': 'identity' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_refresh_token(self): user, social = self.do_refresh_token() self.assertEqual(social.extra_data['access_token'], 'foobar-new-token') python-social-auth-0.2.21/social/tests/backends/test_yahoo.py0000644000175500017550000000474712754357263024026 0ustar debacledebacleimport json import requests from httpretty import HTTPretty from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class YahooOAuth1Test(OAuth1Test): backend_path = 'social.backends.yahoo.YahooOAuth' user_data_url = 'https://social.yahooapis.com/v1/user/a-guid/profile?' \ 'format=json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) guid_body = json.dumps({ 'guid': { 'uri': 'https://social.yahooapis.com/v1/me/guid', 'value': 'a-guid' } }) user_data_body = json.dumps({ 'profile': { 'bdRestricted': True, 'memberSince': '2007-12-11T14:40:30Z', 'image': { 'width': 192, 'imageUrl': 'http://l.yimg.com/dh/ap/social/profile/' 'profile_b192.png', 'size': '192x192', 'height': 192 }, 'created': '2013-03-18T04:15:08Z', 'uri': 'https://social.yahooapis.com/v1/user/a-guid/profile', 'isConnected': False, 'profileUrl': 'http://profile.yahoo.com/a-guid', 'guid': 'a-guid', 'nickname': 'foobar', 'emails': [{ 'handle': 'foobar@yahoo.com', 'id': 1, 'primary': True, 'type': 'HOME', }, { 'handle': 'foobar@email.com', 'id': 2, 'type': 'HOME', }], } }) def test_login(self): HTTPretty.register_uri( HTTPretty.GET, 'https://social.yahooapis.com/v1/me/guid?format=json', status=200, body=self.guid_body ) self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_get_user_details(self): HTTPretty.register_uri( HTTPretty.GET, self.user_data_url, status=200, body=self.user_data_body ) response = requests.get(self.user_data_url) user_details = self.backend.get_user_details( response.json()['profile'] ) self.assertEqual(user_details['email'], 'foobar@yahoo.com') python-social-auth-0.2.21/social/tests/backends/test_vk.py0000644000175500017550000000151712754357263023317 0ustar debacledebacle# coding: utf-8 from __future__ import unicode_literals import json from social.tests.backends.oauth import OAuth2Test class VKOAuth2Test(OAuth2Test): backend_path = 'social.backends.vk.VKOAuth2' user_data_url = 'https://api.vk.com/method/users.get' expected_username = 'durov' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'response': [{ 'uid': '1', 'first_name': 'Павел', 'last_name': 'Дуров', 'screen_name': 'durov', 'nickname': '', 'photo': "http:\/\/cs7003.vk.me\/v7003815\/22a1\/xgG9fb-IJ3Y.jpg" }] }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_tripit.py0000644000175500017550000001017212754357263024207 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class TripitOAuth1Test(OAuth1Test): backend_path = 'social.backends.tripit.TripItOAuth' user_data_url = 'https://api.tripit.com/v1/get/profile' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_content_type = 'text/xml' user_data_body = \ '' \ '1363590451' \ '1040' \ '' \ '' \ '' \ '
foobar@gmail.com
' \ 'false' \ 'true' \ 'true' \ '' \ 'true' \ '' \ '
' \ '
' \ 'true' \ 'false' \ 'foobar' \ 'Foo Bar' \ 'people/foobar' \ 'Foo, Barland' \ '' \ 'https://www.tripit.com/feed/activities/private/' \ 'ignore-this/activities.atom' \ '' \ '' \ 'https://www.tripit.com/feed/alerts/private/' \ 'ignore-this/alerts.atom' \ '' \ '' \ 'webcal://www.tripit.com/feed/ical/private/' \ 'ignore-this/tripit.ics' \ '' \ '
' \ '
' def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class TripitOAuth1UsernameAlternativesTest(TripitOAuth1Test): user_data_body = \ '' \ '1363590451' \ '1040' \ '' \ '' \ '' \ '
foobar@gmail.com
' \ 'false' \ 'true' \ 'true' \ '' \ 'true' \ '' \ '
' \ '
' \ 'true' \ 'false' \ 'foobar' \ 'Foobar' \ 'people/foobar' \ 'Foo, Barland' \ '' \ 'https://www.tripit.com/feed/activities/private/' \ 'ignore-this/activities.atom' \ '' \ '' \ 'https://www.tripit.com/feed/alerts/private/' \ 'ignore-this/alerts.atom' \ '' \ '' \ 'webcal://www.tripit.com/feed/ical/private/' \ 'ignore-this/tripit.ics' \ '' \ '
' \ '
' python-social-auth-0.2.21/social/tests/backends/test_email.py0000644000175500017550000000074412754357263023767 0ustar debacledebaclefrom social.tests.backends.legacy import BaseLegacyTest class EmailTest(BaseLegacyTest): backend_path = 'social.backends.email.EmailAuth' expected_username = 'foo' response_body = 'email=foo@bar.com' form = """
""" def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/legacy.py0000644000175500017550000000274212754357263023105 0ustar debacledebacleimport requests from httpretty import HTTPretty from social.utils import parse_qs from social.tests.backends.base import BaseBackendTest class BaseLegacyTest(BaseBackendTest): form = '' response_body = '' def setUp(self): super(BaseLegacyTest, self).setUp() self.strategy.set_settings({ 'SOCIAL_AUTH_{0}_FORM_URL'.format(self.name): self.strategy.build_absolute_uri('/login/{0}'.format( self.backend.name)) }) def extra_settings(self): return {'SOCIAL_AUTH_{0}_FORM_URL'.format(self.name): '/login/{0}'.format(self.backend.name)} def do_start(self): start_url = self.strategy.build_absolute_uri(self.backend.start().url) HTTPretty.register_uri( HTTPretty.GET, start_url, status=200, body=self.form.format(self.complete_url) ) HTTPretty.register_uri( HTTPretty.POST, self.complete_url, status=200, body=self.response_body, content_type='application/x-www-form-urlencoded' ) response = requests.get(start_url) self.assertEqual(response.text, self.form.format(self.complete_url)) response = requests.post( self.complete_url, data=parse_qs(self.response_body) ) self.strategy.set_request_data(parse_qs(response.text), self.backend) return self.backend.complete() python-social-auth-0.2.21/social/tests/backends/test_mixcloud.py0000644000175500017550000000460212754357263024521 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class MixcloudOAuth2Test(OAuth2Test): backend_path = 'social.backends.mixcloud.MixcloudOAuth2' user_data_url = 'https://api.mixcloud.com/me/' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'username': 'foobar', 'cloudcast_count': 0, 'following_count': 0, 'url': 'http://www.mixcloud.com/foobar/', 'pictures': { 'medium': 'http://images-mix.netdna-ssl.com/w/100/h/100/q/85/' 'images/graphics/33_Profile/default_user_600x600-v4.png', '320wx320h': 'http://images-mix.netdna-ssl.com/w/320/h/320/q/85/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png', 'extra_large': 'http://images-mix.netdna-ssl.com/w/600/h/600/q/85/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png', 'large': 'http://images-mix.netdna-ssl.com/w/300/h/300/q/85/' 'images/graphics/33_Profile/default_user_600x600-v4.png', '640wx640h': 'http://images-mix.netdna-ssl.com/w/640/h/640/q/85/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png', 'medium_mobile': 'http://images-mix.netdna-ssl.com/w/80/h/80/q/75/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png', 'small': 'http://images-mix.netdna-ssl.com/w/25/h/25/q/85/images/' 'graphics/33_Profile/default_user_600x600-v4.png', 'thumbnail': 'http://images-mix.netdna-ssl.com/w/50/h/50/q/85/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png' }, 'is_current_user': True, 'listen_count': 0, 'updated_time': '2013-03-17T06:26:31Z', 'following': False, 'follower': False, 'key': '/foobar/', 'created_time': '2013-03-17T06:26:31Z', 'follower_count': 0, 'favorite_count': 0, 'name': 'foobar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_twitch.py0000644000175500017550000000203212754357263024172 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class TwitchOAuth2Test(OAuth2Test): backend_path = 'social.backends.twitch.TwitchOAuth2' user_data_url = 'https://api.twitch.tv/kraken/user/' expected_username = 'test_user1' access_token_body = json.dumps({ 'access_token': 'foobar', }) user_data_body = json.dumps({ 'type': 'user', 'name': 'test_user1', 'created_at': '2011-06-03T17:49:19Z', 'updated_at': '2012-06-18T17:19:57Z', '_links': { 'self': 'https://api.twitch.tv/kraken/users/test_user1' }, 'logo': 'http://static-cdn.jtvnw.net/jtv_user_pictures/' 'test_user1-profile_image-62e8318af864d6d7-300x300.jpeg', '_id': 22761313, 'display_name': 'test_user1', 'email': 'asdf@asdf.com', 'partnered': True, 'bio': 'test bio woo I\'m a test user' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_kakao.py0000644000175500017550000000153712754357263023767 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class KakaoOAuth2Test(OAuth2Test): backend_path = 'social.backends.kakao.KakaoOAuth2' user_data_url = 'https://kapi.kakao.com/v1/user/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar' }) user_data_body = json.dumps({ 'id': '101010101', 'properties': { 'nickname': 'foobar', 'thumbnail_image': 'http://mud-kage.kakao.co.kr/14/dn/btqbh1AKmRf/' 'ujlHpQhxtMSbhKrBisrxe1/o.jpg', 'profile_image': 'http://mud-kage.kakao.co.kr/14/dn/btqbjCnl06Q/' 'wbMJSVAUZB7lzSImgGdsoK/o.jpg' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_github_enterprise.py0000644000175500017550000002352212754357263026421 0ustar debacledebacleimport json from httpretty import HTTPretty from social.exceptions import AuthFailed from social.tests.backends.oauth import OAuth2Test class GithubEnterpriseOAuth2Test(OAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseOAuth2' user_data_url = 'https://www.example.com/api/v3/user' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://www.example.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://www.example.com/api/v3/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://www.example.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://www.example.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) self.do_partial_pipeline() class GithubEnterpriseOAuth2NoEmailTest(GithubEnterpriseOAuth2Test): user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://www.example.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://www.example.com/api/v3/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://www.example.com/blog', 'location': 'San Francisco', 'email': '', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://www.example.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) url = 'https://www.example.com/api/v3/user/emails' HTTPretty.register_uri(HTTPretty.GET, url, status=200, body=json.dumps(['foo@bar.com']), content_type='application/json') self.do_login() def test_login_next_format(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) url = 'https://www.example.com/api/v3/user/emails' HTTPretty.register_uri(HTTPretty.GET, url, status=200, body=json.dumps([{'email': 'foo@bar.com'}]), content_type='application/json') self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) self.do_partial_pipeline() class GithubEnterpriseOrganizationOAuth2Test(GithubEnterpriseOAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2' def auth_handlers(self, start_url): url = 'https://www.example.com/api/v3/orgs/foobar/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=204, body='') return super(GithubEnterpriseOrganizationOAuth2Test, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME': 'foobar'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME': 'foobar'}) self.do_partial_pipeline() class GithubEnterpriseOrganizationOAuth2FailTest(GithubEnterpriseOAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2' def auth_handlers(self, start_url): url = 'https://www.example.com/api/v3/orgs/foobar/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=404, body='{"message": "Not Found"}', content_type='application/json') return super(GithubEnterpriseOrganizationOAuth2FailTest, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME': 'foobar'}) with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME': 'foobar'}) with self.assertRaises(AuthFailed): self.do_partial_pipeline() class GithubEnterpriseTeamOAuth2Test(GithubEnterpriseOAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseTeamOAuth2' def auth_handlers(self, start_url): url = 'https://www.example.com/api/v3/teams/123/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=204, body='') return super(GithubEnterpriseTeamOAuth2Test, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID': '123'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID': '123'}) self.do_partial_pipeline() class GithubEnterpriseTeamOAuth2FailTest(GithubEnterpriseOAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseTeamOAuth2' def auth_handlers(self, start_url): url = 'https://www.example.com/api/v3/teams/123/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=404, body='{"message": "Not Found"}', content_type='application/json') return super(GithubEnterpriseTeamOAuth2FailTest, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID': '123'}) with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID': '123'}) with self.assertRaises(AuthFailed): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_orbi.py0000644000175500017550000000142212754357263023625 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class OrbiOAuth2Test(OAuth2Test): backend_path = 'social.backends.orbi.OrbiOAuth2' user_data_url = 'https://login.orbi.kr/oauth/user/get' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', }) user_data_body = json.dumps({ 'username': 'foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'name': 'Foo Bar', 'imin': '100000', 'nick': 'foobar', 'photo': 'http://s3.orbi.kr/data/member/wi/wizetdev_132894975780.jpeg', 'sex': 'M', 'birth': '1973-08-03', }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_twitter.py0000644000175500017550000002225112754357263024377 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class TwitterOAuth1Test(OAuth1Test): backend_path = 'social.backends.twitter.TwitterOAuth' user_data_url = 'https://api.twitter.com/1.1/account/' \ 'verify_credentials.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'follow_request_sent': False, 'profile_use_background_image': True, 'id': 10101010, 'description': 'Foo bar baz qux', 'verified': False, 'entities': { 'description': { 'urls': [] } }, 'profile_image_url_https': 'https://twimg0-a.akamaihd.net/' 'profile_images/532018826/' 'n587119531_1939735_9305_normal.jpg', 'profile_sidebar_fill_color': '252429', 'profile_text_color': '666666', 'followers_count': 77, 'profile_sidebar_border_color': '181A1E', 'location': 'Fooland', 'default_profile_image': False, 'listed_count': 4, 'status': { 'favorited': False, 'contributors': None, 'retweeted_status': { 'favorited': False, 'contributors': None, 'truncated': False, 'source': 'web', 'text': '"Foo foo foo foo', 'created_at': 'Fri Dec 21 18:12:00 +0000 2012', 'retweeted': True, 'in_reply_to_status_id': None, 'coordinates': None, 'id': 101010101010101010, 'entities': { 'user_mentions': [], 'hashtags': [], 'urls': [] }, 'in_reply_to_status_id_str': None, 'place': None, 'id_str': '101010101010101010', 'in_reply_to_screen_name': None, 'retweet_count': 8, 'geo': None, 'in_reply_to_user_id_str': None, 'in_reply_to_user_id': None }, 'truncated': False, 'source': 'web', 'text': 'RT @foo: "Foo foo foo foo', 'created_at': 'Fri Dec 21 18:24:10 +0000 2012', 'retweeted': True, 'in_reply_to_status_id': None, 'coordinates': None, 'id': 101010101010101010, 'entities': { 'user_mentions': [{ 'indices': [3, 10], 'id': 10101010, 'screen_name': 'foo', 'id_str': '10101010', 'name': 'Foo' }], 'hashtags': [], 'urls': [] }, 'in_reply_to_status_id_str': None, 'place': None, 'id_str': '101010101010101010', 'in_reply_to_screen_name': None, 'retweet_count': 8, 'geo': None, 'in_reply_to_user_id_str': None, 'in_reply_to_user_id': None }, 'utc_offset': -10800, 'statuses_count': 191, 'profile_background_color': '1A1B1F', 'friends_count': 151, 'profile_background_image_url_https': 'https://twimg0-a.akamaihd.net/' 'images/themes/theme9/bg.gif', 'profile_link_color': '2FC2EF', 'profile_image_url': 'http://a0.twimg.com/profile_images/532018826/' 'n587119531_1939735_9305_normal.jpg', 'is_translator': False, 'geo_enabled': False, 'id_str': '74313638', 'profile_background_image_url': 'http://a0.twimg.com/images/themes/' 'theme9/bg.gif', 'screen_name': 'foobar', 'lang': 'en', 'profile_background_tile': False, 'favourites_count': 2, 'name': 'Foo', 'notifications': False, 'url': None, 'created_at': 'Tue Sep 15 00:26:17 +0000 2009', 'contributors_enabled': False, 'time_zone': 'Buenos Aires', 'protected': False, 'default_profile': False, 'following': False }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class TwitterOAuth1IncludeEmailTest(OAuth1Test): backend_path = 'social.backends.twitter.TwitterOAuth' user_data_url = 'https://api.twitter.com/1.1/account/' \ 'verify_credentials.json?include_email=true' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'follow_request_sent': False, 'profile_use_background_image': True, 'id': 10101010, 'description': 'Foo bar baz qux', 'verified': False, 'entities': { 'description': { 'urls': [] } }, 'profile_image_url_https': 'https://twimg0-a.akamaihd.net/' 'profile_images/532018826/' 'n587119531_1939735_9305_normal.jpg', 'profile_sidebar_fill_color': '252429', 'profile_text_color': '666666', 'followers_count': 77, 'profile_sidebar_border_color': '181A1E', 'location': 'Fooland', 'default_profile_image': False, 'listed_count': 4, 'status': { 'favorited': False, 'contributors': None, 'retweeted_status': { 'favorited': False, 'contributors': None, 'truncated': False, 'source': 'web', 'text': '"Foo foo foo foo', 'created_at': 'Fri Dec 21 18:12:00 +0000 2012', 'retweeted': True, 'in_reply_to_status_id': None, 'coordinates': None, 'id': 101010101010101010, 'entities': { 'user_mentions': [], 'hashtags': [], 'urls': [] }, 'in_reply_to_status_id_str': None, 'place': None, 'id_str': '101010101010101010', 'in_reply_to_screen_name': None, 'retweet_count': 8, 'geo': None, 'in_reply_to_user_id_str': None, 'in_reply_to_user_id': None }, 'truncated': False, 'source': 'web', 'text': 'RT @foo: "Foo foo foo foo', 'created_at': 'Fri Dec 21 18:24:10 +0000 2012', 'retweeted': True, 'in_reply_to_status_id': None, 'coordinates': None, 'id': 101010101010101010, 'entities': { 'user_mentions': [{ 'indices': [3, 10], 'id': 10101010, 'screen_name': 'foo', 'id_str': '10101010', 'name': 'Foo' }], 'hashtags': [], 'urls': [] }, 'in_reply_to_status_id_str': None, 'place': None, 'id_str': '101010101010101010', 'in_reply_to_screen_name': None, 'retweet_count': 8, 'geo': None, 'in_reply_to_user_id_str': None, 'in_reply_to_user_id': None }, 'utc_offset': -10800, 'statuses_count': 191, 'profile_background_color': '1A1B1F', 'friends_count': 151, 'profile_background_image_url_https': 'https://twimg0-a.akamaihd.net/' 'images/themes/theme9/bg.gif', 'profile_link_color': '2FC2EF', 'profile_image_url': 'http://a0.twimg.com/profile_images/532018826/' 'n587119531_1939735_9305_normal.jpg', 'is_translator': False, 'geo_enabled': False, 'id_str': '74313638', 'profile_background_image_url': 'http://a0.twimg.com/images/themes/' 'theme9/bg.gif', 'screen_name': 'foobar', 'lang': 'en', 'profile_background_tile': False, 'favourites_count': 2, 'name': 'Foo', 'notifications': False, 'url': None, 'created_at': 'Tue Sep 15 00:26:17 +0000 2009', 'contributors_enabled': False, 'time_zone': 'Buenos Aires', 'protected': False, 'default_profile': False, 'following': False, 'email': 'foo@bar.bas' }) def test_login(self): user = self.do_login() self.assertEquals(user.email, 'foo@bar.bas') def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_uber.py0000644000175500017550000000175312754357263023636 0ustar debacledebacleimport json from httpretty import HTTPretty from social.p3 import urlencode from social.exceptions import AuthForbidden from social.tests.backends.oauth import OAuth1Test, OAuth2Test class UberOAuth2Test(OAuth2Test): user_data_url = 'https://api.uber.com/v1/me' backend_path = 'social.backends.uber.UberOAuth2' expected_username = 'foo@bar.com' user_data_body = json.dumps({ "first_name": "Foo", "last_name": "Bar", "email": "foo@bar.com", "picture": "https://", "promo_code": "barfoo", "uuid": "91d81273-45c2-4b57-8124-d0165f8240c0" }) access_token_body = json.dumps({ "access_token": "EE1IDxytP04tJ767GbjH7ED9PpGmYvL", "token_type": "Bearer", "expires_in": 2592000, "refresh_token": "Zx8fJ8qdSRRseIVlsGgtgQ4wnZBehr", "scope": "profile history request" }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_flickr.py0000644000175500017550000000125512754357263024150 0ustar debacledebaclefrom social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class FlickrOAuth1Test(OAuth1Test): backend_path = 'social.backends.flickr.FlickrOAuth' expected_username = 'foobar' access_token_body = urlencode({ 'oauth_token_secret': 'a-secret', 'username': 'foobar', 'oauth_token': 'foobar', 'user_nsid': '10101010@N01' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_skyrock.py0000644000175500017550000000247712754357263024372 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class SkyrockOAuth1Test(OAuth1Test): backend_path = 'social.backends.skyrock.SkyrockOAuth' user_data_url = 'https://api.skyrock.com/v2/user/get.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', }) user_data_body = json.dumps({ 'locale': 'en_US', 'city': '', 'has_blog': False, 'web_messager_enabled': True, 'email': 'foo@bar.com', 'username': 'foobar', 'firstname': 'Foo', 'user_url': '', 'address1': '', 'address2': '', 'has_profile': False, 'allow_messages_from': 'everybody', 'is_online': False, 'postalcode': '', 'lang': 'en', 'id_user': 10101010, 'name': 'Bar', 'gender': 0, 'avatar_url': 'http://www.skyrock.com/img/avatars/default-0.jpg', 'nb_friends': 0, 'country': 'US', 'birth_date': '1980-06-10' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_itembase.py0000644000175500017550000000253712754357263024473 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class ItembaseOAuth2Test(OAuth2Test): backend_path = 'social.backends.itembase.ItembaseOAuth2' user_data_url = 'https://users.itembase.com/v1/me' expected_username = 'foobar' access_token_body = json.dumps({ "access_token": "foobar-token", "expires_in": 2592000, "token_type": "bearer", "scope": "user.minimal", "refresh_token": "foobar-refresh-token" }) user_data_body = json.dumps({ "uuid": "a4b91ee7-ec1a-49b9-afce-371dc8797749", "username": "foobar", "email": "foobar@itembase.biz", "first_name": "Foo", "middle_name": None, "last_name": "Bar", "name_format": "first middle last", "locale": "en", "preferred_currency": "EUR" }) refresh_token_body = json.dumps({ "access_token": "foobar-new-token", "expires_in": 2592000, "token_type": "bearer", "scope": "user.minimal", "refresh_token": "foobar-new-refresh-token" }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class ItembaseOAuth2SandboxTest(OAuth2Test): backend_path = 'social.backends.itembase.ItembaseOAuth2Sandbox' user_data_url = 'http://sandbox.users.itembase.io/v1/me' python-social-auth-0.2.21/social/tests/backends/test_username.py0000644000175500017550000000076112754357263024516 0ustar debacledebaclefrom social.tests.backends.legacy import BaseLegacyTest class UsernameTest(BaseLegacyTest): backend_path = 'social.backends.username.UsernameAuth' expected_username = 'foobar' response_body = 'username=foobar' form = """
""" def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_khanacademy.py0000644000175500017550000000152512754357263025143 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class KhanAcademyOAuth1Test(OAuth1Test): backend_path = 'social.backends.khanacademy.KhanAcademyOAuth1' user_data_url = 'https://www.khanacademy.org/api/v1/user' expected_username = 'foo@bar.com' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ "key_email": "foo@bar.com", "user_id": "http://googleid.khanacademy.org/11111111111111", }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_xing.py0000644000175500017550000001406712754357263023650 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class XingOAuth1Test(OAuth1Test): backend_path = 'social.backends.xing.XingOAuth' user_data_url = 'https://api.xing.com/v1/users/me.json' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer', 'user_id': '123456_abcdef' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'users': [{ 'id': '123456_abcdef', 'first_name': 'Foo', 'last_name': 'Bar', 'display_name': 'Foo Bar', 'page_name': 'Foo_Bar', 'permalink': 'https://www.xing.com/profile/Foo_Bar', 'gender': 'm', 'birth_date': { 'day': 12, 'month': 8, 'year': 1963 }, 'active_email': 'foo@bar.com', 'time_zone': { 'name': 'Europe/Copenhagen', 'utc_offset': 2.0 }, 'premium_services': ['SEARCH', 'PRIVATEMESSAGES'], 'badges': ['PREMIUM', 'MODERATOR'], 'wants': 'Nothing', 'haves': 'Skills', 'interests': 'Foo Foo', 'organisation_member': 'ACM, GI', 'languages': { 'de': 'NATIVE', 'en': 'FLUENT', 'fr': None, 'zh': 'BASIC' }, 'private_address': { 'city': 'Foo', 'country': 'DE', 'zip_code': '20357', 'street': 'Bar', 'phone': '12|34|1234560', 'fax': '||', 'province': 'Foo', 'email': 'foo@bar.com', 'mobile_phone': '12|3456|1234567' }, 'business_address': { 'city': 'Foo', 'country': 'DE', 'zip_code': '20357', 'street': 'Bar', 'phone': '12|34|1234569', 'fax': '12|34|1234561', 'province': 'Foo', 'email': 'foo@bar.com', 'mobile_phone': '12|345|12345678' }, 'web_profiles': { 'qype': ['http://qype.de/users/foo'], 'google_plus': ['http://plus.google.com/foo'], 'blog': ['http://blog.example.org'], 'homepage': ['http://example.org', 'http://other-example.org'] }, 'instant_messaging_accounts': { 'skype': 'foobar', 'googletalk': 'foobar' }, 'professional_experience': { 'primary_company': { 'name': 'XING AG', 'title': 'Softwareentwickler', 'company_size': '201-500', 'tag': None, 'url': 'http://www.xing.com', 'career_level': 'PROFESSIONAL_EXPERIENCED', 'begin_date': '2010-01', 'description': None, 'end_date': None, 'industry': 'AEROSPACE' }, 'non_primary_companies': [{ 'name': 'Ninja Ltd.', 'title': 'DevOps', 'company_size': None, 'tag': 'NINJA', 'url': 'http://www.ninja-ltd.co.uk', 'career_level': None, 'begin_date': '2009-04', 'description': None, 'end_date': '2010-07', 'industry': 'ALTERNATIVE_MEDICINE' }, { 'name': None, 'title': 'Wiss. Mitarbeiter', 'company_size': None, 'tag': 'OFFIS', 'url': 'http://www.uni.de', 'career_level': None, 'begin_date': '2007', 'description': None, 'end_date': '2008', 'industry': 'APPAREL_AND_FASHION' }, { 'name': None, 'title': 'TEST NINJA', 'company_size': '201-500', 'tag': 'TESTCOMPANY', 'url': None, 'career_level': 'ENTRY_LEVEL', 'begin_date': '1998-12', 'description': None, 'end_date': '1999-05', 'industry': 'ARTS_AND_CRAFTS' }], 'awards': [{ 'name': 'Awesome Dude Of The Year', 'date_awarded': 2007, 'url': None }] }, 'educational_background': { 'schools': [{ 'name': 'Foo University', 'degree': 'MSc CE/CS', 'notes': None, 'subject': None, 'begin_date': '1998-08', 'end_date': '2005-02' }], 'qualifications': ['TOEFLS', 'PADI AOWD'] }, 'photo_urls': { 'large': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.140x185.jpg', 'mini_thumb': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.18x24.jpg', 'thumb': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.30x40.jpg', 'medium_thumb': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.57x75.jpg', 'maxi_thumb': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.70x93.jpg' } }] }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_coinbase.py0000644000175500017550000000266312754357263024465 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class CoinbaseOAuth2Test(OAuth2Test): backend_path = 'social.backends.coinbase.CoinbaseOAuth2' user_data_url = 'https://coinbase.com/api/v1/users' expected_username = 'SatoshiNakamoto' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'users': [ { 'user': { 'id': "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", 'name': "Satoshi Nakamoto", 'email': "satoshi@nakamoto.com", 'pin': None, 'time_zone': "Eastern Time (US & Canada)", 'native_currency': "USD", 'buy_level': 2, 'sell_level': 2, 'balance': { 'amount': "1000000", 'currency': "BTC" }, 'buy_limit': { 'amount': "50.00000000", 'currency': "BTC" }, 'sell_limit': { 'amount': "50.00000000", 'currency': "BTC" } } } ] }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_stripe.py0000644000175500017550000000115212754357263024200 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class StripeOAuth2Test(OAuth2Test): backend_path = 'social.backends.stripe.StripeOAuth2' access_token_body = json.dumps({ 'stripe_publishable_key': 'pk_test_foobar', 'access_token': 'foobar', 'livemode': False, 'token_type': 'bearer', 'scope': 'read_only', 'refresh_token': 'rt_foobar', 'stripe_user_id': 'acct_foobar' }) expected_username = 'acct_foobar' def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_github.py0000644000175500017550000001445212754357263024163 0ustar debacledebacleimport json from httpretty import HTTPretty from social.exceptions import AuthFailed from social.tests.backends.oauth import OAuth2Test class GithubOAuth2Test(OAuth2Test): backend_path = 'social.backends.github.GithubOAuth2' user_data_url = 'https://api.github.com/user' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class GithubOAuth2NoEmailTest(GithubOAuth2Test): user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': '', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_login(self): url = 'https://api.github.com/user/emails' HTTPretty.register_uri(HTTPretty.GET, url, status=200, body=json.dumps(['foo@bar.com']), content_type='application/json') self.do_login() def test_login_next_format(self): url = 'https://api.github.com/user/emails' HTTPretty.register_uri(HTTPretty.GET, url, status=200, body=json.dumps([{'email': 'foo@bar.com'}]), content_type='application/json') self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class GithubOrganizationOAuth2Test(GithubOAuth2Test): backend_path = 'social.backends.github.GithubOrganizationOAuth2' def auth_handlers(self, start_url): url = 'https://api.github.com/orgs/foobar/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=204, body='') return super(GithubOrganizationOAuth2Test, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ORG_NAME': 'foobar'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ORG_NAME': 'foobar'}) self.do_partial_pipeline() class GithubOrganizationOAuth2FailTest(GithubOAuth2Test): backend_path = 'social.backends.github.GithubOrganizationOAuth2' def auth_handlers(self, start_url): url = 'https://api.github.com/orgs/foobar/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=404, body='{"message": "Not Found"}', content_type='application/json') return super(GithubOrganizationOAuth2FailTest, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ORG_NAME': 'foobar'}) with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ORG_NAME': 'foobar'}) with self.assertRaises(AuthFailed): self.do_partial_pipeline() class GithubTeamOAuth2Test(GithubOAuth2Test): backend_path = 'social.backends.github.GithubTeamOAuth2' def auth_handlers(self, start_url): url = 'https://api.github.com/teams/123/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=204, body='') return super(GithubTeamOAuth2Test, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_TEAM_ID': '123'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_TEAM_ID': '123'}) self.do_partial_pipeline() class GithubTeamOAuth2FailTest(GithubOAuth2Test): backend_path = 'social.backends.github.GithubTeamOAuth2' def auth_handlers(self, start_url): url = 'https://api.github.com/teams/123/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=404, body='{"message": "Not Found"}', content_type='application/json') return super(GithubTeamOAuth2FailTest, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_TEAM_ID': '123'}) with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_TEAM_ID': '123'}) with self.assertRaises(AuthFailed): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/base.py0000644000175500017550000001402012754357263022543 0ustar debacledebacleimport unittest2 as unittest import requests from httpretty import HTTPretty from social.utils import module_member, parse_qs from social.backends.utils import user_backends_data, load_backends from social.tests.strategy import TestStrategy from social.tests.models import User, TestUserSocialAuth, TestNonce, \ TestAssociation, TestCode, TestStorage class BaseBackendTest(unittest.TestCase): backend = None backend_path = None name = None complete_url = '' raw_complete_url = '/complete/{0}' def setUp(self): HTTPretty.enable() Backend = module_member(self.backend_path) self.strategy = TestStrategy(TestStorage) self.backend = Backend(self.strategy, redirect_uri=self.complete_url) self.name = self.backend.name.upper().replace('-', '_') self.complete_url = self.strategy.build_absolute_uri( self.raw_complete_url.format(self.backend.name) ) backends = (self.backend_path, 'social.tests.backends.test_broken.BrokenBackendAuth') self.strategy.set_settings({ 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': backends }) self.strategy.set_settings(self.extra_settings()) # Force backends loading to trash PSA cache load_backends(backends, force_load=True) User.reset_cache() TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() TestCode.reset_cache() def tearDown(self): HTTPretty.disable() self.backend = None self.strategy = None self.name = None self.complete_url = None User.reset_cache() TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() TestCode.reset_cache() def extra_settings(self): return {} def do_start(self): raise NotImplementedError('Implement in subclass') def do_login(self): user = self.do_start() username = self.expected_username self.assertEqual(user.username, username) self.assertEqual(self.strategy.session_get('username'), username) self.assertEqual(self.strategy.get_user(user.id), user) self.assertEqual(self.backend.get_user(user.id), user) user_backends = user_backends_data( user, self.strategy.get_setting('SOCIAL_AUTH_AUTHENTICATION_BACKENDS'), self.strategy.storage ) self.assertEqual(len(list(user_backends.keys())), 3) self.assertEqual('associated' in user_backends, True) self.assertEqual('not_associated' in user_backends, True) self.assertEqual('backends' in user_backends, True) self.assertEqual(len(user_backends['associated']), 1) self.assertEqual(len(user_backends['not_associated']), 1) self.assertEqual(len(user_backends['backends']), 2) return user def pipeline_settings(self): self.strategy.set_settings({ 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.partial.save_status_to_session', 'social.tests.pipeline.ask_for_password', 'social.tests.pipeline.ask_for_slug', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.social_auth.associate_by_email', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.tests.pipeline.set_password', 'social.tests.pipeline.set_slug', 'social.pipeline.user.user_details' ) }) def pipeline_handlers(self, url): HTTPretty.register_uri(HTTPretty.GET, url, status=200, body='foobar') HTTPretty.register_uri(HTTPretty.POST, url, status=200) def pipeline_password_handling(self, url): password = 'foobar' requests.get(url) requests.post(url, data={'password': password}) data = parse_qs(HTTPretty.last_request.body) self.assertEqual(data['password'], password) self.strategy.session_set('password', data['password']) return password def pipeline_slug_handling(self, url): slug = 'foo-bar' requests.get(url) requests.post(url, data={'slug': slug}) data = parse_qs(HTTPretty.last_request.body) self.assertEqual(data['slug'], slug) self.strategy.session_set('slug', data['slug']) return slug def do_partial_pipeline(self): url = self.strategy.build_absolute_uri('/password') self.pipeline_settings() redirect = self.do_start() self.assertEqual(redirect.url, url) self.pipeline_handlers(url) password = self.pipeline_password_handling(url) data = self.strategy.session_pop('partial_pipeline') idx, backend, xargs, xkwargs = self.strategy.partial_from_session(data) self.assertEqual(backend, self.backend.name) redirect = self.backend.continue_pipeline(pipeline_index=idx, *xargs, **xkwargs) url = self.strategy.build_absolute_uri('/slug') self.assertEqual(redirect.url, url) self.pipeline_handlers(url) slug = self.pipeline_slug_handling(url) data = self.strategy.session_pop('partial_pipeline') idx, backend, xargs, xkwargs = self.strategy.partial_from_session(data) self.assertEqual(backend, self.backend.name) user = self.backend.continue_pipeline(pipeline_index=idx, *xargs, **xkwargs) self.assertEqual(user.username, self.expected_username) self.assertEqual(user.slug, slug) self.assertEqual(user.password, password) return user python-social-auth-0.2.21/social/tests/backends/test_angel.py0000644000175500017550000000212112754357263023755 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class AngelOAuth2Test(OAuth2Test): backend_path = 'social.backends.angel.AngelOAuth2' user_data_url = 'https://api.angel.co/1/me/' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'facebook_url': 'http://www.facebook.com/foobar', 'bio': None, 'name': 'Foo Bar', 'roles': [], 'github_url': None, 'angellist_url': 'https://angel.co/foobar', 'image': 'https://graph.facebook.com/foobar/picture?type=square', 'linkedin_url': None, 'locations': [], 'twitter_url': None, 'what_ive_built': None, 'dribbble_url': None, 'behance_url': None, 'blog_url': None, 'aboutme_url': None, 'follower_count': 0, 'online_bio_url': None, 'id': 101010 }) expected_username = 'foobar' def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/oauth.py0000644000175500017550000001067212754357263022762 0ustar debacledebacleimport requests from httpretty import HTTPretty from social.p3 import urlparse from social.utils import parse_qs, url_add_parameters from social.tests.models import User from social.tests.backends.base import BaseBackendTest class BaseOAuthTest(BaseBackendTest): backend = None backend_path = None user_data_body = None user_data_url = '' user_data_content_type = 'application/json' access_token_body = None access_token_status = 200 expected_username = '' def extra_settings(self): return {'SOCIAL_AUTH_' + self.name + '_KEY': 'a-key', 'SOCIAL_AUTH_' + self.name + '_SECRET': 'a-secret-key'} def _method(self, method): return {'GET': HTTPretty.GET, 'POST': HTTPretty.POST}[method] def handle_state(self, start_url, target_url): start_query = parse_qs(urlparse(start_url).query) redirect_uri = start_query.get('redirect_uri') if getattr(self.backend, 'STATE_PARAMETER', False): if start_query.get('state'): target_url = url_add_parameters(target_url, { 'state': start_query['state'] }) if redirect_uri and getattr(self.backend, 'REDIRECT_STATE', False): redirect_query = parse_qs(urlparse(redirect_uri).query) if redirect_query.get('redirect_state'): target_url = url_add_parameters(target_url, { 'redirect_state': redirect_query['redirect_state'] }) return target_url def auth_handlers(self, start_url): target_url = self.handle_state(start_url, self.strategy.build_absolute_uri( self.complete_url )) HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=target_url) HTTPretty.register_uri(HTTPretty.GET, target_url, status=200, body='foobar') HTTPretty.register_uri(self._method(self.backend.ACCESS_TOKEN_METHOD), uri=self.backend.access_token_url(), status=self.access_token_status, body=self.access_token_body or '', content_type='text/json') if self.user_data_url: HTTPretty.register_uri(HTTPretty.GET, self.user_data_url, body=self.user_data_body or '', content_type=self.user_data_content_type) return target_url def do_start(self): start_url = self.backend.start().url target_url = self.auth_handlers(start_url) response = requests.get(start_url) self.assertEqual(response.url, target_url) self.assertEqual(response.text, 'foobar') self.strategy.set_request_data(parse_qs(urlparse(target_url).query), self.backend) return self.backend.complete() class OAuth1Test(BaseOAuthTest): request_token_body = None raw_complete_url = '/complete/{0}/?oauth_verifier=bazqux&' \ 'oauth_token=foobar' def request_token_handler(self): HTTPretty.register_uri(self._method(self.backend.REQUEST_TOKEN_METHOD), self.backend.REQUEST_TOKEN_URL, body=self.request_token_body, status=200) def do_start(self): self.request_token_handler() return super(OAuth1Test, self).do_start() class OAuth2Test(BaseOAuthTest): raw_complete_url = '/complete/{0}/?code=foobar' refresh_token_body = '' def refresh_token_arguments(self): return {} def do_refresh_token(self): self.do_login() HTTPretty.register_uri(self._method(self.backend.REFRESH_TOKEN_METHOD), self.backend.refresh_token_url(), status=200, body=self.refresh_token_body) user = list(User.cache.values())[0] social = user.social[0] social.refresh_token(strategy=self.strategy, **self.refresh_token_arguments()) return user, social python-social-auth-0.2.21/social/tests/backends/test_edmodo.py0000644000175500017550000000247312754357263024150 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class EdmodoOAuth2Test(OAuth2Test): backend_path = 'social.backends.edmodo.EdmodoOAuth2' user_data_url = 'https://api.edmodo.com/users/me' expected_username = 'foobar12345' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'username': 'foobar12345', 'coppa_verified': False, 'first_name': 'Foo', 'last_name': 'Bar', 'premium': False, 'verified_institution_member': False, 'url': 'https://api.edmodo.com/users/12345', 'type': 'teacher', 'time_zone': None, 'end_level': None, 'start_level': None, 'locale': 'en', 'subjects': None, 'utc_offset': None, 'email': 'foo.bar@example.com', 'gender': None, 'about': None, 'user_title': None, 'id': 12345, 'avatars': { 'small': 'https://api.edmodo.com/users/12345/avatar?type=small&u=5a15xug93m53mi4ey3ck4fvkq', 'large': 'https://api.edmodo.com/users/12345/avatar?type=large&u=5a15xug93m53mi4ey3ck4fvkq' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_spotify.py0000644000175500017550000000161012754357263024366 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class SpotifyOAuth2Test(OAuth2Test): backend_path = 'social.backends.spotify.SpotifyOAuth2' user_data_url = 'https://api.spotify.com/v1/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'display_name': None, 'external_urls': { 'spotify': 'https://open.spotify.com/user/foobar' }, 'followers': { 'href': None, 'total': 0 }, 'href': 'https://api.spotify.com/v1/users/foobar', 'id': 'foobar', 'images': [], 'type': 'user', 'uri': 'spotify:user:foobar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_digitalocean.py0000644000175500017550000000174112754357263025321 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class DigitalOceanOAuthTest(OAuth2Test): backend_path = 'social.backends.digitalocean.DigitalOceanOAuth' user_data_url = 'https://api.digitalocean.com/v2/account' expected_username = 'sammy@digitalocean.com' access_token_body = json.dumps({ 'access_token': '547cac21118ae7', 'token_type': 'bearer', 'expires_in': 2592000, 'refresh_token': '00a3aae641658d', 'scope': 'read write', 'info': { 'name': 'Sammy Shark', 'email': 'sammy@digitalocean.com' } }) user_data_body = json.dumps({ "account": { 'droplet_limit': 25, 'email': 'sammy@digitalocean.com', 'uuid': 'b6fr89dbf6d9156cace5f3c78dc9851d957381ef', 'email_verified': True } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_coursera.py0000644000175500017550000000214312754357263024516 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class CourseraOAuth2Test(OAuth2Test): backend_path = 'social.backends.coursera.CourseraOAuth2' user_data_url = \ 'https://api.coursera.org/api/externalBasicProfiles.v1?q=me' expected_username = '560e7ed2076e0d589e88bd74b6aad4b7' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'Bearer', 'expires_in': 1795 }) request_token_body = json.dumps({ 'code': 'foobar-code', 'client_id': 'foobar-client-id', 'client_secret': 'foobar-client-secret', 'redirect_uri': 'http://localhost:8000/accounts/coursera/', 'grant_type': 'authorization_code' }) user_data_body = json.dumps({ 'token_type': 'Bearer', 'paging': None, 'elements': [{ 'id': '560e7ed2076e0d589e88bd74b6aad4b7' }], 'access_token': 'foobar', 'expires_in': 1800, 'linked': None }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_soundcloud.py0000644000175500017550000000313712754357263025056 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class SoundcloudOAuth2Test(OAuth2Test): backend_path = 'social.backends.soundcloud.SoundcloudOAuth2' user_data_url = 'https://api.soundcloud.com/me.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'website': None, 'myspace_name': None, 'public_favorites_count': 0, 'followings_count': 0, 'full_name': 'Foo Bar', 'id': 10101010, 'city': None, 'track_count': 0, 'playlist_count': 0, 'discogs_name': None, 'private_tracks_count': 0, 'followers_count': 0, 'online': True, 'username': 'foobar', 'description': None, 'subscriptions': [], 'kind': 'user', 'quota': { 'unlimited_upload_quota': False, 'upload_seconds_left': 7200, 'upload_seconds_used': 0 }, 'website_title': None, 'primary_email_confirmed': False, 'permalink_url': 'http://soundcloud.com/foobar', 'private_playlists_count': 0, 'permalink': 'foobar', 'upload_seconds_left': 7200, 'country': None, 'uri': 'https://api.soundcloud.com/users/10101010', 'avatar_url': 'https://a1.sndcdn.com/images/' 'default_avatar_large.png?ca77017', 'plan': 'Free' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_upwork.py0000644000175500017550000000277212754357263024232 0ustar debacledebacleimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class UpworkOAuth1Test(OAuth1Test): backend_path = 'social.backends.upwork.UpworkOAuth' user_data_url = 'https://www.upwork.com/api/auth/v1/info.json' expected_username = '10101010' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'info': { 'portrait_32_img': '', 'capacity': { 'buyer': 'no', 'affiliate_manager': 'no', 'provider': 'yes' }, 'company_url': '', 'has_agency': '1', 'portrait_50_img': '', 'portrait_100_img': '', 'location': { 'city': 'New York', 'state': '', 'country': 'USA' }, 'ref': '9755314', 'profile_url': 'https://www.upwork.com/users/~10101010' }, 'auth_user': { 'timezone': 'USA/New York', 'first_name': 'Foo', 'last_name': 'Bar', 'timezone_offset': '10000' }, 'server_time': '1111111111' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_google.py0000644000175500017550000002423512754357263024155 0ustar debacledebacleimport datetime import json from httpretty import HTTPretty from social.p3 import urlencode from social.actions import do_disconnect from social.tests.models import User from social.tests.backends.oauth import OAuth1Test, OAuth2Test from social.tests.backends.open_id import OpenIdTest, OpenIdConnectTestMixin class GoogleOAuth2Test(OAuth2Test): backend_path = 'social.backends.google.GoogleOAuth2' user_data_url = 'https://www.googleapis.com/plus/v1/people/me' expected_username = 'foo' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'aboutMe': 'About me text', 'cover': { 'coverInfo': { 'leftImageOffset': 0, 'topImageOffset': 0 }, 'coverPhoto': { 'height': 629, 'url': 'https://lh5.googleusercontent.com/-ui-GqpNh5Ms/' 'AAAAAAAAAAI/AAAAAAAAAZw/a7puhHMO_fg/photo.jpg', 'width': 940 }, 'layout': 'banner' }, 'displayName': 'Foo Bar', 'emails': [{ 'type': 'account', 'value': 'foo@bar.com' }], 'etag': '"e-tag string"', 'gender': 'male', 'id': '101010101010101010101', 'image': { 'url': 'https://lh5.googleusercontent.com/-ui-GqpNh5Ms/' 'AAAAAAAAAAI/AAAAAAAAAZw/a7puhHMO_fg/photo.jpg', }, 'isPlusUser': True, 'kind': 'plus#person', 'language': 'en', 'name': { 'familyName': 'Bar', 'givenName': 'Foo' }, 'objectType': 'person', 'occupation': 'Software developer', 'organizations': [{ 'name': 'Org name', 'primary': True, 'type': 'school' }], 'placesLived': [{ 'primary': True, 'value': 'Anyplace' }], 'url': 'https://plus.google.com/101010101010101010101', 'urls': [{ 'label': 'http://foobar.com', 'type': 'otherProfile', 'value': 'http://foobar.com', }], 'verified': False }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_with_unique_user_id(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_UNIQUE_USER_ID': True, }) self.do_login() class GoogleOAuth2DeprecatedAPITest(GoogleOAuth2Test): user_data_url = 'https://www.googleapis.com/oauth2/v1/userinfo' user_data_body = json.dumps({ 'family_name': 'Bar', 'name': 'Foo Bar', 'picture': 'https://lh5.googleusercontent.com/-ui-GqpNh5Ms/' 'AAAAAAAAAAI/AAAAAAAAAZw/a7puhHMO_fg/photo.jpg', 'locale': 'en', 'gender': 'male', 'email': 'foo@bar.com', 'birthday': '0000-01-22', 'link': 'https://plus.google.com/101010101010101010101', 'given_name': 'Foo', 'id': '101010101010101010101', 'verified_email': True }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_DEPRECATED_API': True }) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_DEPRECATED_API': True }) self.do_partial_pipeline() def test_with_unique_user_id(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_UNIQUE_USER_ID': True, 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_DEPRECATED_API': True }) self.do_login() class GoogleOAuth1Test(OAuth1Test): backend_path = 'social.backends.google.GoogleOAuth' user_data_url = 'https://www.googleapis.com/userinfo/email' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = urlencode({ 'email': 'foobar@gmail.com', 'isVerified': 'true', 'id': '101010101010101010101' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_with_unique_user_id(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH_USE_UNIQUE_USER_ID': True }) self.do_login() def test_with_anonymous_key_and_secret(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH_KEY': None, 'SOCIAL_AUTH_GOOGLE_OAUTH_SECRET': None }) self.do_login() JANRAIN_NONCE = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') class GoogleOpenIdTest(OpenIdTest): backend_path = 'social.backends.google.GoogleOpenId' expected_username = 'FooBar' discovery_body = ''.join([ '', '', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', 'https://www.google.com/accounts/o8/ud', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', 'https://www.google.com/accounts/o8/ud?source=mail', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', 'https://www.google.com/accounts/o8/ud?source=gmail.com', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', '', 'https://www.google.com/accounts/o8/ud?source=googlemail.com', '', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', 'https://www.google.com/accounts/o8/ud?source=profiles', '', '', '' ]) server_response = urlencode({ 'janrain_nonce': JANRAIN_NONCE, 'openid.assoc_handle': 'assoc-handle', 'openid.claimed_id': 'https://www.google.com/accounts/o8/id?' 'id=some-google-id', 'openid.ext1.mode': 'fetch_response', 'openid.ext1.type.email': 'http://axschema.org/contact/email', 'openid.ext1.type.first_name': 'http://axschema.org/namePerson/first', 'openid.ext1.type.last_name': 'http://axschema.org/namePerson/last', 'openid.ext1.type.old_email': 'http://schema.openid.net/contact/email', 'openid.ext1.value.email': 'foo@bar.com', 'openid.ext1.value.first_name': 'Foo', 'openid.ext1.value.last_name': 'Bar', 'openid.ext1.value.old_email': 'foo@bar.com', 'openid.identity': 'https://www.google.com/accounts/o8/id?' 'id=some-google-id', 'openid.mode': 'id_res', 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.ns.ext1': 'http://openid.net/srv/ax/1.0', 'openid.op_endpoint': 'https://www.google.com/accounts/o8/ud', 'openid.response_nonce': JANRAIN_NONCE + 'by95cT34vX7p9g', 'openid.return_to': 'http://myapp.com/complete/google/?' 'janrain_nonce=' + JANRAIN_NONCE, 'openid.sig': 'brT2kmu3eCzb1gQ1pbaXdnWioVM=', 'openid.signed': 'op_endpoint,claimed_id,identity,return_to,' 'response_nonce,assoc_handle,ns.ext1,ext1.mode,' 'ext1.type.old_email,ext1.value.old_email,' 'ext1.type.first_name,ext1.value.first_name,' 'ext1.type.last_name,ext1.value.last_name,' 'ext1.type.email,ext1.value.email' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class GoogleRevokeTokenTest(GoogleOAuth2Test): def test_revoke_token(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_REVOKE_TOKENS_ON_DISCONNECT': True }) self.do_login() user = User.get(self.expected_username) user.password = 'password' HTTPretty.register_uri(self._method(self.backend.REVOKE_TOKEN_METHOD), self.backend.REVOKE_TOKEN_URL, status=200) do_disconnect(self.backend, user) class GoogleOpenIdConnectTest(OpenIdConnectTestMixin, GoogleOAuth2Test): backend_path = 'social.backends.google.GoogleOpenIdConnect' user_data_url = \ 'https://www.googleapis.com/plus/v1/people/me/openIdConnect' issuer = "accounts.google.com" python-social-auth-0.2.21/social/tests/backends/test_disqus.py0000644000175500017550000000416612754357263024212 0ustar debacledebacleimport json from social.tests.backends.oauth import OAuth2Test class DisqusOAuth2Test(OAuth2Test): backend_path = 'social.backends.disqus.DisqusOAuth2' user_data_url = 'https://disqus.com/api/3.0/users/details.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'code': 0, 'response': { 'username': 'foobar', 'numFollowers': 0, 'isFollowing': False, 'numFollowing': 0, 'name': 'Foo Bar', 'numPosts': 0, 'url': '', 'isAnonymous': False, 'rep': 1.231755, 'about': '', 'isFollowedBy': False, 'connections': {}, 'emailHash': '5280f14cedf530b544aecc31fcfe0240', 'reputation': 1.231755, 'avatar': { 'small': { 'permalink': 'https://disqus.com/api/users/avatars/' 'foobar.jpg', 'cache': 'https://securecdn.disqus.com/uploads/' 'users/453/4556/avatar32.jpg?1285535379' }, 'isCustom': False, 'permalink': 'https://disqus.com/api/users/avatars/foobar.jpg', 'cache': 'https://securecdn.disqus.com/uploads/users/453/' '4556/avatar92.jpg?1285535379', 'large': { 'permalink': 'https://disqus.com/api/users/avatars/' 'foobar.jpg', 'cache': 'https://securecdn.disqus.com/uploads/users/' '453/4556/avatar92.jpg?1285535379' } }, 'profileUrl': 'http://disqus.com/foobar/', 'numLikesReceived': 0, 'isPrimary': True, 'joinedAt': '2010-09-26T21:09:39', 'id': '1010101', 'location': '' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/test_evernote.py0000644000175500017550000000316712754357263024531 0ustar debacledebaclefrom requests import HTTPError from social.p3 import urlencode from social.exceptions import AuthCanceled from social.tests.backends.oauth import OAuth1Test class EvernoteOAuth1Test(OAuth1Test): backend_path = 'social.backends.evernote.EvernoteOAuth' expected_username = '101010' access_token_body = urlencode({ 'edam_webApiUrlPrefix': 'https://sandbox.evernote.com/shard/s1/', 'edam_shard': 's1', 'oauth_token': 'foobar', 'edam_expires': '1395118279645', 'edam_userId': '101010', 'edam_noteStoreUrl': 'https://sandbox.evernote.com/shard/s1/notestore' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class EvernoteOAuth1CanceledTest(EvernoteOAuth1Test): access_token_status = 401 def test_login(self): with self.assertRaises(AuthCanceled) as cm: self.do_login() self.assertTrue(cm.exception.response is not None) def test_partial_pipeline(self): with self.assertRaises(AuthCanceled) as cm: self.do_partial_pipeline() self.assertTrue(cm.exception.response is not None) class EvernoteOAuth1ErrorTest(EvernoteOAuth1Test): access_token_status = 500 def test_login(self): with self.assertRaises(HTTPError): self.do_login() def test_partial_pipeline(self): with self.assertRaises(HTTPError): self.do_partial_pipeline() python-social-auth-0.2.21/social/tests/backends/data/0000755000175500017550000000000012754357263022173 5ustar debacledebaclepython-social-auth-0.2.21/social/tests/backends/data/saml_config.json0000644000175500017550000001002112754357263025341 0ustar debacledebacle{ "SOCIAL_AUTH_SAML_SP_ENTITY_ID": "https://github.com/omab/python-social-auth/saml-test", "SOCIAL_AUTH_SAML_SP_PUBLIC_CERT": "MIICsDCCAhmgAwIBAgIJAO7BwdjDZcUWMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNBMRkwFwYDVQQIExBCcml0aXNoIENvbHVtYmlhMRswGQYDVQQKExJweXRob24tc29jaWFsLWF1dGgwHhcNMTUwNTA4MDc1ODQ2WhcNMjUwNTA3MDc1ODQ2WjBFMQswCQYDVQQGEwJDQTEZMBcGA1UECBMQQnJpdGlzaCBDb2x1bWJpYTEbMBkGA1UEChMScHl0aG9uLXNvY2lhbC1hdXRoMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq3g1Cl+3uR5vCnN4HbgjTg+m3nHhteEMyb++ycZYre2bxUfsshER6x33l23tHckRYwm7MdBbrp3LrVoiOCdPblTml1IhEPTCwKMhBKvvWqTvgfcSSnRzAWkLlQYSusayyZK4n9qcYkV5MFni1rbjx+Mr5aOEmb5u33amMKLwSTwIDAQABo4GnMIGkMB0GA1UdDgQWBBRRiBR6zS66fKVokp0yJHbgv3RYmjB1BgNVHSMEbjBsgBRRiBR6zS66fKVokp0yJHbgv3RYmqFJpEcwRTELMAkGA1UEBhMCQ0ExGTAXBgNVBAgTEEJyaXRpc2ggQ29sdW1iaWExGzAZBgNVBAoTEnB5dGhvbi1zb2NpYWwtYXV0aIIJAO7BwdjDZcUWMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJwsMU3YSaybVjuJ8US0fUhlPOlM40QFCGL4vB3TEbb24Mq8HrjUwrU0JFPGls9a2OYzN2B3e35NorMuxs+grGtr2yP6LvuX+nV6A93wb4ooGHoGfC7VLlyxSSns937SS5R1pzQ4gWzZma2KGWKICWph5zQ0ARVhL63967mGLmoI=", "SOCIAL_AUTH_SAML_SP_PRIVATE_KEY": "MIICXgIBAAKBgQCq3g1Cl+3uR5vCnN4HbgjTg+m3nHhteEMyb++ycZYre2bxUfsshER6x33l23tHckRYwm7MdBbrp3LrVoiOCdPblTml1IhEPTCwKMhBKvvWqTvgfcSSnRzAWkLlQYSusayyZK4n9qcYkV5MFni1rbjx+Mr5aOEmb5u33amMKLwSTwIDAQABAoGBAIHAg6NJSiYC/NYpVzWfKlasuoNy78R5adXYSNZiCR5V5FNm5OzmODZgXUt6g0A7FomshIT/txQWoV7y5FmwPs8n13JY3Hdt4tJ6MHw2feLo710+OEp9VBQus3JsB2F8ONYrGvs00hPPL7h5av/rzTdE8F67YM1mSgeg7xEF6BghAkEA12OOqSzp2MLTNY7PqOaLDzy4aAMVNN3Ntv2jBN0jq7s1b5ilQ2PGkLwdtkicq/VZcRyUqVbZbMwz05II3nqx3wJBAMsVhRQ5sdFCRBzEbSAm2YEJaFh5u6QT3+zWHMFpPJRnaBAWz3RXKEnleJ+DS2Xz1Jm6ZrmLdZiwMx/8dK5rDZECQQC7GTdWi7ZC3dIcpwaKIGHRhZxmda8ZMkc9Wwwd8H7I8aFUZFPCu0xEc7SXoHHACit8zyfwBYpvMN8gPK3JnOkfAkEAsUSpk0wBMT38one7IZOHzCDgGkq4RbKrhdon45Pus0PIDDM9BrqFimtpbSN4DxhVfZK91DwtfAhhuAvv9cewYQJAPMhpAqv3PBGYmtRDUlWXJQv2JRJJkrvbbqgBed2OX5RRgj5V3SR6PBhLbcTZ+q+1tdPkMFzZo5U6MN5m/6oXvQ==", "SOCIAL_AUTH_SAML_ORG_INFO": { "en-US": {"name": "psa", "displayname": "PSA", "url": "https://github.com/omab/python-social-auth/"} }, "SOCIAL_AUTH_SAML_TECHNICAL_CONTACT": {"givenName": "Tech Gal", "emailAddress": "technical@example.com"}, "SOCIAL_AUTH_SAML_SUPPORT_CONTACT": {"givenName": "Support Guy", "emailAddress": "support@example.com"}, "SOCIAL_AUTH_SAML_ENABLED_IDPS": { "testshib": { "entity_id": "https://idp.testshib.org/idp/shibboleth", "url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", "x509cert": "MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzEVMBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMREwDwYDVQQKEwhUZXN0U2hpYjEZMBcGA1UEAxMQaWRwLnRlc3RzaGliLm9yZzAeFw0wNjA4MzAyMTEyMjVaFw0xNjA4MjcyMTEyMjVaMGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQIEwxQZW5uc3lsdmFuaWExEzARBgNVBAcTClBpdHRzYnVyZ2gxETAPBgNVBAoTCFRlc3RTaGliMRkwFwYDVQQDExBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArYkCGuTmJp9eAOSGHwRJo1SNatB5ZOKqDM9ysg7CyVTDClcpu93gSP10nH4gkCZOlnESNgttg0r+MqL8tfJC6ybddEFB3YBo8PZajKSe3OQ01Ow3yT4I+Wdg1tsTpSge9gEz7SrC07EkYmHuPtd71CHiUaCWDv+xVfUQX0aTNPFmDixzUjoYzbGDrtAyCqA8f9CN2txIfJnpHE6q6CmKcoLADS4UrNPlhHSzd614kR/JYiks0K4kbRqCQF0Dv0P5Di+rEfefC6glV8ysC8dB5/9nb0yh/ojRuJGmgMWHgWk6h0ihjihqiu4jACovUZ7vVOCgSE5Ipn7OIwqd93zp2wIDAQABo4HEMIHBMB0GA1UdDgQWBBSsBQ869nh83KqZr5jArr4/7b+QazCBkQYDVR0jBIGJMIGGgBSsBQ869nh83KqZr5jArr4/7b+Qa6FrpGkwZzELMAkGA1UEBhMCVVMxFTATBgNVBAgTDFBlbm5zeWx2YW5pYTETMBEGA1UEBxMKUGl0dHNidXJnaDERMA8GA1UEChMIVGVzdFNoaWIxGTAXBgNVBAMTEGlkcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAjR29PhrCbk8qLN5MFfSVk98t3CT9jHZoYxd8QMRLI4j7iYQxXiGJTT1FXs1nd4Rha9un+LqTfeMMYqISdDDI6tv8iNpkOAvZZUosVkUo93pv1T0RPz35hcHHYq2yee59HJOco2bFlcsH8JBXRSRrJ3Q7Eut+z9uo80JdGNJ4/SJy5UorZ8KazGj16lfJhOBXldgrhppQBb0Nq6HKHguqmwRfJ+WkxemZXzhediAjGeka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr8K/qhmFT2nIQi538n6rVYLeWj8Bbnl+ev0peYzxFyF5sQA==" }, "other": { "entity_id": "https://unused.saml.example.com", "url": "https://unused.saml.example.com/SAML2/Redirect/SSO" } } } python-social-auth-0.2.21/social/tests/backends/data/saml_response.txt0000644000175500017550000004212712754357263025614 0ustar debacledebaclehttp://myapp.com/?RelayState=testshib&SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL215YXBwLmNvbSIgSUQ9Il8yNTk2NTFlOTY3ZGIwOGZjYTQ4MjdkODI3YWY1M2RkMCIgSW5SZXNwb25zZVRvPSJURVNUX0lEIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMDUtMDlUMDM6NTc6NDMuNzkyWiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVudGl0eSI%2BaHR0cHM6Ly9pZHAudGVzdHNoaWIub3JnL2lkcC9zaGliYm9sZXRoPC9zYW1sMjpJc3N1ZXI%2BPHNhbWwycDpTdGF0dXM%2BPHNhbWwycDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWwycDpTdGF0dXM%2BPHNhbWwyOkVuY3J5cHRlZEFzc2VydGlvbiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI%2BPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIElkPSJfMGM0NzYzNzIyOWFkNmEzMTY1OGU0MDc2ZDNlYzBmNmQiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNhZXMxMjgtY2JjIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiLz48ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI%2BPHhlbmM6RW5jcnlwdGVkS2V5IElkPSJfYjZmNmU2YWZjMzYyNGI3NmM1N2JmOWZhODA5YzAzNmMiIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiLz48L3hlbmM6RW5jcnlwdGlvbk1ldGhvZD48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE%2BPGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDc0RDQ0FobWdBd0lCQWdJSkFPN0J3ZGpEWmNVV01BMEdDU3FHU0liM0RRRUJCUVVBTUVVeEN6QUpCZ05WQkFZVEFrTkJNUmt3CkZ3WURWUVFJRXhCQ2NtbDBhWE5vSUVOdmJIVnRZbWxoTVJzd0dRWURWUVFLRXhKd2VYUm9iMjR0YzI5amFXRnNMV0YxZEdnd0hoY04KTVRVd05UQTRNRGMxT0RRMldoY05NalV3TlRBM01EYzFPRFEyV2pCRk1Rc3dDUVlEVlFRR0V3SkRRVEVaTUJjR0ExVUVDQk1RUW5KcApkR2x6YUNCRGIyeDFiV0pwWVRFYk1Ca0dBMVVFQ2hNU2NIbDBhRzl1TFhOdlkybGhiQzFoZFhSb01JR2ZNQTBHQ1NxR1NJYjNEUUVCCkFRVUFBNEdOQURDQmlRS0JnUUNxM2cxQ2wrM3VSNXZDbk40SGJnalRnK20zbkhodGVFTXliKyt5Y1pZcmUyYnhVZnNzaEVSNngzM2wKMjN0SGNrUll3bTdNZEJicnAzTHJWb2lPQ2RQYmxUbWwxSWhFUFRDd0tNaEJLdnZXcVR2Z2ZjU1NuUnpBV2tMbFFZU3VzYXl5Wks0bgo5cWNZa1Y1TUZuaTFyYmp4K01yNWFPRW1iNXUzM2FtTUtMd1NUd0lEQVFBQm80R25NSUdrTUIwR0ExVWREZ1FXQkJSUmlCUjZ6UzY2CmZLVm9rcDB5SkhiZ3YzUlltakIxQmdOVkhTTUViakJzZ0JSUmlCUjZ6UzY2ZktWb2twMHlKSGJndjNSWW1xRkpwRWN3UlRFTE1Ba0cKQTFVRUJoTUNRMEV4R1RBWEJnTlZCQWdURUVKeWFYUnBjMmdnUTI5c2RXMWlhV0V4R3pBWkJnTlZCQW9URW5CNWRHaHZiaTF6YjJOcApZV3d0WVhWMGFJSUpBTzdCd2RqRFpjVVdNQXdHQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ1lFQUp3c01VM1lTCmF5YlZqdUo4VVMwZlVobFBPbE00MFFGQ0dMNHZCM1RFYmIyNE1xOEhyalV3clUwSkZQR2xzOWEyT1l6TjJCM2UzNU5vck11eHMrZ3IKR3RyMnlQNkx2dVgrblY2QTkzd2I0b29HSG9HZkM3VkxseXhTU25zOTM3U1M1UjFwelE0Z1d6Wm1hMktHV0tJQ1dwaDV6UTBBUlZoTAo2Mzk2N21HTG1vST08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6Q2lwaGVyVmFsdWU%2BTElQdkVNVUVGeXhrVHowQ2N4QVA5TjV4Y3NYT2V4aVV4cXBvR2VIeVFMV0R5RVBBUDVnZ1daL3NLZ1ViL2xWSk92bCtuQXhSdVhXUlc5dGxSWWx3R2orRVhIOWhIbmdEY1BWMDNqSUJMQnFJbElBL1RmMGw4cVliOHFKRy9ZM0RTS2RQNkwvUURtYXBtTXpFM29YOEJxMW5Ea3YrUWh4cmQwMGVGK2ZMYVQ0PTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6Q2lwaGVyVmFsdWU%2BRVpUWDhHTkM0My9yWStTUVlBMXRudHlUTTVVNkN2dUNCaktsVEVlekZPRjBZZHhCWUdFQVVjYU8xNVNKOXBMemJ1L1h0WGxzTkVMZTdKdEx4RUpwYUxubWFENnIranNWczdLaTBLNHRTMGNBUERDWHV2R1FoMmFOVjVQOGJ3N1JWUGhLOGQwYlJ1RklGR09FOHMwTTZYOUpxWDN4S0MvL1lSbVVoeDlybnU3ZWlwMGh5ZitPaUZiVGR2SDY2NTB2LzQ3aVdKcDNZeFlUV0QyMHBNbVRJMUpwWUEwYjByWVFQRkR0RU93d0JxYktxanRJc3ZYVFJzeXJhQkxvbnFOeHN5dHpEWHEra0JsMXp3WGUvSE5QcUVQblczdnNxaFhZcDVGM3dkWThkKzNCOTRMZlpOdUd4a0p3VDNzdVR0OGY5VHRBSlI4VytBUmtzT2M4eDBVaVNsVG5BNHFHOTBLMTR5dkVoVHcvd2drZjFXV01RT3dpZDNpakFYbUV4MU5MbVZvYUxYb3p4VExkTjN6YnJ6VEJIRXc3R2J3ZEdrdU5pMlhZOW16YUgwaWtGRm51VUxjMHUwc0pycEdGdzlaK0VlUk44RzNVUVZ5MjhtS2g3ZFBwWU5KbzhyajIxZFFaK2JaeUtTUHZablU3REkyakdJRE5US1g2ZkVyVWFINGlOTzN4cUU2Vk90L2d4T3BMNE5VNUhLV0Q0bG93VzcwdUJjVEVQRmhwaThpYUovdTB6YzUvTEhvdVBjMzByc1RLZFc5cmJLL2NWaHNQUHErZzA5WHZpZ0QweTJvN2tOc1pVL25tRXFiSzBKOTBrazhCR3I5cXRSczY4bUJnSURtUHVwUkhwWjM4eXNnU2VZN3V0VlVaSG5tQ0dzTzZ2NDJ6OTVOK05Pb3RCTEVZbFd1ZEdzYnowQWc4VkRDSlY5ak95QW95MDZyL1AyUHBsOFhjdmJza2d2T1BMMWdDNnVYbVJJS1lmOEw4UDJCNXVjN0haK0dtUHNOWXRLS2VKRDFFUHovdCt2NlBIbXNVb3dsSDhSd3FMRHdtMUF4dlNLQTR3UXBlQ0dQd3A5YXRYS0lWMS84NUZzRWMzajVzNjd6VlRybThrVEpydXV2MDZEdFVRZDNMOFdwTkV4cWhQait6RUp6U3RxSG04ckhNMVhNQUVxdVozc0xycTVqLzFSNlpqS0dOdFJCbjhwOE5ERGtrWm0vWTV5TXlJNXJJS3U5bnA3bXdaaEVpeWVHeHdxblV3VVMvUzVDRjNnMHVidnd4eVVnalVvd1ZvTkNqYktBbkdtT2VCSW5abkh0eGdIVUhVOUVlTFdyd2pRc3JtUmpJV0R2RkZQa3l6SzJDL20yaitubmNxc2E1OGRLVXZxcGR1VTRJYnNPQng3UGpXdXRBNmY5bXd6YWxyRU1NK0lGR3VPdk9HMC93eUdzQjZLREV6bldjUC83NkQ4angzaHZFSlAzN3REbFgreGM4Qno5TXdKdkd6VG4xbTdCb2xoR0lzSXlCTys1ZXpXa3RDWVVIUURGVE9wbXA0MDlOWHp6ZUNTUGY1U2NDWG5YYjRPd01ULy9VM1JFUnRRbGMrNmU2WG1JRjhoRkJVc0taUUJsS2ppSDkwZHlzYWlsNmN2V3UyQW55Q3QxbWxXcHFLc0MzU2RTRVZDTG1qRjlUQUFUMEtFSGdZQjg3RjZtZUpTTysvOXkyZkRuYVVvUUlUVzdubnVuSCtkT3dWSGZMU0wyL2N5YTltNlQzR29TSVNMbGJPMVRzalhKclVkZW55OTcvM2tkNmhFQlphdGY1U3NETFQ3SjNsQUVJNDROeXJ0NkIxQWdod2JNdkpqd1JNTXRNdUJLc3ltUytKVzc4UFNEWXQ4MG9waDJQTTc1N0tBNCtUMTAvYnZaQkE5Vk1OdVpqNVV3NXRWMnFIS3dwS0t6ZVVETUFiQlBRaGpYcXlQZzFKa09rd2RQMUpnOHRITjJTelBZQTlmT1htV0pBZGJDS2tMb0F4ZTV6cDZBUzYzS3FXMmFmSUt6SHJ3RTJmS1VtamppeURvMnNuMkJHbWtBaTRzbnpiVzc2SUQvSVgwd044aDBaQ2VRc29vKzdtb1RCMEJxSnBkS1MycXlsUktoc3BSTC9henVQdmxaK1pwckJxdXpJdEZkNFVLMkpzQkp6VXcwZkpxcTV1bk9PZENzVWM3SUU3QTNmZ1NmZ3NBd1R3WFZJMEVoME5ySWZpMkFKV1Z2VFpEMys2eFZ3dS96WWhuVjc0VXkvMFE4Mi8yQWtpSGpFRjNJVGNLWHdTNTB6bWtLakxjZDJqa2h5TUFYMWRoQ0wwZElFMUJoN0RNamVvNC9YbjBqSlpPL3Rrbi9xZmYzc3RNb1BYVG9KTnBIU1RjR2ZheGtaMzJYNCt3Q0xPc0VBRWxlMVZSY0kwUkZyOFhHTSsxWU9BTjBodFdGcFMxaG9kSi9OczJqL1FnUVNEemNpQ1FZeUFDd3lFRWZDZjZybnR0VmJyTlJQZWlmSHhBM3B2UnZ5ZGRhNDE5cXl0ZXI0akJ3cmw3ZUpuVnJ2VEprR2VhU2FRbDdXWk5SQXBscXRnNnZPYmpiMHZDRWlFaFhKbmNzQUhxcXp5QTRGeWFUVGQ2R0FySU9adUNxRWVoWk51T01lOVlrMVpya0VkR3pIalJESWk3Q1BKQk12NEZ4ZHI3bnJvN0I1WEhKb0ZMNE1DSUtOWWU2aWZiTUtYOU5uN1FWdnphUmY2UXlaSW1BWENQZndvU1BkN2x6NXl3UDJLSUIyaGhFMWt5eVZ5YVc5T0praWpUY3dvUnZrSXhIU0RqMXFqeGxueXh0QzhVZ1pNWmlwcGgzQXJpcjRiekIzUDhIbGIzejZ0OW51KzZMemNiN2ZObVo0UHluaU50Vk9OQ0lHbEh4dTBSY3hQK3cwUXNsM1BtTzJLaHBpc2RIanhvSUJ1YVY1NXdoTlFFNmdNNFBrT0xINDc4Rzg4bUxkd2s2RFpkWVl4L2d6RWE3b3ZIL0pReFp2TzRLdFVTNmZjZHJxV2thTFg1cEhkNkdneFBGZ2NFc2Nad1ZqM2hCS0xFQmE5L0dodERINEhzRnNRbmpPZnNDQkNzN0tjRitmTi9oSUdUeHFqTVlKVHJRYmNtdWF5dk9xR3RQMDFPcXltR24rVm5FSVkzKytQcm95SFN3K0Q0b0JIVG1maFNXRmJLZCtuTlVFS3BhRVIxNkdCU256WktQRVRVSmdRWEw5QWJRQ3RXVjFHb0UzRWNnMDZYaVd2aHFHakpGNldtdEU4dHY4Q25rZmxMNm91TDRvNldpbmx2WnNEdkZrS0R6TDkwUTNsWC9NanBtRTFpWU9uYzdISXdEVGwraFRRcHdsYXJiTDVUNGNkZTg1akNwYU0xU3p1TStiQU5zMHlXVDA0ZXJUVFc2cnhlbXFDTHAra202TVVMTlZOcE1CazBiQjJpRU82UlRtc3VpRlhDUU1xdU5xZjdkWXUwTFFCZzQ0MkJzU1pBV1ZrWEVZblduOURLdTRSby8veEFsb2h5VHozWlZmSkhuWVBSdDloSUErRHVUL3c4T2ZzTURIWnlCelUvL0JEa1NiNkxjMHdraVA3QlhIdjBoNVdud2dNWUxlZDBPalR5UWI2aGxpVnQ5b0FjaDRFVy9EZUlBdkpaQ1BYVm1pUFFYTGVsOVJIRko2bXFiYVo0TCtaZG1ONmQwcFZNZ1FveXhmQTR3dEwwYVpiNnFZYkhibjJMd2VBQVZwL3M2TzVlMVExdnZpZDRTWHo0a2l3RW1LSStIeXZEQ1pnekpQQVN5Z1gvWDJFWEZ0NGV3SjVmUFQyVXZmWnhQWlpqMFZGSFpyUFQwWVd2VE16bjUva3hoT09oM2drVGdDSmNwNWVsZnp4cEFPNFl1a0NoNHJXdVNndDRqVUJyaWNYbFdWdWo5U3JSZVhUalNHTktLK202NWovUDllNHRHT0RkMk9BbjNKTVQ3Q3FuaDhreTZpZjVjbmpVMmU3UDhTZnBONGwxWEFiZEZEcGk5bVJYamEyTzR1RWFHNGNvNW4xcWNDT3ZNMWYyblFBY1ZGNUFoSXhueS96TWhmU2l2RXdOQ0Zyd2tBWDRyQVE0WldUNldFakFyUG5jb1Y4Z1VRclhxQVA4NDJmK1lNWWI5RHFncmFicEg1a3ZuMnQzcWRldGJHODJ0QWlTamhPcUxNYW9iU2F4cXdWa1lUOHRTMW9rUUt2MWZoZ2t6elpEOE5IQnVQQzdNVHdXS0VCS2tDRUUzRWRFMXhNQURLd1B1M3NSaGpSaExXZyszZ2srejJtdlU4cTBhTlc0Y3hObUdoekx4eEY0Q3NFNStMQ1cwOWFpUVJOM1VvWmg1aktBZzBiMlh3WHBLS3pycUVTY1BYdnI0L1dWUTMyMm5qRWRvQVdXR0t2WnBKMlRlREo0eDdiT21LVElFc2RHWU1UZzFVaEU2eFFQcnhqS3dWeGFJNVJyaVE4a0xpaGgwa0t0WHQvYTVsSDhzUjVwR0ZISGZ3dlNVb3liQTB1eUVDNnNRVitPbTVReUZmRmpqZHFCOGNpOGxQS1hLTHFCTHJ6bjNmUkh3TmQwbzFiRTg0aGllTkx5UlhZVmhrRCtFNEpGaVd3ZWt3U3VWM3BjQk9ybnRVU3RoWmx6M3hIUURUVGNJNWliOFJyQ2swZEZ6YTgvQmw3VUdtWlUwSXZ2UmdvVXF2TXNHT2dMY3pGWmRpZnJ5aGNiUTY4a2ZzZ3lCMHppdC9MN1BSV3V4RkdYdDFoTVZSVUZ3WXBJS04zVkI3cXVKZlgwamZsU1JaRndMaXdlK3VhYndmTVZ6c2doajUvOXZNNzcwK0JaMGtJcE45NzBTMG5BbHl6R0h0aW1nTUl1RXFhbUt5QTNTQlI1aHZIYmRyNENnTHFUbXIzbFFnWmpnSkNvN1FXYUJWTXdCR0RpdzVOVVhUUnBycWc4U3h2eDlnNWZwbXMrL0o2QjFEelNTM3ZRZzgxdHFRU1ZDWVJpc0Y3M2VqZlFuZk4zcUszd3RJRDkxQnRISmFvMEFaUUdKVFpKOXVsZ0kzV3hzdWR4ejB0VHVpNlJlSWpmSWsxekZRdFpwRExGMnB3NGpTQVdQTlJqNDBYdVIrRzFUVlI3OVFiME9FYkw4RDFoTU5zWmo3MTZNbUhSOTlKaUxNdm1FWHV5a1V4VGhGYjRMTzZVbW1kU3UwTlBpMXQ2NmNkYURpQWhMaVBFTGdUNkZsenA2T2FGSGNSNjRncEtyemtTNDJONEhJeFpNa2R6M0FsYkRhK2pOWHZPR1l3UWl5K0xNNENZWGtrTWtHR3ZTWis5R2xWQ0l5RXBJaXIzbEQ3bmdzZGk4emxGWDYvekNaczlQSUtwZFZlSGJGZi9GS20wV3AreHI0Ykd0R0RrVHR2Nk1Manh2YU8zanFHaUFWeERKVWFkTVBlS2VHSm5uempTdnpKbGdOVHV3c3grRnF5L2dPMkwxMGowWmhDWi92dE9NelVjNjl3cGhKZm9FNzU3V3lOeFJOcThJc0Y1Tkg5Y0x0b3UvbUNxOTc3YnZPSkRrSURCN3lKWEJ6YUhVQkJuSXJra1Qyemg3bGJmUm5SREJUSFZraVZMazVESUxqeC9XL1BSZEZpUUM2SzRmZGx4Y29JbzlMcnM4ZFVWZkt2TTNNYnJ6c1hGT3ZtVVh0K3NsZldvd3UyTC9ndG9mRFhvTUJZZnlEcWIvWlRaRWZ0MC83blliRm1relBEUlZacU5SR0F3YWZVNTU1UjB2SWtNbGR2VjdKUzhNT1BNYWlXQVBpelNLRG4yRzNvcys1MzRFQytaOGZnWmFPVWpZL0xLME9vME9RMmhvNUV6MGNMYWpwUjFINk9FNEhvUm1ydjQzZkFjdGpYc0hYdi81RXg3emdrWk1NZXZhTFNEdjZtcjFGcDk4QXR4L296VTFGVDBoMDUxcVcwR0g2VWpRRXk5aExSZDBBMnFkUTRMZXpReDNvbDFTblhsamt2MG4zTXFlaFozOC94bzZhdHFDdkJtQkc3amlUdXd6YnlVUngzRm1TM0NCNllOYnFON3hPYVRZRnlkOEZDL01nY0xGQmMwS3F4MXllQ2VUd1hucldQb0dvdllVQlYxYjA1cWtIa1d5V0RUaCsveXJFNzF0RjNxbUQvd3F6cUJyNE04NERtWWVuQkdFOWxtb3FIZEMyWnRpK09KVFZKcmlHZWxQQ3RjZnZRaUlQcHdDZ3BFNmg1ekZhRndLajRuZGtBUkRpTC95L1EwWTZxNU5rM1g5RURlTmdjY1pIcFdmOUpKQ3M2a29wdXRtYjdDczIrbVJYdER1S09DaGY5UVUyN3Bmb1NJaklYK3NGdHY1c0hhSms2aHBZMlpzUUhzaTBYbFowc3FMTnQ5ayszdTVnYnBSU1JCczlHaC9BaVY0dkNyYTRkOTh5U0dCdzRSR1FhSStpQ29RaG9YK3lxc3VrYkx6bXJUU3FXMVRXaXJReUlHZ1Q5VnFERE1mUzAxeGdQSlNFSTlIWlp6TGlFVXVGMm1CMi81Y2dqaEFUaWQrdGV1UVB4aldhN2NSc2t5YUhuTENjQURVUU9ESUFPVjJDWXROcnAwY29ZL091S3ZzaXlJT0lacVJ5dE1PMGVNZ1ZJWTBzWmdxeVEycXlubUx0NDBmWmd3SFVyV245Zm9TYTNtMkVRTy9uOS8yU2NuelJWdVZpVnNjM0tCSElQL3AzNlJlSWowTGlNcCtPQ0p3SHlLVW1UeDRBU1V0dXVhWktlRHl1QjlxcXJuUEFNWUVCeElsTGFvdXMzV1pHakIrcW9ub3QvNmk1UE40bUZjbHFDcUxhMGJHbks4ZnJxYy9yd2tuVGV0YUE0c2tXTEw1L21qNEd5MitFQkh3a0x3UXd2K0FKdmZTOXYvNDl1LzY0N1ZFYW15UzdZQ2ZEUHNBQUREQ1FFcWJNQ1h2Ui8xVmEwWi9YUWhoNlkrZUt0MEVpRDdpNmRZODJtQkFoNEJMRmRVV3VGZHVrdUVwaGZ2WXB3N2loVjNxTjB1NFM1NTRXU0dUa0ZsdlpYNG1hbkF4a1g2ekQxS0NWaEFMdEJnSDgzdkhxam9uc0lwOFMydHgwZ0tiYzEreHVaRVppVWlNVVlVdTByQVFsRFcrZHJoN3lVRHZqekFHSnBmTk01eThaMW45em93VzZ5YW5VZWFBNjhSZDd5TUxobFd0NVh6bGhBTVZDZmZYZ0pFelR1YzJEbENVOXNMLzVTVkRaV2N4R1E5aFM1cnJtK2VyQ1Jxd2FJQk1DNUtza0RCZHdOWmh2Q0FCdEpqS2Vla1FUSjd5MFp4SGNhbGVCaU1rbkYwZVRDZzFvUEhPUVZLQ3V3NE94cHRZUS9xS1V0TEFIWFZ2OTlLMGRWcWZDMmpVQWlHQmVYa0t3aGRYTGtJYlZxU0EyZmxraXBBeEhYNnByUEExQjF3eTVab3hPUFg4RVExOW92eXpBbFg1dHU0OXEwWC9PSExFN1o5T1cxenltRXR6ZFpyNXJZbWtFcVdtcHVSNU5jeHFwTWlZam93dUNXZWhubzIyeG5JM09IQ0xDZkFKaHRrcklhL1hPc0tZRFpCRzFJMGJsN2taR2R5cEtUQlhYdXl6WE5WUlU5L005ejhaVytwdG1oZ2NOUzBJS2VaaSs5bFl4cWRlS3lnbldTTTV3czdSYUpmNlRRZTNSaWJZUjFvNkhwRzB2VHpiTEtQZTZnRjJGODdiWlBJei9mcTNLWnZiM3UrSnhZcCtJVjBtQi9VN29YelhRRk1RK3VmWllpNzUxbkx6WlVxRE1ybU53TFJPVUFNUk8rVnJtblkwSVB1cFBVMXc0b0hBb1dnVGRnTk5pNk1uTFQ4V0pmUlhjT0pKMk1lbUc2K2ZNeHNZUU52UVJwa1RGY05vaFV6Y3ZjcHJ3NUV3WEVZQTJzbzczL2MvY3RIRGcreU05YlF4REppUlltRnFydkhYb29hS1JyekxnUjZLVWdoM3ltaWxaQ0lSSm9KbTE3aEtHM1pxTTE0Lzl5OUc5OE9BZjNkVTlqMDk3aUNlaEc3a2VxYXRJQ2hFWmJqbmQ4Y00rS3djN2FtVWp2ekQzQmNvMHl3MDJxT054OWF3OGhSblZiWDZhdkRJbGhySHZ6SU44MzFvUjljRHBwMG1DUEJXZFVDQlNqVGJ1RkZqRC90WElSbGxlT2JraFFKSUdSNlE2U1MxcXkzT29WT1VheFl6THY0U2s3dndrQUMwUitGREVIeVFZbFVhbVVkTWcyUmdwRUdhSVd1V3IxaGNnRm10QmREV2g3ZFBuWTF0U3VKOC95MXp4NkRvN2ZJYmNFenBBK2E0ODNtRG5vemdld3VmaFdqVCsvUS85WlEreFQ5UWJBT1pQSXhHV3VhSXVrVk8zSWxvZDhJM1NGZFJCTHY5ZXBDNzFLeXpSdVlpMktkOHJ5NVNINit1WnMxUHlZUlpRakdDK3Q4VzRtSE82Z1lFRWVXSkJ1UWhnSHdmV2xhZXlWb3hac0NBQVZKRUllT3hPZDZtNW45OHRCUDdHTmgxT1M0eDRCS2FVN1A0UVQzNVVIZW5meE84WWFQUThmbXlobUJhSVJVZklBTVN2ZTJZRFp5SWNNTTkrN0tNSVVabzJ0eXRvYzdCOGVvZzBNaUkrVkpFdFg0c29FRjFSWkhQZVV3NWlCTjI4OTh2MmVTcGNnVUJhWHFzOUN5VlZtTVJQMEtLUDJ1REt4MUdJcUhjS0ZCOXVQVWRkQS9vT3dNa0tVUWsraFZVVDVPbEVMdjd1a0FBUEE0eE4rZkczVmYxeUVKV0FiVGx5dWtGcThjNXBTRkY1cXVHbUgwVmVpQzVvVEFka1VES3Z6WGhWWUs5c3BRYjNVZ1Z0Qld6N1ZScnlOUVVST3BIZU5xeDlhZHA4YWREWCtRSHJUKytYblN4VVI3SVdGanlNTkZJRWlMWmkxdks1UVVrZlRDUU9qdjh2SHdiUi9MRHF3Z3M5bXdsT3pPY0RLdVBVK0dTb2lnVFdRejRWN0N2SHRaVDI3WUdKVG44RFFFM3IzdjB4aWxvODJ2U3VXSDg0WEU3VEJsTUpFb2R5eDNDRngwVUVkc3VhRHBPSEV3UjZYNlUyU0xseERYSXVZeEhlNXh2NjI4bXU0bDRMSnBYUjhkYmljTEZKQW55Q0FVeDJLb2dDamt1cmU4bXNUZktDbG8wamFlN1hNR05PSk15b0ZYbVlHZUh2eGhNUGMzTEtYLy9VY1p0c3p3dFJrQmNFdURXQysvQWNWZVBOSHVOWWI5MEpIcnRucGg1ZDlhL1lpTkpzY1N3QTFwUVZrdW1TQWtPQWdLdWRzcnl3c0N3Zkg1anNydVpHUTJDd1hKRXQzUU4wU2NLUlVnT1NCQ3FYa1BqZDVSVzJuOFZpamt4anovbWptakhCNmk0eHM5NEU2Nzk5STAyaldYNVd3UDZhTFRaTGt5TjhxNDUxT0RmeUZVZEY5WWsyZXQ5VUpsV1NzRFJMSWVCd0ZyQkEyZTdyRWsybWFLVUNCRW5PUWM2bUhVMXQvZ3gzK1VXVVFXbkpMZVUxbWUvbkFEdy96UGUwd3d0Vm9BaERZdDBoR1hQblJydjFoUHRGS01CeWtqckg3a0J5U0R3WDlQMi9XZkNkQlE5K1J4cHRsR2hvRmdpMUs0NVlOeEpEd05wTmd5MDV2WXUzVUtrMkpRYVNGUzcwK0Y1NzluRE5RenZpK0pPRlRsdDFmWDJGNXk5NEV2NHZobWRQSmRVOFVVRjU2Ymx0emxKREVFdmsySlFrOTM0aHpwTXJGZ1d3ZHUxUkxxSEhCN2h2T2hnaHNqV0ZGY01zNjZaRUtWcVhKUytxWWNVMHk0akwySVQrNlF2N2pvQ3BWbUdzUWtGY1FyblhxOUJiOTdaUS96UCtwaldmWTU0UmNRVlMydUU1YURObVVyVkdLK3E0d0xRcUhuRVViT2puSHFFeGlacUtxOVdRaUtUK2c3QS96bVlIQ2k0YzFTejRNVWhHb0t6U2l4aXoxYUNJUEJXdy9vczR2cUVqbXgzOGx6YnV0OWNWbElzeGNkTUpUTERRK3ZOZ0YyY1ZRaVcxRTQ0d3lWcnI3TUFaOE9KRVpFSzlEZWt5MzJQUkFuSkRUVXVqdGFscmJ0T2VOczhyS09uTjcvNFRqUEwvZmRlbEI4bjA4WXdSNXdmbU42VGpGWUhRSDFjbUZmK1AvNUxVMTI4Q1pEYjNQUStxMlFJazV3aE40eGwvcy9lb29pallmeWtDcm5aSEhHWkluTGhoU2pWbk5ISWdTL203VWV0NlhBTDdvZUl5UFRLeHVnbDJzRWtUQzNnZ0tjTnFZR0E5U3ZlYVlaQ00vWHNQRUtQbWs3QmlRNmprWFBKaE1yREd4Vkc0SW9aSDgrYjBrUWJYR2l0Mkw0L3hZdHh1bTVzcFNPSjdsTDltVFpRNnBxM2JOaTEwZU1mZ0ZWaDc3NU5JRlc0SEp3U1FtaTU0bk11blZTQjhxdjZKc0w3SGlsZ2N0ZHFSNThTTjVad1lCa2dOR1hzYjA1QXJWemVXbHh1Y21BSHNPT3dyczFnMzh6bTRZN2ZPZmducmFhV1kxanZZOFlEODZQZThkZzR4cE5paTg3UnNDZk5WK2NKVmMraktFdnpuZVY1Zzd0RmlxZCtsZHp4STlKemdSS2t0WUV6RUpRSVU5M2UvclJaN1lrVkZtNVV1cjVhMWYzcG83T0VtYkJUc2MrQ1FaOGNnYmIvbUphRXJoa3NyL3JURjBNcjNxeDl5SlJWSEJ6YWNWd0dScEFRaURPdnJkWU4xQXBVOTRyR1lrVFVzdWs1YjE1Wll2QVZxRlRzVlVMaS9HY29mbEljMm01Z2RFTFZOblRmdXY1Zlk5S1NlWHFoUU80S0pOYVZmbHAwQ0VKYWFFZFNLUXJJNXRaT2w1RkE4VXZlNmxTWVd5TVk0REl4a1RiT1JoWHVBdzR6b1RTMjgrN3d2TXhydVBkZnlKbUJCTkhQdCtEYmdKNHovcHJZWUhpTmFMTXNZamtQZE44ajNKZDczQXJFZk92Um52MzYxSVVVMFg1RDc1dlRSdlpkbzMzWERzanRlOU4weUo3K2lIQnF1a1FJY2pIVW9ic2RQN0hOajBVYWNSMHIvTmRlVTlGNFBNc1VLY2t6Tk4rZGhyMVI2d1J2R1VZb1pDRWJaWlJMWEt4QnA3SElUNEVQUktHakIvdW1xTFhhMXl6RWx2QW1WQUJhMDFZN3dGdk4wM2Ywb25FbUhTM2w1d1paRmV6cjVibnN5T01XVGxhMU5kaW1ZNXNVeE15VFliZmc4dzB2cXNEc28zWFAxYndLdzZ3M3VIRGQ1UHBSWnVDSnR0eWk0ZzJGeWI0Ymg1UU42ZkdORTI2ekRGN1Y4QmJwZXJLNkFKQ0xTWm5kaDZMMTlPUTBram4xUGpEMGk4c1BZcGFXOWxVeVJkZElPKzRWQS9LemxPUzJ4M2s5VUtUdElsTTBUSVdtZXFIS0dYUVpocGpvVGI2VlNKN203cjZaaVlQMnVsQVVvZmVWL0o2eCtzckxEQXkyQ2ZFNnFrREZ1OU9NWDBBSXVnN3loQUtOMDRyT3hVNk5tcGtjOUZ4bXUvVS9vR3hHdmIzeFVFTDYwdE1sSE9EaWtqY1I5RDJrKzRwbEc1WnV0d0FIY2kwRU02WHRrVEhQOU5QMlRTR1VFN1E5SGYvU0VEc2V0a25hZXhvWmhDczJLWDFMeU5JS0U0N2pkMkR3MTUreDRRVXV0VUFTbzU5Q1lHMVFBeW9BVVhrV3dtbXkzTGdTUWp5T3ZLV25qaE8veWpPd0FyWGd0NFBrSVVnZDQ1N05ReFpMbU41K0J4NVJoQ0FHdkUxYmxOZjlMek9keGJiaG5VZ2Z1RDM5MXVSRkhjS2RYREY3ZmVqb3gveThtaWZJcTRWVzQyajBHQnFOQUtkK0prMnJCMW9hOTRiT2hxcVVzanhqWnlRaGRXTzhNblR6T2tOaGVpZXU2blYxcW5yZ3JHU2huWTNJMlczb29GNFNnczRjZ3drZ2h2dHpFa0xUbU5OUm83RTdudVRuMkxJcmlGSnlvTmZQdUp0aWN0S0JtNzRGZytkWVBTMlIzTzNmOWxBZWxiVWZjbzZGNU9EL3hkS1VuRTh0V3FOMExVcDlWQUptWVZYZFVDaGJ4MjM4MWtDaStLNDJoRzUydFNQYU1hb1dTb0xQY2Zrb24rc1pYdjdEdEtwZi9HTzdhcUMza1pzRGpva29haHJGZGJWSlNTZWhrNGp5K3RzRHplQnJKSjBrMVZrUnJHN1NoVHZjTmd1cjVucVRUTEE5dlJMQmJNTTlhNlI1NEZ0Z1pQOWFKMU1aMEdCcUVpMnF6Ui8yd2tYQlhwcFhZdi9TcU1RV1dhbTVsSHBMVktxaDN4ZHRjNFdmck9mYldsbU1PNXA5Z0JUSFp1YUcxVGFkZXFRVVpKQmZBS01ENFdSR0NsMDFaeDRTVzE0YzZrdnFKdXExL080N215L3RsVHlLWndpYlBkQTNRMVVGd0I3R2Z4anEwaDN2ckxFbUNrS3Vsc0VBUkN6UnZNVjJSVnBVbFpUV240Y1Boc0hjcTNROElHSUYyKy9nOENFSU4vMU8xcVMvMkpXcXlDNmtIb0w4Y2R2R0VHbmkxSTNDTk1JcXhxaHhJL1V0R3REc2VwYmwrSHI0elh4MzZna3BCbXBoT2xkTFVYTHAzVEtibVVZRWJSWHcvZmRmeFQ3WDdZUFhHQ0hHVG1uTzk4WkxDOTA2Zmkvekd2b04rNlpzbCs3MkpWMGxJWEo0V3dZdWxFUmZHbkFDWGNoa0Yzei9ITWR3elcwTUFFaXptQmwvREo2ZUoyU01PSG1Uc25YbElGRDRlcFRrYnFBQ0dpZ2I1UExFdHdQRVRjYkNRckM5YUtTU1FnSTdEZXd1aWlxM2J0Y0RUWkIzeEI5WWxlbmhpU0FXNjIwcmwzc2ZjY3d3eGFSOHBDV2Rzd0x3dmFxcDhjM01PV3RCc2xPcmVTSkNEcWgvdzBYbm1WMFJVWFpNM2JvUmkwVXhsaHVUeDFlM1NTd09pbTlOczNYV3NoTmI4Lzc3VkhnUWhRVFlSUU1NRllYaWRmMElCKzBtSUpocWNoQTlUeUY3dGRjSDhrUUJUSHNEWS96bFpqK3EwNlFMd0JkbTkxc3IyK3VzZmxlaXB3WUMrcmdiNHROVnA3VU5rYkVqTnR6ZWZsTi9VRTlkbHZtT2x6V1dtZkh2NGVkUGkzMmJmeUNRS1d6SGJVVEV3NU0yVFpsZnpNaTFWUjVsaDBxQ1lqaDNITUlmL2MwcHBKd2I1b1lFTnBBenlxbnlmdmlTV3lBYzc2L1l1VWwvb2FVaysrYzBZc2d1TGo5ZGFQdVVvemhoZ3VjSytQRGlNckI0ODU1Mk83VWg0aHRwNmZ3S2dJa1JCTVFIUTd6MmV5WXovV1AwQm9ZZVhjOGc3aUprclhFNzA1bFo1bXhGU0poT3E1WlNleVJSb21pUm41K3VRemM5ZFdWQjBYb2JURXdOc0VRM2FIZ25JY29BczY2UGplUT09PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWREYXRhPjwvc2FtbDI6RW5jcnlwdGVkQXNzZXJ0aW9uPjwvc2FtbDJwOlJlc3BvbnNlPg==python-social-auth-0.2.21/social/tests/actions/0000755000175500017550000000000012754357263021150 5ustar debacledebaclepython-social-auth-0.2.21/social/tests/actions/actions.py0000644000175500017550000002052712754357263023170 0ustar debacledebacleimport json import requests import unittest2 as unittest from httpretty import HTTPretty from social.utils import parse_qs, module_member from social.p3 import urlparse from social.actions import do_auth, do_complete from social.tests.models import TestStorage, User, TestUserSocialAuth, \ TestNonce, TestAssociation from social.tests.strategy import TestStrategy class BaseActionTest(unittest.TestCase): user_data_url = 'https://api.github.com/user' login_redirect_url = '/success' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def __init__(self, *args, **kwargs): self.strategy = None super(BaseActionTest, self).__init__(*args, **kwargs) def setUp(self): HTTPretty.enable() User.reset_cache() TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() Backend = module_member('social.backends.github.GithubOAuth2') self.strategy = self.strategy or TestStrategy(TestStorage) self.backend = Backend(self.strategy, redirect_uri='/complete/github') self.user = None def tearDown(self): self.backend = None self.strategy = None self.user = None User.reset_cache() User.set_active(True) TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() HTTPretty.disable() def do_login(self, after_complete_checks=True, user_data_body=None, expected_username=None): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_KEY': 'a-key', 'SOCIAL_AUTH_GITHUB_SECRET': 'a-secret-key', 'SOCIAL_AUTH_LOGIN_REDIRECT_URL': self.login_redirect_url, 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( 'social.backends.github.GithubOAuth2', ) }) start_url = do_auth(self.backend).url target_url = self.strategy.build_absolute_uri( '/complete/github/?code=foobar' ) start_query = parse_qs(urlparse(start_url).query) location_url = target_url + ('?' in target_url and '&' or '?') + \ 'state=' + start_query['state'] location_query = parse_qs(urlparse(location_url).query) HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=location_url) HTTPretty.register_uri(HTTPretty.GET, location_url, status=200, body='foobar') response = requests.get(start_url) self.assertEqual(response.url, location_url) self.assertEqual(response.text, 'foobar') HTTPretty.register_uri(HTTPretty.POST, uri=self.backend.ACCESS_TOKEN_URL, status=200, body=self.access_token_body or '', content_type='text/json') if self.user_data_url: user_data_body = user_data_body or self.user_data_body or '' HTTPretty.register_uri(HTTPretty.GET, self.user_data_url, body=user_data_body, content_type='text/json') self.strategy.set_request_data(location_query, self.backend) def _login(backend, user, social_user): backend.strategy.session_set('username', user.username) redirect = do_complete(self.backend, user=self.user, login=_login) if after_complete_checks: self.assertEqual(self.strategy.session_get('username'), expected_username or self.expected_username) self.assertEqual(redirect.url, self.login_redirect_url) return redirect def do_login_with_partial_pipeline(self, before_complete=None): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_KEY': 'a-key', 'SOCIAL_AUTH_GITHUB_SECRET': 'a-secret-key', 'SOCIAL_AUTH_LOGIN_REDIRECT_URL': self.login_redirect_url, 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( 'social.backends.github.GithubOAuth2', ), 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.partial.save_status_to_session', 'social.tests.pipeline.ask_for_password', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.tests.pipeline.set_password', 'social.pipeline.user.user_details' ) }) start_url = do_auth(self.backend).url target_url = self.strategy.build_absolute_uri( '/complete/github/?code=foobar' ) start_query = parse_qs(urlparse(start_url).query) location_url = target_url + ('?' in target_url and '&' or '?') + \ 'state=' + start_query['state'] location_query = parse_qs(urlparse(location_url).query) HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=location_url) HTTPretty.register_uri(HTTPretty.GET, location_url, status=200, body='foobar') response = requests.get(start_url) self.assertEqual(response.url, location_url) self.assertEqual(response.text, 'foobar') HTTPretty.register_uri(HTTPretty.GET, uri=self.backend.ACCESS_TOKEN_URL, status=200, body=self.access_token_body or '', content_type='text/json') if self.user_data_url: HTTPretty.register_uri(HTTPretty.GET, self.user_data_url, body=self.user_data_body or '', content_type='text/json') self.strategy.set_request_data(location_query, self.backend) def _login(backend, user, social_user): backend.strategy.session_set('username', user.username) redirect = do_complete(self.backend, user=self.user, login=_login) url = self.strategy.build_absolute_uri('/password') self.assertEqual(redirect.url, url) HTTPretty.register_uri(HTTPretty.GET, redirect.url, status=200, body='foobar') HTTPretty.register_uri(HTTPretty.POST, redirect.url, status=200) password = 'foobar' requests.get(url) requests.post(url, data={'password': password}) data = parse_qs(HTTPretty.last_request.body) self.assertEqual(data['password'], password) self.strategy.session_set('password', data['password']) if before_complete: before_complete() redirect = do_complete(self.backend, user=self.user, login=_login) self.assertEqual(self.strategy.session_get('username'), self.expected_username) self.assertEqual(redirect.url, self.login_redirect_url) python-social-auth-0.2.21/social/tests/actions/__init__.py0000644000175500017550000000000012754357263023247 0ustar debacledebaclepython-social-auth-0.2.21/social/tests/actions/test_associate.py0000644000175500017550000000556412754357263024546 0ustar debacledebacleimport json from social.exceptions import AuthAlreadyAssociated from social.tests.models import User from social.tests.actions.actions import BaseActionTest class AssociateActionTest(BaseActionTest): expected_username = 'foobar' def setUp(self): super(AssociateActionTest, self).setUp() self.user = User(username='foobar', email='foo@bar.com') self.backend.strategy.session_set('username', self.user.username) def test_associate(self): self.do_login() self.assertTrue(len(self.user.social), 1) self.assertEqual(self.user.social[0].provider, 'github') def test_associate_with_partial_pipeline(self): self.do_login_with_partial_pipeline() self.assertEqual(len(self.user.social), 1) self.assertEqual(self.user.social[0].provider, 'github') class MultipleAccountsTest(AssociateActionTest): alternative_user_data_body = json.dumps({ 'login': 'foobar2', 'id': 2, 'avatar_url': 'https://github.com/images/error/foobar2_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar2', 'name': 'monalisa foobar2', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar2', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_multiple_social_accounts(self): self.do_login() self.do_login(user_data_body=self.alternative_user_data_body) self.assertEqual(len(self.user.social), 2) self.assertEqual(self.user.social[0].provider, 'github') self.assertEqual(self.user.social[1].provider, 'github') class AlreadyAssociatedErrorTest(BaseActionTest): def setUp(self): super(AlreadyAssociatedErrorTest, self).setUp() self.user1 = User(username='foobar', email='foo@bar.com') self.user = None def tearDown(self): super(AlreadyAssociatedErrorTest, self).tearDown() self.user1 = None self.user = None def test_already_associated_error(self): self.user = self.user1 self.do_login() self.user = User(username='foobar2', email='foo2@bar2.com') with self.assertRaisesRegexp(AuthAlreadyAssociated, 'This github account is already in use.'): self.do_login() python-social-auth-0.2.21/social/tests/actions/test_login.py0000644000175500017550000000511712754357263023675 0ustar debacledebaclefrom social.tests.models import User from social.tests.actions.actions import BaseActionTest class LoginActionTest(BaseActionTest): def test_login(self): self.do_login() def test_login_with_partial_pipeline(self): self.do_login_with_partial_pipeline() def test_fields_stored_in_session(self): self.strategy.set_settings({ 'SOCIAL_AUTH_FIELDS_STORED_IN_SESSION': ['foo', 'bar'] }) self.strategy.set_request_data({'foo': '1', 'bar': '2'}, self.backend) self.do_login() self.assertEqual(self.strategy.session_get('foo'), '1') self.assertEqual(self.strategy.session_get('bar'), '2') def test_redirect_value(self): self.strategy.set_request_data({'next': '/after-login'}, self.backend) redirect = self.do_login(after_complete_checks=False) self.assertEqual(redirect.url, '/after-login') def test_login_with_invalid_partial_pipeline(self): def before_complete(): partial = self.strategy.session_get('partial_pipeline') partial['backend'] = 'foobar' self.strategy.session_set('partial_pipeline', partial) self.do_login_with_partial_pipeline(before_complete) def test_new_user(self): self.strategy.set_settings({ 'SOCIAL_AUTH_NEW_USER_REDIRECT_URL': '/new-user' }) redirect = self.do_login(after_complete_checks=False) self.assertEqual(redirect.url, '/new-user') def test_inactive_user(self): self.strategy.set_settings({ 'SOCIAL_AUTH_INACTIVE_USER_URL': '/inactive' }) User.set_active(False) redirect = self.do_login(after_complete_checks=False) self.assertEqual(redirect.url, '/inactive') def test_invalid_user(self): self.strategy.set_settings({ 'SOCIAL_AUTH_LOGIN_ERROR_URL': '/error', 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', 'social.tests.pipeline.remove_user' ) }) redirect = self.do_login(after_complete_checks=False) self.assertEqual(redirect.url, '/error') python-social-auth-0.2.21/social/tests/actions/test_disconnect.py0000644000175500017550000000514012754357263024712 0ustar debacledebacleimport requests from httpretty import HTTPretty from social.actions import do_disconnect from social.exceptions import NotAllowedToDisconnect from social.utils import parse_qs from social.tests.models import User, TestUserSocialAuth from social.tests.actions.actions import BaseActionTest class DisconnectActionTest(BaseActionTest): def test_not_allowed_to_disconnect(self): self.do_login() user = User.get(self.expected_username) with self.assertRaises(NotAllowedToDisconnect): do_disconnect(self.backend, user) def test_disconnect(self): self.do_login() user = User.get(self.expected_username) user.password = 'password' do_disconnect(self.backend, user) self.assertEqual(len(user.social), 0) def test_disconnect_with_association_id(self): self.do_login() user = User.get(self.expected_username) user.password = 'password' association_id = user.social[0].id second_usa = TestUserSocialAuth(user, user.social[0].provider, "uid2") self.assertEqual(len(user.social), 2) do_disconnect(self.backend, user, association_id) self.assertEqual(len(user.social), 1) self.assertEqual(user.social[0], second_usa) def test_disconnect_with_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_DISCONNECT_PIPELINE': ( 'social.pipeline.partial.save_status_to_session', 'social.tests.pipeline.ask_for_password', 'social.tests.pipeline.set_password', 'social.pipeline.disconnect.allowed_to_disconnect', 'social.pipeline.disconnect.get_entries', 'social.pipeline.disconnect.revoke_tokens', 'social.pipeline.disconnect.disconnect' ) }) self.do_login() user = User.get(self.expected_username) redirect = do_disconnect(self.backend, user) url = self.strategy.build_absolute_uri('/password') self.assertEqual(redirect.url, url) HTTPretty.register_uri(HTTPretty.GET, redirect.url, status=200, body='foobar') HTTPretty.register_uri(HTTPretty.POST, redirect.url, status=200) password = 'foobar' requests.get(url) requests.post(url, data={'password': password}) data = parse_qs(HTTPretty.last_request.body) self.assertEqual(data['password'], password) self.strategy.session_set('password', data['password']) redirect = do_disconnect(self.backend, user) self.assertEqual(len(user.social), 0) python-social-auth-0.2.21/social/tests/run_tests.sh0000755000175500017550000000010312754357263022067 0ustar debacledebacle#!/bin/sh nosetests --with-coverage --cover-package=social --stop python-social-auth-0.2.21/social/tests/requirements.txt0000644000175500017550000000021612754357263022773 0ustar debacledebaclehttpretty==0.6.5 coverage>=3.6 mock==1.0.1 nose>=1.2.1 rednose>=0.4.1 requests>=1.1.0 PyJWT>=1.0.0,<2.0.0 unittest2==0.5.1 python-saml==2.1.3 python-social-auth-0.2.21/social/tests/pipeline.py0000644000175500017550000000225212754357263021670 0ustar debacledebaclefrom social.pipeline.partial import partial def ask_for_password(strategy, *args, **kwargs): if strategy.session_get('password'): return {'password': strategy.session_get('password')} else: return strategy.redirect(strategy.build_absolute_uri('/password')) @partial def ask_for_slug(strategy, *args, **kwargs): if strategy.session_get('slug'): return {'slug': strategy.session_get('slug')} else: return strategy.redirect(strategy.build_absolute_uri('/slug')) def set_password(strategy, user, *args, **kwargs): user.set_password(kwargs['password']) def set_slug(strategy, user, *args, **kwargs): user.slug = kwargs['slug'] def remove_user(strategy, user, *args, **kwargs): return {'user': None} @partial def set_user_from_kwargs(strategy, *args, **kwargs): if strategy.session_get('attribute'): kwargs['user'].id else: return strategy.redirect(strategy.build_absolute_uri('/attribute')) @partial def set_user_from_args(strategy, user, *args, **kwargs): if strategy.session_get('attribute'): user.id else: return strategy.redirect(strategy.build_absolute_uri('/attribute')) python-social-auth-0.2.21/social/tests/requirements-pypy.txt0000644000175500017550000000017312754357263023774 0ustar debacledebaclehttpretty==0.6.5 coverage>=3.6 mock==1.0.1 nose>=1.2.1 rednose>=0.4.1 requests>=1.1.0 PyJWT>=1.0.0,<2.0.0 unittest2==0.5.1 python-social-auth-0.2.21/social/tests/test_exceptions.py0000644000175500017550000000553012754357263023305 0ustar debacledebacleimport unittest2 as unittest from social.exceptions import SocialAuthBaseException, WrongBackend, \ AuthFailed, AuthTokenError, \ AuthMissingParameter, AuthStateMissing, \ NotAllowedToDisconnect, AuthException, \ AuthCanceled, AuthUnknownError, \ AuthStateForbidden, AuthAlreadyAssociated, \ AuthTokenRevoked class BaseExceptionTestCase(unittest.TestCase): exception = None expected_message = '' def test_exception_message(self): if self.exception is None and self.expected_message == '': return try: raise self.exception except SocialAuthBaseException as err: self.assertEqual(str(err), self.expected_message) class WrongBackendTest(BaseExceptionTestCase): exception = WrongBackend('foobar') expected_message = 'Incorrect authentication service "foobar"' class AuthFailedTest(BaseExceptionTestCase): exception = AuthFailed('foobar', 'wrong_user') expected_message = 'Authentication failed: wrong_user' class AuthFailedDeniedTest(BaseExceptionTestCase): exception = AuthFailed('foobar', 'access_denied') expected_message = 'Authentication process was canceled' class AuthTokenErrorTest(BaseExceptionTestCase): exception = AuthTokenError('foobar', 'Incorrect tokens') expected_message = 'Token error: Incorrect tokens' class AuthMissingParameterTest(BaseExceptionTestCase): exception = AuthMissingParameter('foobar', 'username') expected_message = 'Missing needed parameter username' class AuthStateMissingTest(BaseExceptionTestCase): exception = AuthStateMissing('foobar') expected_message = 'Session value state missing.' class NotAllowedToDisconnectTest(BaseExceptionTestCase): exception = NotAllowedToDisconnect() expected_message = '' class AuthExceptionTest(BaseExceptionTestCase): exception = AuthException('foobar', 'message') expected_message = 'message' class AuthCanceledTest(BaseExceptionTestCase): exception = AuthCanceled('foobar') expected_message = 'Authentication process canceled' class AuthUnknownErrorTest(BaseExceptionTestCase): exception = AuthUnknownError('foobar', 'some error') expected_message = 'An unknown error happened while ' \ 'authenticating some error' class AuthStateForbiddenTest(BaseExceptionTestCase): exception = AuthStateForbidden('foobar') expected_message = 'Wrong state parameter given.' class AuthAlreadyAssociatedTest(BaseExceptionTestCase): exception = AuthAlreadyAssociated('foobar') expected_message = '' class AuthTokenRevokedTest(BaseExceptionTestCase): exception = AuthTokenRevoked('foobar') expected_message = 'User revoke access to the token' python-social-auth-0.2.21/social/strategies/0000755000175500017550000000000012754357263020520 5ustar debacledebaclepython-social-auth-0.2.21/social/strategies/cherrypy_strategy.py0000644000175500017550000000360412754357263024664 0ustar debacledebacleimport six import cherrypy from social.strategies.base import BaseStrategy, BaseTemplateStrategy class CherryPyJinja2TemplateStrategy(BaseTemplateStrategy): def __init__(self, strategy): self.strategy = strategy self.env = cherrypy.tools.jinja2env def render_template(self, tpl, context): return self.env.get_template(tpl).render(context) def render_string(self, html, context): return self.env.from_string(html).render(context) class CherryPyStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = CherryPyJinja2TemplateStrategy def get_setting(self, name): return cherrypy.config[name] def request_data(self, merge=True): if merge: data = cherrypy.request.params elif cherrypy.request.method == 'POST': data = cherrypy.body.params else: data = cherrypy.request.params return data def request_host(self): return cherrypy.request.base def redirect(self, url): raise cherrypy.HTTPRedirect(url) def html(self, content): return content def authenticate(self, backend, *args, **kwargs): kwargs['strategy'] = self kwargs['storage'] = self.storage kwargs['backend'] = backend return backend.authenticate(*args, **kwargs) def session_get(self, name, default=None): return cherrypy.session.get(name, default) def session_set(self, name, value): cherrypy.session[name] = value def session_pop(self, name): cherrypy.session.pop(name, None) def session_setdefault(self, name, value): return cherrypy.session.setdefault(name, value) def build_absolute_uri(self, path=None): return cherrypy.url(path or '') def is_response(self, value): return isinstance(value, six.string_types) or \ isinstance(value, cherrypy.CherryPyException) python-social-auth-0.2.21/social/strategies/utils.py0000644000175500017550000000157412754357263022241 0ustar debacledebaclefrom social.utils import module_member # Current strategy getter cache, currently only used by Django to set a method # to get the current strategy which is latter used by backends get_user() # method to retrieve the user saved in the session. Backends need an strategy # to properly access the storage, but Django does not know about that when # creates the backend instance, this method workarounds the problem. _current_strategy_getter = None def get_strategy(strategy, storage, *args, **kwargs): Strategy = module_member(strategy) Storage = module_member(storage) return Strategy(Storage, *args, **kwargs) def set_current_strategy_getter(func): global _current_strategy_getter _current_strategy_getter = func def get_current_strategy(): global _current_strategy_getter if _current_strategy_getter is not None: return _current_strategy_getter() python-social-auth-0.2.21/social/strategies/__init__.py0000644000175500017550000000000012754357263022617 0ustar debacledebaclepython-social-auth-0.2.21/social/strategies/tornado_strategy.py0000644000175500017550000000464512754357263024473 0ustar debacledebacleimport json import six from tornado.template import Loader, Template from social.utils import build_absolute_uri from social.strategies.base import BaseStrategy, BaseTemplateStrategy class TornadoTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): path, tpl = tpl.rsplit('/', 1) return Loader(path).load(tpl).generate(**context) def render_string(self, html, context): return Template(html).generate(**context) class TornadoStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = TornadoTemplateStrategy def __init__(self, storage, request_handler, tpl=None): self.request_handler = request_handler self.request = self.request_handler.request super(TornadoStrategy, self).__init__(storage, tpl) def get_setting(self, name): return self.request_handler.settings[name] def request_data(self, merge=True): # Multiple valued arguments not supported yet return dict((key, val[0].decode()) for key, val in six.iteritems(self.request.arguments)) def request_host(self): return self.request.host def redirect(self, url): return self.request_handler.redirect(url) def html(self, content): self.request_handler.write(content) def session_get(self, name, default=None): value = self.request_handler.get_secure_cookie(name) if value: return json.loads(value.decode()) return default def session_set(self, name, value): self.request_handler.set_secure_cookie(name, json.dumps(value).encode()) def session_pop(self, name): value = self.session_get(name) self.request_handler.clear_cookie(name) return value def session_setdefault(self, name, value): pass def build_absolute_uri(self, path=None): return build_absolute_uri('{0}://{1}'.format(self.request.protocol, self.request.host), path) def partial_to_session(self, next, backend, request=None, *args, **kwargs): return json.dumps(super(TornadoStrategy, self).partial_to_session( next, backend, request=request, *args, **kwargs )) def partial_from_session(self, session): if session: return super(TornadoStrategy, self).partial_to_session( json.loads(session) ) python-social-auth-0.2.21/social/strategies/webpy_strategy.py0000644000175500017550000000361412754357263024146 0ustar debacledebacleimport web from social.strategies.base import BaseStrategy, BaseTemplateStrategy class WebpyTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): return web.template.render(tpl)(**context) def render_string(self, html, context): return web.template.Template(html)(**context) class WebpyStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = WebpyTemplateStrategy def get_setting(self, name): return getattr(web.config, name) def request_data(self, merge=True): if merge: data = web.input(_method='both') elif web.ctx.method == 'POST': data = web.input(_method='post') else: data = web.input(_method='get') return data def request_host(self): return web.ctx.host def redirect(self, url): return web.seeother(url) def html(self, content): web.header('Content-Type', 'text/html;charset=UTF-8') return content def render_html(self, tpl=None, html=None, context=None): if not tpl and not html: raise ValueError('Missing template or html parameters') context = context or {} if tpl: tpl = web.template.frender(tpl) else: tpl = web.template.Template(html) return tpl(**context) def session_get(self, name, default=None): return web.web_session.get(name, default) def session_set(self, name, value): web.web_session[name] = value def session_pop(self, name): return web.web_session.pop(name, None) def session_setdefault(self, name, value): return web.web_session.setdefault(name, value) def build_absolute_uri(self, path=None): path = path or '' if path.startswith('http://') or path.startswith('https://'): return path return web.ctx.protocol + '://' + web.ctx.host + path python-social-auth-0.2.21/social/strategies/django_strategy.py0000644000175500017550000001167412754357263024267 0ustar debacledebaclefrom django.conf import settings from django.http import HttpResponse from django.db.models import Model from django.contrib.contenttypes.models import ContentType from django.contrib.auth import authenticate from django.shortcuts import redirect from django.template import TemplateDoesNotExist, RequestContext, loader from django.utils.encoding import force_text from django.utils.functional import Promise from django.utils.translation import get_language from social.strategies.base import BaseStrategy, BaseTemplateStrategy class DjangoTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): template = loader.get_template(tpl) return template.render(RequestContext(self.strategy.request, context)) def render_string(self, html, context): template = loader.get_template_from_string(html) return template.render(RequestContext(self.strategy.request, context)) class DjangoStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = DjangoTemplateStrategy def __init__(self, storage, request=None, tpl=None): self.request = request self.session = request.session if request else {} super(DjangoStrategy, self).__init__(storage, tpl) def get_setting(self, name): value = getattr(settings, name) # Force text on URL named settings that are instance of Promise if name.endswith('_URL') and isinstance(value, Promise): value = force_text(value) return value def request_data(self, merge=True): if not self.request: return {} if merge: data = self.request.GET.copy() data.update(self.request.POST) elif self.request.method == 'POST': data = self.request.POST else: data = self.request.GET return data def request_host(self): if self.request: return self.request.get_host() def request_is_secure(self): """Is the request using HTTPS?""" return self.request.is_secure() def request_path(self): """path of the current request""" return self.request.path def request_port(self): """Port in use for this request""" return self.request.META['SERVER_PORT'] def request_get(self): """Request GET data""" return self.request.GET.copy() def request_post(self): """Request POST data""" return self.request.POST.copy() def redirect(self, url): return redirect(url) def html(self, content): return HttpResponse(content, content_type='text/html;charset=UTF-8') def render_html(self, tpl=None, html=None, context=None): if not tpl and not html: raise ValueError('Missing template or html parameters') context = context or {} try: template = loader.get_template(tpl) except TemplateDoesNotExist: template = loader.get_template_from_string(html) return template.render(RequestContext(self.request, context)) def authenticate(self, backend, *args, **kwargs): kwargs['strategy'] = self kwargs['storage'] = self.storage kwargs['backend'] = backend return authenticate(*args, **kwargs) def session_get(self, name, default=None): return self.session.get(name, default) def session_set(self, name, value): self.session[name] = value if hasattr(self.session, 'modified'): self.session.modified = True def session_pop(self, name): return self.session.pop(name, None) def session_setdefault(self, name, value): return self.session.setdefault(name, value) def build_absolute_uri(self, path=None): if self.request: return self.request.build_absolute_uri(path) else: return path def random_string(self, length=12, chars=BaseStrategy.ALLOWED_CHARS): try: from django.utils.crypto import get_random_string except ImportError: # django < 1.4 return super(DjangoStrategy, self).random_string(length, chars) else: return get_random_string(length, chars) def to_session_value(self, 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 from_session_value(self, val): """Converts back the instance saved by self._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 get_language(self): """Return current language""" return get_language() python-social-auth-0.2.21/social/strategies/pyramid_strategy.py0000644000175500017550000000472412754357263024470 0ustar debacledebaclefrom webob.multidict import NoVars from pyramid.response import Response from pyramid.httpexceptions import HTTPFound from pyramid.renderers import render from social.utils import build_absolute_uri from social.strategies.base import BaseStrategy, BaseTemplateStrategy class PyramidTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): return render(tpl, context, request=self.strategy.request) def render_string(self, html, context): return render(html, context, request=self.strategy.request) class PyramidStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = PyramidTemplateStrategy def __init__(self, storage, request, tpl=None): self.request = request super(PyramidStrategy, self).__init__(storage, tpl) def redirect(self, url): """Return a response redirect to the given URL""" response = getattr(self.request, 'response', None) if response is None: response = HTTPFound(location=url) else: response = HTTPFound(location=url, headers=response.headers) return response def get_setting(self, name): """Return value for given setting name""" return self.request.registry.settings[name] def html(self, content): """Return HTTP response with given content""" return Response(body=content) def request_data(self, merge=True): """Return current request data (POST or GET)""" if self.request.method == 'POST': if merge: data = self.request.POST.copy() if not isinstance(self.request.GET, NoVars): data.update(self.request.GET) else: data = self.request.POST else: data = self.request.GET return data def request_host(self): """Return current host value""" return self.request.host def session_get(self, name, default=None): """Return session value for given key""" return self.request.session.get(name, default) def session_set(self, name, value): """Set session value for given key""" self.request.session[name] = value def session_pop(self, name): """Pop session value for given key""" return self.request.session.pop(name, None) def build_absolute_uri(self, path=None): """Build absolute URI with given (optional) path""" return build_absolute_uri(self.request.host_url, path) python-social-auth-0.2.21/social/strategies/base.py0000644000175500017550000001670012754357263022010 0ustar debacledebacleimport time import random import hashlib from social.utils import setting_name, module_member from social.store import OpenIdStore, OpenIdSessionWrapper from social.pipeline import DEFAULT_AUTH_PIPELINE, DEFAULT_DISCONNECT_PIPELINE from social.pipeline.utils import partial_from_session, partial_to_session class BaseTemplateStrategy(object): def __init__(self, strategy): self.strategy = strategy def render(self, tpl=None, html=None, context=None): if not tpl and not html: raise ValueError('Missing template or html parameters') context = context or {} if tpl: return self.render_template(tpl, context) else: return self.render_string(html, context) def render_template(self, tpl, context): raise NotImplementedError('Implement in subclass') def render_string(self, html, context): raise NotImplementedError('Implement in subclass') class BaseStrategy(object): ALLOWED_CHARS = 'abcdefghijklmnopqrstuvwxyz' \ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \ '0123456789' DEFAULT_TEMPLATE_STRATEGY = BaseTemplateStrategy def __init__(self, storage=None, tpl=None): self.storage = storage self.tpl = (tpl or self.DEFAULT_TEMPLATE_STRATEGY)(self) def setting(self, name, default=None, backend=None): names = [setting_name(name), name] if backend: names.insert(0, setting_name(backend.name, name)) for name in names: try: return self.get_setting(name) except (AttributeError, KeyError): pass return default def create_user(self, *args, **kwargs): return self.storage.user.create_user(*args, **kwargs) def get_user(self, *args, **kwargs): return self.storage.user.get_user(*args, **kwargs) def session_setdefault(self, name, value): self.session_set(name, value) return self.session_get(name) def openid_session_dict(self, name): # Many frameworks are switching the session serialization from Pickle # to JSON to avoid code execution risks. Flask did this from Flask # 0.10, Django is switching to JSON by default from version 1.6. # # Sadly python-openid stores classes instances in the session which # fails the JSON serialization, the classes are: # # openid.yadis.manager.YadisServiceManager # openid.consumer.discover.OpenIDServiceEndpoint # # This method will return a wrapper over the session value used with # openid (a dict) which will automatically keep a pickled value for the # mentioned classes. return OpenIdSessionWrapper(self.session_setdefault(name, {})) def to_session_value(self, val): return val def from_session_value(self, val): return val def partial_to_session(self, next, backend, request=None, *args, **kwargs): return partial_to_session(self, next, backend, request=request, *args, **kwargs) def partial_from_session(self, session): return partial_from_session(self, session) def clean_partial_pipeline(self, name='partial_pipeline'): self.session_pop(name) def openid_store(self): return OpenIdStore(self) def get_pipeline(self): return self.setting('PIPELINE', DEFAULT_AUTH_PIPELINE) def get_disconnect_pipeline(self): return self.setting('DISCONNECT_PIPELINE', DEFAULT_DISCONNECT_PIPELINE) def random_string(self, length=12, chars=ALLOWED_CHARS): # Implementation borrowed from django 1.4 try: random.SystemRandom() except NotImplementedError: key = self.setting('SECRET_KEY', '') seed = '{0}{1}{2}'.format(random.getstate(), time.time(), key) random.seed(hashlib.sha256(seed.encode()).digest()) return ''.join([random.choice(chars) for i in range(length)]) def absolute_uri(self, path=None): uri = self.build_absolute_uri(path) if uri and self.setting('REDIRECT_IS_HTTPS'): uri = uri.replace('http://', 'https://') return uri def get_language(self): """Return current language""" return '' def send_email_validation(self, backend, email): email_validation = self.setting('EMAIL_VALIDATION_FUNCTION') send_email = module_member(email_validation) code = self.storage.code.make_code(email) send_email(self, backend, code) return code def validate_email(self, email, code): verification_code = self.storage.code.get_code(code) if not verification_code or verification_code.code != code: return False elif verification_code.email != email: return False else: verification_code.verify() return True def render_html(self, tpl=None, html=None, context=None): """Render given template or raw html with given context""" return self.tpl.render(tpl, html, context) def authenticate(self, backend, *args, **kwargs): """Trigger the authentication mechanism tied to the current framework""" kwargs['strategy'] = self kwargs['storage'] = self.storage kwargs['backend'] = backend return backend.authenticate(*args, **kwargs) def get_backends(self): """Return configured backends""" return self.setting('AUTHENTICATION_BACKENDS', []) # Implement the following methods on strategies sub-classes def redirect(self, url): """Return a response redirect to the given URL""" raise NotImplementedError('Implement in subclass') def get_setting(self, name): """Return value for given setting name""" raise NotImplementedError('Implement in subclass') def html(self, content): """Return HTTP response with given content""" raise NotImplementedError('Implement in subclass') def request_data(self, merge=True): """Return current request data (POST or GET)""" raise NotImplementedError('Implement in subclass') def request_host(self): """Return current host value""" raise NotImplementedError('Implement in subclass') def session_get(self, name, default=None): """Return session value for given key""" raise NotImplementedError('Implement in subclass') def session_set(self, name, value): """Set session value for given key""" raise NotImplementedError('Implement in subclass') def session_pop(self, name): """Pop session value for given key""" raise NotImplementedError('Implement in subclass') def build_absolute_uri(self, path=None): """Build absolute URI with given (optional) path""" raise NotImplementedError('Implement in subclass') def request_is_secure(self): """Is the request using HTTPS?""" raise NotImplementedError('Implement in subclass') def request_path(self): """path of the current request""" raise NotImplementedError('Implement in subclass') def request_port(self): """Port in use for this request""" raise NotImplementedError('Implement in subclass') def request_get(self): """Request GET data""" raise NotImplementedError('Implement in subclass') def request_post(self): """Request POST data""" raise NotImplementedError('Implement in subclass') python-social-auth-0.2.21/social/strategies/flask_strategy.py0000644000175500017550000000315612754357263024121 0ustar debacledebaclefrom flask import current_app, request, redirect, make_response, session, \ render_template, render_template_string from social.utils import build_absolute_uri from social.strategies.base import BaseStrategy, BaseTemplateStrategy class FlaskTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): return render_template(tpl, **context) def render_string(self, html, context): return render_template_string(html, **context) class FlaskStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = FlaskTemplateStrategy def get_setting(self, name): return current_app.config[name] def request_data(self, merge=True): if merge: data = request.form.copy() data.update(request.args) elif request.method == 'POST': data = request.form else: data = request.args return data def request_host(self): return request.host def redirect(self, url): return redirect(url) def html(self, content): response = make_response(content) response.headers['Content-Type'] = 'text/html;charset=UTF-8' return response def session_get(self, name, default=None): return session.get(name, default) def session_set(self, name, value): session[name] = value def session_pop(self, name): return session.pop(name, None) def session_setdefault(self, name, value): return session.setdefault(name, value) def build_absolute_uri(self, path=None): return build_absolute_uri(request.host_url, path) python-social-auth-0.2.21/social/exceptions.py0000644000175500017550000000634212754357263021106 0ustar debacledebacleclass SocialAuthBaseException(ValueError): """Base class for pipeline exceptions.""" pass class WrongBackend(SocialAuthBaseException): def __init__(self, backend_name): self.backend_name = backend_name def __str__(self): return 'Incorrect authentication service "{0}"'.format( self.backend_name ) class MissingBackend(WrongBackend): def __str__(self): return 'Missing backend "{0}" entry'.format(self.backend_name) class NotAllowedToDisconnect(SocialAuthBaseException): """User is not allowed to disconnect it's social account.""" pass 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 __str__(self): msg = super(AuthFailed, self).__str__() if msg == 'access_denied': return 'Authentication process was canceled' return 'Authentication failed: {0}'.format(msg) class AuthCanceled(AuthException): """Auth process was canceled by user.""" def __init__(self, *args, **kwargs): self.response = kwargs.pop('response', None) super(AuthCanceled, self).__init__(*args, **kwargs) def __str__(self): return 'Authentication process canceled' class AuthUnknownError(AuthException): """Unknown auth process error.""" def __str__(self): msg = super(AuthUnknownError, self).__str__() return 'An unknown error happened while authenticating {0}'.format(msg) class AuthTokenError(AuthException): """Auth token error.""" def __str__(self): msg = super(AuthTokenError, self).__str__() return 'Token error: {0}'.format(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 __str__(self): return 'Missing needed parameter {0}'.format(self.parameter) class AuthStateMissing(AuthException): """State parameter is incorrect.""" def __str__(self): return 'Session value state missing.' class AuthStateForbidden(AuthException): """State parameter is incorrect.""" def __str__(self): return '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 __str__(self): return 'User revoke access to the token' class AuthForbidden(AuthException): """Authentication for this user is forbidden""" def __str__(self): return 'Your credentials aren\'t allowed' class AuthUnreachableProvider(AuthException): """Cannot reach the provider""" def __str__(self): return 'The authentication provider could not be reached' class InvalidEmail(AuthException): def __str__(self): return 'Email couldn\'t be validated' python-social-auth-0.2.21/README.rst0000644000175500017550000002414412754357263016570 0ustar debacledebaclePython Social Auth ================== Python Social Auth is an easy-to-setup social authentication/registration mechanism with support for several frameworks and auth providers. Crafted using base code from django-social-auth, it implements a common interface to define new authentication providers from third parties, and to bring support for more frameworks and ORMs. .. image:: https://travis-ci.org/omab/python-social-auth.png?branch=master :target: https://travis-ci.org/omab/python-social-auth .. image:: https://badge.fury.io/py/python-social-auth.png :target: http://badge.fury.io/py/python-social-auth .. image:: https://readthedocs.org/projects/python-social-auth/badge/?version=latest :target: https://readthedocs.org/projects/python-social-auth/?badge=latest :alt: Documentation Status .. contents:: Table of Contents Features ======== This application provides user registration and login using social sites credentials. Here are some features, which is probably not a full list yet. Supported frameworks -------------------- Multiple frameworks are supported: * Django_ * Flask_ * Pyramid_ * Webpy_ * Tornado_ More frameworks can be added easily (and should be even easier in the future once the code matures). Auth providers -------------- Several services are supported by simply defining backends (new ones can be easily added or current ones extended): * Amazon_ OAuth2 http://login.amazon.com/website * Angel_ OAuth2 * AOL_ OpenId http://www.aol.com/ * Appsfuel_ OAuth2 * ArcGIS_ OAuth2 * Behance_ OAuth2 * BelgiumEIDOpenId_ OpenId https://www.e-contract.be/ * Bitbucket_ OAuth1 * Box_ OAuth2 * Clef_ OAuth2 * Coursera_ OAuth2 * Dailymotion_ OAuth2 * DigitalOcean_ OAuth2 https://developers.digitalocean.com/documentation/oauth/ * Disqus_ OAuth2 * Douban_ OAuth1 and OAuth2 * Dropbox_ OAuth1 and OAuth2 * Evernote_ OAuth1 * Exacttarget OAuth2 * Facebook_ OAuth2 and OAuth2 for Applications * Fedora_ OpenId http://fedoraproject.org/wiki/OpenID * Fitbit_ OAuth2 and OAuth1 * Flickr_ OAuth1 * Foursquare_ OAuth2 * `Google App Engine`_ Auth * Github_ OAuth2 * Google_ OAuth1, OAuth2 and OpenId * Instagram_ OAuth2 * Itembase_ OAuth2 * Jawbone_ OAuth2 https://jawbone.com/up/developer/authentication * Kakao_ OAuth2 https://developer.kakao.com * `Khan Academy`_ OAuth1 * Launchpad_ OpenId * Line_ OAuth2 * Linkedin_ OAuth1 * Live_ OAuth2 * Livejournal_ OpenId * LoginRadius_ OAuth2 and Application Auth * Mailru_ OAuth2 * MapMyFitness_ OAuth2 * Mendeley_ OAuth1 http://mendeley.com * Mixcloud_ OAuth2 * `Moves app`_ OAuth2 https://dev.moves-app.com/docs/authentication * `Mozilla Persona`_ * NaszaKlasa_ OAuth2 * `NGPVAN ActionID`_ OpenId * Odnoklassniki_ OAuth2 and Application Auth * OpenId_ * OpenStreetMap_ OAuth1 http://wiki.openstreetmap.org/wiki/OAuth * OpenSuse_ OpenId http://en.opensuse.org/openSUSE:Connect * Pinterest_ OAuth2 * PixelPin_ OAuth2 * Pocket_ OAuth2 * Podio_ OAuth2 * Rdio_ OAuth1 and OAuth2 * Readability_ OAuth1 * Reddit_ OAuth2 https://github.com/reddit/reddit/wiki/OAuth2 * Shopify_ OAuth2 * Sketchfab_ OAuth2 * Skyrock_ OAuth1 * Soundcloud_ OAuth2 * Stackoverflow_ OAuth2 * Steam_ OpenId * Stocktwits_ OAuth2 * Strava_ OAuth2 * Stripe_ OAuth2 * Taobao_ OAuth2 http://open.taobao.com/doc/detail.htm?id=118 * ThisIsMyJam_ OAuth1 https://www.thisismyjam.com/developers/authentication * Trello_ OAuth1 https://trello.com/docs/gettingstarted/oauth.html * Tripit_ OAuth1 * Tumblr_ OAuth1 * Twilio_ Auth * Twitter_ OAuth1 * Uber_ OAuth2 * Untappd_ OAuth2 * VK.com_ OpenAPI, OAuth2 and OAuth2 for Applications * Weibo_ OAuth2 * Withings_ OAuth1 * Wunderlist_ OAuth2 * Xing_ OAuth1 * Yahoo_ OpenId and OAuth2 * Yammer_ OAuth2 * Yandex_ OAuth1, OAuth2 and OpenId * Zotero_ OAuth1 User data --------- Basic user data population, to allow custom field values from provider's response. Social accounts association --------------------------- Multiple social accounts can be associated to a single user. Authentication processing ------------------------- Extensible pipeline to handle authentication/association mechanism in ways that suits your project. Dependencies ============ Dependencies that **must** be met to use the application: - OpenId_ support depends on python-openid_ - OAuth_ support depends on requests-oauthlib_ - Several backends demand application registration on their corresponding sites and other dependencies like sqlalchemy_ on Flask and Webpy. - Other dependencies: * six_ * requests_ Documents ========= Project homepage is available at http://psa.matiasaguirre.net/ and documents at http://psa.matiasaguirre.net or http://python-social-auth.readthedocs.org/. Installation ============ From pypi_:: $ pip install python-social-auth Or:: $ easy_install python-social-auth Or clone from github_:: $ git clone git://github.com/omab/python-social-auth.git And add social to ``PYTHONPATH``:: $ export PYTHONPATH=$PYTHONPATH:$(pwd)/python-social-auth/ Or:: $ cd python-social-auth $ sudo python setup.py install Upgrading --------- Django with South ~~~~~~~~~~~~~~~~~ Upgrading from 0.1 to 0.2 is likely to cause problems trying to apply a migration when the tables already exist. In this case a fake migration needs to be applied:: $ python manage.py migrate --fake default Support --------------------- If you're having problems with using the project, use the support forum at CodersClan. .. image:: http://www.codersclan.net/graphics/getSupport_github4.png :target: http://codersclan.net/forum/index.php?repo_id=8 Copyrights and License ====================== ``python-social-auth`` is protected by BSD license. Check the LICENSE_ for details. The base work was derived from django-social-auth_ work and copyrighted too, check `django-social-auth LICENSE`_ for details: .. _LICENSE: https://github.com/omab/python-social-auth/blob/master/LICENSE .. _django-social-auth: https://github.com/omab/django-social-auth .. _django-social-auth LICENSE: https://github.com/omab/django-social-auth/blob/master/LICENSE .. _OpenId: http://openid.net/ .. _OAuth: http://oauth.net/ .. _myOpenID: https://www.myopenid.com/ .. _Angel: https://angel.co .. _Appsfuel: http://docs.appsfuel.com .. _ArcGIS: http://www.arcgis.com/ .. _Behance: https://www.behance.net .. _Bitbucket: https://bitbucket.org .. _Box: https://www.box.com .. _Clef: https://getclef.com/ .. _Coursera: https://www.coursera.org/ .. _Dailymotion: https://dailymotion.com .. _DigitalOcean: https://www.digitalocean.com/ .. _Disqus: https://disqus.com .. _Douban: http://www.douban.com .. _Dropbox: https://dropbox.com .. _Evernote: https://www.evernote.com .. _Facebook: https://www.facebook.com .. _Fitbit: https://fitbit.com .. _Flickr: http://www.flickr.com .. _Foursquare: https://foursquare.com .. _Google App Engine: https://developers.google.com/appengine/ .. _Github: https://github.com .. _Google: http://google.com .. _Instagram: https://instagram.com .. _Itembase: https://www.itembase.com .. _LaunchPad: https://help.launchpad.net/YourAccount/OpenID .. _Line: https://line.me/ .. _Linkedin: https://www.linkedin.com .. _Live: https://live.com .. _Livejournal: http://livejournal.com .. _Khan Academy: https://github.com/Khan/khan-api/wiki/Khan-Academy-API-Authentication .. _Mailru: https://mail.ru .. _MapMyFitness: http://www.mapmyfitness.com/ .. _Mixcloud: https://www.mixcloud.com .. _Moves app: https://dev.moves-app.com/docs/ .. _Mozilla Persona: http://www.mozilla.org/persona/ .. _NaszaKlasa: https://developers.nk.pl/ .. _NGPVAN ActionID: http://developers.ngpvan.com/action-id .. _Odnoklassniki: http://www.odnoklassniki.ru .. _Pocket: http://getpocket.com .. _Podio: https://podio.com .. _Shopify: http://shopify.com .. _Sketchfab: https://sketchfab.com/developers/oauth .. _Skyrock: https://skyrock.com .. _Soundcloud: https://soundcloud.com .. _Stocktwits: https://stocktwits.com .. _Strava: http://strava.com .. _Stripe: https://stripe.com .. _Taobao: http://open.taobao.com/doc/detail.htm?id=118 .. _Tripit: https://www.tripit.com .. _Twilio: https://www.twilio.com .. _Twitter: http://twitter.com .. _Uber: http://uber.com .. _VK.com: http://vk.com .. _Weibo: https://weibo.com .. _Wunderlist: https://wunderlist.com .. _Xing: https://www.xing.com .. _Yahoo: http://yahoo.com .. _Yammer: https://www.yammer.com .. _Yandex: https://yandex.ru .. _Readability: http://www.readability.com/ .. _Stackoverflow: http://stackoverflow.com/ .. _Steam: http://steamcommunity.com/ .. _Rdio: https://www.rdio.com .. _Tumblr: http://www.tumblr.com/ .. _Amazon: http://login.amazon.com/website .. _AOL: http://www.aol.com/ .. _BelgiumEIDOpenId: https://www.e-contract.be/ .. _Fedora: http://fedoraproject.org/wiki/OpenID .. _Jawbone: https://jawbone.com/up/developer/authentication .. _Mendeley: http://mendeley.com .. _Reddit: https://github.com/reddit/reddit/wiki/OAuth2 .. _OpenSuse: http://en.opensuse.org/openSUSE:Connect .. _ThisIsMyJam: https://www.thisismyjam.com/developers/authentication .. _Trello: https://trello.com/docs/gettingstarted/oauth.html .. _Django: https://github.com/omab/python-social-auth/tree/master/social/apps/django_app .. _Flask: https://github.com/omab/python-social-auth/tree/master/social/apps/flask_app .. _Pyramid: http://www.pylonsproject.org/projects/pyramid/about .. _Webpy: https://github.com/omab/python-social-auth/tree/master/social/apps/webpy_app .. _Tornado: http://www.tornadoweb.org/ .. _python-openid: http://pypi.python.org/pypi/python-openid/ .. _requests-oauthlib: https://requests-oauthlib.readthedocs.org/ .. _sqlalchemy: http://www.sqlalchemy.org/ .. _pypi: http://pypi.python.org/pypi/python-social-auth/ .. _OpenStreetMap: http://www.openstreetmap.org .. _six: http://pythonhosted.org/six/ .. _requests: http://docs.python-requests.org/en/latest/ .. _PixelPin: http://pixelpin.co.uk .. _Zotero: http://www.zotero.org/ .. _Pinterest: https://www.pinterest.com .. _Untappd: https://untappd.com/ python-social-auth-0.2.21/setup.cfg0000644000175500017550000000033612754357263016717 0ustar debacledebacle[flake8] max-line-length = 119 # Ignore some well known paths exclude = .venv,.tox,dist,doc,build,*.egg,db/env.py,db/versions/*.py,site [nosetests] verbosity=2 with-coverage=1 cover-erase=1 cover-package=social rednose=1 python-social-auth-0.2.21/setup.py0000644000175500017550000000521512754357263016611 0ustar debacledebacle# -*- coding: utf-8 -*- """Setup file for easy installation""" import sys import os from os.path import join, dirname, split from setuptools import setup PY3 = os.environ.get('BUILD_VERSION') == '3' or sys.version_info[0] == 3 version = __import__('social').__version__ LONG_DESCRIPTION = """ Python Social Auth is an easy to setup social authentication/registration mechanism with support for several frameworks and auth providers. Crafted using base code from django-social-auth, implements a common interface to define new authentication providers from third parties. And to bring support for more frameworks and ORMs. """ 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 def path_tokens(path): if not path: return [] head, tail = split(path) return path_tokens(head) + [tail] def get_packages(): exclude_pacakages = ('__pycache__',) packages = [] for path_info in os.walk('social'): tokens = path_tokens(path_info[0]) if tokens[-1] not in exclude_pacakages: packages.append('.'.join(tokens)) return packages requirements_file, tests_requirements_file = { False: ('requirements.txt', 'social/tests/requirements.txt'), True: ('requirements-python3.txt', 'social/tests/requirements-python3.txt') }[PY3] with open(requirements_file, 'r') as f: requirements = f.readlines() with open(tests_requirements_file, 'r') as f: tests_requirements = [line for line in f.readlines() if '@' not in line] setup( name='python-social-auth', version=version, author='Matias Aguirre', author_email='matiasaguirre@gmail.com', description='Python social authentication made simple.', license='BSD', keywords='django, flask, pyramid, webpy, openid, oauth, social auth', url='https://github.com/omab/python-social-auth', packages=get_packages(), long_description=long_description(), install_requires=requirements, classifiers=[ 'Development Status :: 4 - Beta', 'Topic :: Internet', 'License :: OSI Approved :: BSD License', 'Intended Audience :: Developers', 'Environment :: Web Environment', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3' ], package_data={ 'social/tests': ['social/tests/*.txt'] }, include_package_data=True, tests_require=tests_requirements, test_suite='social.tests', zip_safe=False ) python-social-auth-0.2.21/test_requirements.txt0000644000175500017550000000006712754357263021422 0ustar debacledebaclemock==1.0.1 freeze==0.8.0 sure==1.2.7 httpretty==0.8.3 python-social-auth-0.2.21/docs/0000755000175500017550000000000012754357263016024 5ustar debacledebaclepython-social-auth-0.2.21/docs/configuration/0000755000175500017550000000000012754357263020673 5ustar debacledebaclepython-social-auth-0.2.21/docs/configuration/cherrypy.rst0000644000175500017550000000460712754357263023301 0ustar debacledebacleCherryPy Framework ================== CherryPy framework is supported, it works but I'm sure there's room for improvements. The implementation uses SQLAlchemy as ORM and expects some values accessible on ``cherrypy.request`` for it to work. At the moment the configuration is expected on ``cherrypy.config`` but ideally it should be an application configuration instead. Expected values are: ``cherrypy.request.user`` Current logged in user, load it in your application on a ``before_handler`` handler. ``cherrypy.request.db`` Current database session, again, load it in your application on a ``before_handler``. Dependencies ------------ The `CherryPy built-in application` depends on sqlalchemy_, there's no support for others ORMs yet but pull-requests are welcome. Enabling the application ------------------------ The application is defined on ``social.apps.cherrypy_app.views.CherryPyPSAViews``, register it in the preferred way for your project. Check the rest of the docs for the other settings like enabling authentication backends and backends keys. Models Setup ------------ The models are located in ``social.apps.cherrypy_app.models``. A reference to your ``User`` model is required to be defined in the project settings, it should be an import path, for example:: cherrypy.config.update({ 'SOCIAL_AUTH_USER_MODEL': 'models.User' }) Login mechanism --------------- By default the application sets the session value ``user_id``, this is a simple solution and it should be improved, if you want to provider your own login mechanism you can do it by defining the ``SOCIAL_AUTH_LOGIN_METHOD`` setting, it should be an import path to a callable, like this:: SOCIAL_AUTH_USER_MODEL = 'app.login_user' And an example of this function:: def login_user(strategy, user): strategy.session_set('user_id', user.id) Then, ensure to load the user in your application at ``cherrypy.request.user``, for example:: def load_user(): user_id = cherrypy.session.get('user_id') if user_id: cherrypy.request.user = cherrypy.request.db.query(User).get(user_id) else: cherrypy.request.user = None cherrypy.tools.authenticate = cherrypy.Tool('before_handler', load_user) .. _CherryPy built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/cherrypy_app .. _sqlalchemy: http://www.sqlalchemy.org/ python-social-auth-0.2.21/docs/configuration/flask.rst0000644000175500017550000001137312754357263022532 0ustar debacledebacleFlask Framework =============== Flask reusable applications are tricky (or I'm not capable enough). Here are details on how to enable this application on Flask. Dependencies ------------ The `Flask built-in app` depends on sqlalchemy_, there's initial support for MongoEngine_ ORM too (check below for more details). Enabling the application ------------------------ The applications define a `Flask Blueprint`_, which needs to be registered once the Flask app is configured by:: from social.apps.flask_app.routes import social_auth app.register_blueprint(social_auth) For MongoEngine_ you need this setting:: SOCIAL_AUTH_STORAGE = 'social.apps.flask_app.me.models.FlaskStorage' Models Setup ------------ At the moment the models for python-social-auth_ are defined inside a function because they need the reference to the current db instance and the User model used on your project (check *User model reference* below). Once the Flask app and the database are defined, call ``init_social`` to register the models:: from social.apps.flask_app.default.models import init_social init_social(app, db) For MongoEngine_:: from social.apps.flask_app.me.models import init_social init_social(app, db) So far I wasn't able to find another way to define the models on another way rather than making it as a side-effect of calling this function since the database is not available and ``current_app`` cannot be used on init time, just run time. User model reference -------------------- The application keeps a reference to the User model used by your project, define it by using this setting:: SOCIAL_AUTH_USER_MODEL = 'foobar.models.User' The value must be the import path to the User model. Global user ----------- The application expects the current logged in user accesible at ``g.user``, define a handler like this to ensure that:: @app.before_request def global_user(): g.user = get_current_logged_in_user Flask-Login ----------- The application works quite well with Flask-Login_, ensure to have some similar handlers to these:: @login_manager.user_loader def load_user(userid): try: return User.query.get(int(userid)) except (TypeError, ValueError): pass @app.before_request def global_user(): g.user = login.current_user # Make current user available on templates @app.context_processor def inject_user(): try: return {'user': g.user} except AttributeError: return {'user': None} Remembering sessions -------------------- The users session can be remembered when specified on login. The common implementation for this feature is to pass a parameter from the login form (``remember_me``, ``keep``, etc), to flag the action. Flask-Login_ will mark the session as persistent if told so. python-social-auth_ will check for a given name (``keep``) by default, but since providers won't pass parameters back to the application, the value must be persisted in the session before the authentication process happens. So, the following setting is required for this to work:: SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['keep'] It's possible to override the default name with this setting:: SOCIAL_AUTH_REMEMBER_SESSION_NAME = 'remember_me' Don't use the value ``remember`` since that will clash with Flask-Login_ which pops the value from the session. Then just pass the parameter ``keep=1`` as a GET or POST parameter. Exceptions handling ------------------- The Django application has a middleware (that fits in the framework architecture) to facilitate the different exceptions_ handling raised by python-social-auth_. The same can be accomplished (even on a simpler way) in Flask by defining an errorhandler_. For example the next code will redirect any social-auth exception to a ``/socialerror`` URL:: from social.exceptions import SocialAuthBaseException @app.errorhandler(500) def error_handler(error): if isinstance(error, SocialAuthBaseException): return redirect('/socialerror') Be sure to set your debug and test flags to ``False`` when testing this on your development environment, otherwise the exception will be raised and error handlers won't be called. .. _Flask Blueprint: http://flask.pocoo.org/docs/blueprints/ .. _Flask-Login: https://github.com/maxcountryman/flask-login .. _python-social-auth: https://github.com/omab/python-social-auth .. _Flask built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/flask_app .. _sqlalchemy: http://www.sqlalchemy.org/ .. _exceptions: https://github.com/omab/python-social-auth/blob/master/social/exceptions.py .. _errorhandler: http://flask.pocoo.org/docs/api/#flask.Flask.errorhandler .. _MongoEngine: http://mongoengine.org python-social-auth-0.2.21/docs/configuration/settings.rst0000644000175500017550000003016712754357263023274 0ustar debacledebacleConfiguration ============= Application setup ----------------- Once the application is installed (check Installation_) define the following settings to enable the application behavior. Also check the sections dedicated to each framework for detailed instructions. Settings name ------------- Almost all settings are prefixed with ``SOCIAL_AUTH_``, there are some exceptions for Django framework like ``AUTHENTICATION_BACKENDS``. All settings can be defined per-backend by adding the backend name to the setting name like ``SOCIAL_AUTH_TWITTER_LOGIN_URL``. Settings discovery is done by reducing the name starting with backend setting, then app setting and finally global setting, for example:: SOCIAL_AUTH_TWITTER_LOGIN_URL SOCIAL_AUTH_LOGIN_URL LOGIN_URL The backend name is generated from the ``name`` attribute from the backend class by uppercasing it and replacing ``-`` with ``_``. Keys and secrets ---------------- - Setup needed OAuth keys (see OAuth_ section for details):: SOCIAL_AUTH_TWITTER_KEY = 'foobar' SOCIAL_AUTH_TWITTER_SECRET = 'bazqux' OpenId backends don't require keys usually, but some need some API Key to call any API on the provider. Check Backends_ sections for details. Authentication backends ----------------------- Register the backends you plan to use, on Django framework use the usual ``AUTHENTICATION_BACKENDS`` settings, for others, define ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', ... ) URLs options ------------ These URLs are used on different steps of the auth process, some for successful results and others for error situations. ``SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/logged-in/'`` Used to redirect the user once the auth process ended successfully. The value of ``?next=/foo`` is used if it was present ``SOCIAL_AUTH_LOGIN_ERROR_URL = '/login-error/'`` URL where the user will be redirected in case of an error ``SOCIAL_AUTH_LOGIN_URL = '/login-url/'`` Is used as a fallback for ``LOGIN_ERROR_URL`` ``SOCIAL_AUTH_NEW_USER_REDIRECT_URL = '/new-users-redirect-url/'`` Used to redirect new registered users, will be used in place of ``SOCIAL_AUTH_LOGIN_REDIRECT_URL`` if defined. Note that ``?next=/foo`` is appended if present, if you want new users to go to next, you'll need to do it yourself. ``SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = '/new-association-redirect-url/'`` Like ``SOCIAL_AUTH_NEW_USER_REDIRECT_URL`` but for new associated accounts (user is already logged in). Used in place of ``SOCIAL_AUTH_LOGIN_REDIRECT_URL`` ``SOCIAL_AUTH_DISCONNECT_REDIRECT_URL = '/account-disconnected-redirect-url/'`` The user will be redirected to this URL when a social account is disconnected ``SOCIAL_AUTH_INACTIVE_USER_URL = '/inactive-user/'`` Inactive users can be redirected to this URL when trying to authenticate. Successful URLs will default to ``SOCIAL_AUTH_LOGIN_URL`` while error URLs will fallback to ``SOCIAL_AUTH_LOGIN_ERROR_URL``. User model ---------- ``UserSocialAuth`` instances keep a reference to the ``User`` model of your project, since this is not known, the ``User`` model must be configured by a setting:: SOCIAL_AUTH_USER_MODEL = 'foo.bar.User' ``User`` model must have a ``username`` and ``email`` field, these are required. Also an ``is_authenticated`` and ``is_active`` boolean flags are recommended, these can be methods if necessary (must return ``True`` or ``False``). If the model lacks them a ``True`` value is assumed. Tweaking some fields length --------------------------- Some databases impose limitations on index columns (like MySQL InnoDB). These limitations won't play nice on some ``UserSocialAuth`` fields. To avoid such errors, define some of the following settings. ``SOCIAL_AUTH_UID_LENGTH = `` Used to define the max length of the field `uid`. A value of 223 should work when using MySQL InnoDB which impose a 767 bytes limit (assuming UTF-8 encoding). ``SOCIAL_AUTH_NONCE_SERVER_URL_LENGTH = `` ``Nonce`` model has a unique constraint over ``('server_url', 'timestamp', 'salt')``, salt has a max length of 40, so ``server_url`` length must be tweaked using this setting. ``SOCIAL_AUTH_ASSOCIATION_SERVER_URL_LENGTH = `` or ``SOCIAL_AUTH_ASSOCIATION_HANDLE_LENGTH = `` ``Association`` model has a unique constraint over ``('server_url', 'handle')``, both fields lengths can be tweaked by these settings. Username generation ------------------- Some providers return a username, others just an ID or email or first and last names. The application tries to build a meaningful username when possible but defaults to generating one if needed. A UUID is appended to usernames in case of collisions. Here are some settings to control username generation. ``SOCIAL_AUTH_UUID_LENGTH = 16`` This controls the length of the UUID appended to usernames. ``SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = True`` If you want to use the full email address as the ``username``, define this setting. ``SOCIAL_AUTH_SLUGIFY_USERNAMES = False`` For those that prefer slugged usernames, the ``get_username`` pipeline can apply a slug transformation (code borrowed from Django project) by defining this setting to ``True``. The feature is disabled by default to to not force this option to all projects. ``SOCIAL_AUTH_CLEAN_USERNAMES = True`` By default the regex ``r'[^\w.@+-_]+'`` is applied over usernames to clean them from usual undesired characters like spaces. Set this setting to ``False`` to disable this behavior. Extra arguments on auth processes --------------------------------- Some providers accept particular GET parameters that produce different results during the auth process, usually used to show different dialog types (mobile version, etc). You can send extra parameters on auth process by defining settings per backend, example to request Facebook to show Mobile authorization page, define:: FACEBOOK_AUTH_EXTRA_ARGUMENTS = {'display': 'touch'} For other providers, just define settings in the form:: SOCIAL_AUTH__AUTH_EXTRA_ARGUMENTS = {...} Also, you can send extra parameters on request token process by defining settings in the same way explained above but with this other suffix:: SOCIAL_AUTH__REQUEST_TOKEN_EXTRA_ARGUMENTS = {...} Basic information is requested to the different providers in order to create a coherent user instance (with first and last name, email and full name), this could be too intrusive for some sites that want to ask users the minimum data possible. It's possible to override the default values requested by defining any of the following settings, for Open Id providers:: SOCIAL_AUTH__IGNORE_DEFAULT_AX_ATTRS = True SOCIAL_AUTH__AX_SCHEMA_ATTRS = [ (schema, alias) ] For OAuth backends:: SOCIAL_AUTH__IGNORE_DEFAULT_SCOPE = True SOCIAL_AUTH__SCOPE = [ ... ] Processing redirects and urlopen -------------------------------- The application issues several redirects and API calls. The following settings allow some tweaks to the behavior of these. ``SOCIAL_AUTH_SANITIZE_REDIRECTS = False`` The auth process finishes with a redirect, by default it's done to the value of ``SOCIAL_AUTH_LOGIN_REDIRECT_URL`` but can be overridden with ``next`` GET argument. If this setting is ``True``, this application will vary the domain of the final URL and only redirect to it if it's on the same domain. ``SOCIAL_AUTH_REDIRECT_IS_HTTPS = False`` On projects behind a reverse proxy that uses HTTPS, the redirect URIs can have the wrong schema (``http://`` instead of ``https://``) if the request lacks the appropriate headers, which might cause errors during the auth process. To force HTTPS in the final URIs set this setting to ``True`` ``SOCIAL_AUTH_URLOPEN_TIMEOUT = 30`` Any ``urllib2.urlopen`` call will be performed with the default timeout value, to change it without affecting the global socket timeout define this setting (the value specifies timeout seconds). ``urllib2.urlopen`` uses ``socket.getdefaulttimeout()`` value by default, so setting ``socket.setdefaulttimeout(...)`` will affect ``urlopen`` when this setting is not defined, otherwise this setting takes precedence. Also this might affect other places in Django. ``timeout`` argument was introduced in python 2.6 according to `urllib2 documentation`_ Whitelists ---------- Registration can be limited to a set of users identified by their email address or domain name. To white-list just set any of these settings: ``SOCIAL_AUTH__WHITELISTED_DOMAINS = ['foo.com', 'bar.com']`` Supply a list of domain names to be white-listed. Any user with an email address on any of the allowed domains will login successfully, otherwise ``AuthForbidden`` is raised. ``SOCIAL_AUTH__WHITELISTED_EMAILS = ['me@foo.com', 'you@bar.com']`` Supply a list of email addresses to be white-listed. Any user with an email address in this list will login successfully, otherwise ``AuthForbidden`` is raised. Miscellaneous settings ---------------------- ``SOCIAL_AUTH_PROTECTED_USER_FIELDS = ['email',]`` During the pipeline process a ``dict`` named ``details`` will be populated with the needed values to create the user instance, but it's also used to update the user instance. Any value in it will be checked as an attribute in the user instance (first by doing ``hasattr(user, name)``). Usually there are attributes that cannot be updated (like ``username``, ``id``, ``email``, etc.), those fields need to be *protect*. Set any field name that requires *protection* in this setting, and it won't be updated. ``SOCIAL_AUTH_SESSION_EXPIRATION = False`` By default, user session expiration time will be set by your web framework (in Django, for example, it is set with `SESSION_COOKIE_AGE`_). Some providers return the time that the access token will live, which is stored in ``UserSocialAuth.extra_data`` under the key ``expires``. Changing this setting to True will override your web framework's session length setting and set user session lengths to match the ``expires`` value from the auth provider. ``SOCIAL_AUTH_OPENID_PAPE_MAX_AUTH_AGE = `` Enable `OpenID PAPE`_ extension support by defining this setting. ``SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['foo',]`` If you want to store extra parameters from POST or GET in session, like it was made for ``next`` parameter, define this setting with the parameter names. In this case ``foo`` field's value will be stored when user follows this link ``...``. ``SOCIAL_AUTH_PASSWORDLESS = False`` When this setting is ``True`` and ``social.pipeline.mail.send_validation`` is enabled, it allows the implementation of a `passwordless authentication mechanism`_. Example of this implementation can be found at psa-passwordless_. Account disconnection --------------------- Disconnect is an side-effect operation and should be done by POST method only, some CSRF protection is encouraged (and enforced on Django app). Ensure that any call to `/disconnect//` or `/disconnect///` is done using POST. .. _urllib2 documentation: http://docs.python.org/library/urllib2.html#urllib2.urlopen .. _OpenID PAPE: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0.html .. _Installation: ../installing.html .. _Backends: ../backends/index.html .. _OAuth: http://oauth.net/ .. _passwordless authentication mechanism: https://medium.com/@ninjudd/passwords-are-obsolete-9ed56d483eb .. _psa-passwordless: https://github.com/omab/psa-passwordless .. _SESSION_COOKIE_AGE: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-SESSION_COOKIE_AGE python-social-auth-0.2.21/docs/configuration/porting_from_dsa.rst0000644000175500017550000001203512754357263024762 0ustar debacledebaclePorting from django-social-auth =============================== Being a derivative work from django-social-auth_, porting from it to python-social-auth_ should be an easy task. Porting to others libraries usually is a pain, I'm trying to make this as easy as possible. Installed apps -------------- On django-social-auth_ there was a single application to add into ``INSTALLED_APPS`` plus a setting to define which ORM to be used (default or MongoEngine). Now the apps are split and there's not need for that extra setting. When using the default ORM:: INSTALLED_APPS = ( ... 'social.apps.django_app.default', ... ) And when using MongoEngine:: INSTALLED_APPS = ( ... 'social.apps.django_app.me', ... ) The models table names were defined to be compatible with those used on django-social-auth_, so data is not needed to be migrated. URLs ---- The URLs are namespaced, you can chose your namespace, the `example app`_ uses the ``social`` namespace. Replace the old include with:: urlpatterns = patterns('', ... url('', include('social.apps.django_app.urls', namespace='social')) ... ) On templates use a namespaced URL:: {% url 'social:begin' "google-oauth2" %} Account disconnection URL would be:: {% url 'social:disconnect_individual' provider, id %} Porting settings ---------------- All python-social-auth_ settings are prefixed with ``SOCIAL_AUTH_``, except for some exception on Django framework, ``AUTHENTICATION_BACKENDS`` remains the same for obvious reasons. All backends settings have the backend name into it, all uppercase and with dashes replaced with underscores, take for instance Google OAuth2 backend is named ``google-oauth2``, any setting name related to that backend should start with ``SOCIAL_AUTH_GOOGLE_OAUTH2_``. Keys and secrets are some mandatory settings needed for OAuth providers, to keep consistency the names follow the same naming convention ``*_KEY`` for the application key, and ``*_SECRET`` for the secret. OAuth1 backends use to have ``CONSUMER`` in the setting name, not anymore. Following with the Google OAuth2 example:: SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '...' SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '...' Remember that the name of the backend is needed in the settings, and names differ a little from backend to backend, like `Facebook OAuth2 backend`_ name is ``facebook``. So the settings should be:: SOCIAL_AUTH_FACEBOOK_KEY = '...' SOCIAL_AUTH_FACEBOOK_SECRET = '...' Authentication backends ----------------------- Import path for authentication backends changed a little, there's no more ``contrib`` module, there's no need for it. Some backends changed the names to have some consistency, check the backends, it should be easy to track the names changes. Examples of the new import paths:: AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.facebook.FacebookOAuth2', ) Session ------- Django stores the last authentication backend used in the user session as an import path, this can cause import troubles when porting since the old import paths aren't valid anymore. Some solutions to this problem are: 1. Clean the session and force the users to login again in your site 2. Run a migration script that will update the authentication backend session value for each session in your database. This implies figuring out the new import path for each backend you have configured, which is the value used in ``AUTHENTICATION_BACKENDS`` setting. `@tomgruner`_ created a Gist here_ that updates the value just for Facebook backend. A ``template`` for this script would look like this:: from django.contrib.sessions.models import Session BACKENDS = { 'social_auth.backends.facebook.FacebookBackend': 'social.backends.facebook.FacebookOAuth2' } for sess in Session.objects.iterator(): session_dict = sess.get_decoded() if '_auth_user_backend' in session_dict.keys(): # Change old backend import path from new backend import path if session_dict['_auth_user_backend'].startswith('social_auth'): session_dict['_auth_user_backend'] = BACKENDS[session_dict['_auth_user_backend']] new_sess = Session.objects.save(sess.session_key, session_dict, sess.expire_date) print 'New session saved {}'.format(new_sess.pk) .. _django-social-auth: https://github.com/omab/django-social-auth .. _python-social-auth: https://github.com/omab/python-social-auth .. _example app: https://github.com/omab/python-social-auth/blob/master/examples/django_example/example/urls.py#L17 .. _Facebook OAuth2 backend: https://github.com/omab/python-social-auth/blob/master/social/backends/facebook.py#L29 .. _@tomgruner: https://github.com/tomgruner .. _here: https://gist.github.com/tomgruner/5ce8bb1f4c55d17b5b25 python-social-auth-0.2.21/docs/configuration/django.rst0000644000175500017550000001540212754357263022671 0ustar debacledebacleDjango Framework ================ Django framework has a little more support since this application was derived from `django-social-auth`_. Here are some details on configuring this application on Django. Register the application ------------------------ The `Django built-in app`_ comes with two ORMs, one for default Django ORM and another for MongoEngine_ ORM. Add the application to ``INSTALLED_APPS`` setting, for default ORM:: INSTALLED_APPS = ( ... 'social.apps.django_app.default', ... ) And for MongoEngine_ ORM:: INSTALLED_APPS = ( ... 'social.apps.django_app.me', ... ) Also ensure to define the MongoEngine_ storage setting:: SOCIAL_AUTH_STORAGE = 'social.apps.django_app.me.models.DjangoStorage' Database -------- (For Django 1.7 and higher) sync database to create needed models:: ./manage.py migrate If you're still using South, you'll need override SOUTH_MIGRATION_MODULES_:: SOUTH_MIGRATION_MODULES = { 'default': 'social.apps.django_app.default.south_migrations' } Note that Django's app labels take the last part of the import, so in this case ``social.apps.django_app.default`` becomes ``default`` here. Sync database to create needed models:: ./manage.py syncdb Authentication backends ----------------------- Add desired authentication backends to Django's AUTHENTICATION_BACKENDS_ setting:: AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', ... 'django.contrib.auth.backends.ModelBackend', ) Take into account that backends **must** be defined in AUTHENTICATION_BACKENDS_ or Django won't pick them when trying to authenticate the user. Don't miss ``django.contrib.auth.backends.ModelBackend`` if using ``django.contrib.auth`` application or users won't be able to login by username / password method. URLs entries ------------ Add URLs entries:: urlpatterns = patterns('', ... url('', include('social.apps.django_app.urls', namespace='social')) ... ) In case you need a custom namespace, this setting is also needed:: SOCIAL_AUTH_URL_NAMESPACE = 'social' Template Context Processors --------------------------- There's a context processor that will add backends and associations data to template context:: TEMPLATE_CONTEXT_PROCESSORS = ( ... 'social.apps.django_app.context_processors.backends', 'social.apps.django_app.context_processors.login_redirect', ... ) ``backends`` context processor will load a ``backends`` key in the context with three entries on it: ``associated`` It's a list of ``UserSocialAuth`` instances related with the currently logged in user. Will be empty if there's no current user. ``not_associated`` A list of available backend names not associated with the current user yet. If there's no user logged in, it will be a list of all available backends. ``backends`` A list of all available backend names. ORMs ---- As detailed above the built-in Django application supports default ORM and MongoEngine_ ORM. When using MongoEngine_ make sure you've followed the instructions for `MongoEngine Django integration`_, as you're now utilizing that user model. The `MongoEngine_` backend was developed and tested with version 0.6.10 of `MongoEngine_`. Alternate storage models implementations currently follow a tight pattern of models that behave near or identical to Django ORM models. It is currently not decoupled from this pattern by any abstraction layer. If you would like to implement your own alternate, please see the ``social.apps.django_app.default.models`` and ``social.apps.django_app.me.models`` modules for guidance. Exceptions Middleware --------------------- A base middleware is provided that handles ``SocialAuthBaseException`` by providing a message to the user via the Django messages framework, and then responding with a redirect to a URL defined in one of the middleware methods. The middleware is at ``social.apps.django_app.middleware.SocialAuthExceptionMiddleware``. Any method can be overridden, but for simplicity these two are recommended:: get_message(request, exception) get_redirect_uri(request, exception) By default, the message is the exception message and the URL for the redirect is the location specified by the ``LOGIN_ERROR_URL`` setting. If a valid backend was detected by ``strategy()`` decorator, it will be available at ``request.strategy.backend`` and ``process_exception()`` will use it to build a backend-dependent redirect URL but fallback to default if not defined. Exception processing is disabled if any of this settings is defined with a ``True`` value:: _SOCIAL_AUTH_RAISE_EXCEPTIONS = True SOCIAL_AUTH_RAISE_EXCEPTIONS = True RAISE_EXCEPTIONS = True DEBUG = True The redirect destination will get two ``GET`` parameters: ``message = ''`` Message from the exception raised, in some cases it's the message returned by the provider during the auth process. ``backend = ''`` Backend name that was used, if it was a valid backend. Django Admin ------------ The default application (not the MongoEngine_ one) contains an ``admin.py`` module that will be auto-discovered by the usual mechanism. But, by the nature of the application which depends on the existence of a user model, it's easy to fall in a recursive import ordering making the application fail to load. This happens because the admin module will build a set of fields to populate the ``search_fields`` property to search for related users in the administration UI, but this requires the user model to be retrieved which might not be defined at that time. To avoid this issue define the following setting to circumvent the import error:: SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ['field1', 'field2'] For example:: SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ['username', 'first_name', 'email'] The fields listed **must** be user models fields. .. _MongoEngine: http://mongoengine.org .. _MongoEngine Django integration: http://mongoengine-odm.readthedocs.org/en/latest/django.html .. _django-social-auth: https://github.com/omab/django-social-auth .. _Django built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/django_app .. _AUTHENTICATION_BACKENDS: http://docs.djangoproject.com/en/dev/ref/settings/?from=olddocs#authentication-backends .. _django@dc43fbc: https://github.com/django/django/commit/dc43fbc2f21c12e34e309d0e8a121020391aa03a .. _SOUTH_MIGRATION_MODULES: http://south.readthedocs.org/en/latest/settings.html#south-migration-modules python-social-auth-0.2.21/docs/configuration/index.rst0000644000175500017550000000101112754357263022525 0ustar debacledebacleConfiguration ============= All the apps share the settings names, some settings for Django framework are special (like ``AUTHENTICATION_BACKENDS``). Below there's a main settings document detailing each configuration and its purpose, plus sections detailed for each framework and their particularities. Support for more frameworks will be added in the future, pull-requests are very welcome. Contents: .. toctree:: :maxdepth: 2 settings django flask pyramid cherrypy webpy porting_from_dsa python-social-auth-0.2.21/docs/configuration/pyramid.rst0000644000175500017550000001101512754357263023070 0ustar debacledebaclePyramid Framework ================= Pyramid_ reusable applications are tricky (or I'm not capable enough). Here are details on how to enable this application on Pyramid. Dependencies ------------ The `Pyramid built-in app`_ depends on sqlalchemy_, there's no support for others ORMs yet but pull-requests are welcome. Enabling the application ------------------------ The application can be scanned by ``Configurator.scan()``, also it defines an ``includeme()`` in the ``__init__.py`` file which will add the needed routes to your application configuration. To scan it just add:: config.include('social.apps.pyramid_app') config.scan('social.apps.pyramid_app') Models Setup ------------ At the moment the models for python-social-auth_ are defined inside a function because they need the reference to the current DB instance and the User model used on your project (check *User model reference* below). Once the Pyramid application configuration and database are defined, call ``init_social`` to register the models:: from social.apps.pyramid_app.models import init_social init_social(config, Base, DBSession) So far I wasn't able to find another way to define the models on another way rather than making it as a side-effect of calling this function since the database is not available and ``current_app`` cannot be used on initialization time, just run time. User model reference -------------------- The application keeps a reference to the User model used by your project, define it by using this setting:: SOCIAL_AUTH_USER_MODEL = 'foobar.models.User' The value must be the import path to the User model. Global user ----------- The application expects the current logged in user accessible at ``request.user``, the example application ensures that with this hander:: def get_user(request): user_id = request.session.get('user_id') if user_id: user = DBSession.query(User)\ .filter(User.id == user_id)\ .first() else: user = None return user The handler is added to the configuration doing:: config.add_request_method('example.auth.get_user', 'user', reify=True) This is just a simple example, probably your project does it in a better way. User login ---------- Since the application doesn't make any assumption on how you are going to login the users, you need to specify it. In order to do that, define these settings:: SOCIAL_AUTH_LOGIN_FUNCTION = 'example.auth.login_user' SOCIAL_AUTH_LOGGEDIN_FUNCTION = 'example.auth.login_required' The first one must accept the strategy used and the user instance that was created or retrieved from the database, there you can set the user id in the session or cookies or whatever place used later to retrieve the id again and load the user from the database (check the snippet above in *Global User*). The second one is used to ensure that there's a user logged in when calling the disconnect view. It must accept a ``User`` instance and return ``True`` or ``Flase``. Check the auth.py_ in the example application for details on how it's done there. Social auth in templates context -------------------------------- To access the social instances related to a user in the template context, you can do so by accessing the ``social_auth`` attribute in the user instance::
  • ${social.provider}
  • Also you can add the backends (associated and not associated to a user) by enabling this context function in your project:: from pyramid.events import subscriber, BeforeRender from social.apps.pyramid_app.utils import backends @subscriber(BeforeRender) def add_social(event): request = event['request'] event.update(backends(request, request.user)) That will load a dict with entries:: { 'associated': [...], 'not_associated': [...], 'backends': [...] } The ``associated`` key will have all the associated ``UserSocialAuth`` instances related to the given user. ``not_associated`` will have the backends names not associated and backends will have all the enabled backends names. .. _Pyramid: http://www.pylonsproject.org/projects/pyramid/about .. _python-social-auth: https://github.com/omab/python-social-auth .. _Pyramid built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/pyramid_app .. _sqlalchemy: http://www.sqlalchemy.org/ .. _auth.py: https://github.com/omab/python-social-auth/blob/master/examples/pyramid_example/example/auth.py python-social-auth-0.2.21/docs/configuration/webpy.rst0000644000175500017550000000363212754357263022557 0ustar debacledebacleWebpy Framework =============== Webpy_ framework is easy to setup, once that python-social-auth_ is installed or accessible in the ``PYTHONPATH``, just add the needed configurations to make it run. Dependencies ------------ The `Webpy built-in app` depends on sqlalchemy_, there's no support for others ORMs yet but pull-requests are welcome. Configuration ------------- Add the needed settings into ``web.config`` store. Settings are prefixed with ``SOCIAL_AUTH_`` but there's a helper for it:: from social.utils import setting_name web.config[setting_name('USER_MODEL')] = 'models.User' web.config[setting_name('LOGIN_REDIRECT_URL')] = '/done/' web.config[setting_name('AUTHENTICATION_BACKENDS')] = ( 'social.backends.google.GoogleOAuth2', ... ) Add all the settings needed for the app (check Configuration_ section for details). URLs ---- Add the social application into URLs:: from social.apps.webpy_app import app as social_app urls = ( ... '', social_app.app_social ... ) Session ------- python-social-auth_ depends on sessions storage to keep some essential values, usually redirects and ``state`` parameters used to validate authentication process on OAuth providers. The `Webpy built-in app` expects the session reference to be available under ``web.web_session`` so ensure it's available there. User model ---------- Like the other apps, the User model must be defined on settings since a reference to it is kept on ``UserSocialAuth`` instance. Define like this:: web.config[setting_name('USER_MODEL')] = 'models.User' Where the value is the import path to the User model used on your project. .. _python-social-auth: https://github.com/omab/python-social-auth .. _Webpy: http://webpy.org/ .. _Webpy built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/webpy_app .. _sqlalchemy: http://www.sqlalchemy.org/ python-social-auth-0.2.21/docs/developer_intro.rst0000644000175500017550000001535512754357263021767 0ustar debacledebacleBeginners Guide =============== This is an attempt to bring together a number of concepts in python-social-auth (psa) so that you will understand how it fits into your system. This definitely has a Django flavor to it (because that's how I learned it). Understanding PSA URLs ----------------------- If you have not seen namespaced URLs before, you are about to be introduced. When you add the PSA entry to your urls.py, it looks like this:: url(r'', include('social.apps.django_app.urls', namespace='social')) that "namespace" part on the end is what keeps the names in the PSA-world from colliding with the names in your app, or other 3rd-party apps. So your login link will look like this:: Login (See how "social" in the URL mapping matches the value of "namespace" in the urls.py entry?) Understanding Backends ---------------------- PSA implements a lot of backends. Find the entry in the docs for your backend, and if it's there, follow the steps to enable it, which come down to 1) Set up SOCIAL_AUTH_{backend} variables in settings.py. (The settings vary, based on the backends) 2) Adding your backend to AUTHENTICATION_BACKENDS in settings.py. If you need to implement a different backend (for instance, let's say you want to use Intuit's OpenID), you can subclass the nearest one and override the "name" attribute:: from social.backends.open_id import OpenIDAuth class IntuitOpenID(OpenIDAuth): name = 'intuit' And then add your new backend to AUTHENTICATION_BACKENDS in settings.py. A couple notes about the pipeline: The standard pipeline does not log the user in until after the pipeline has completed. So if you get a value in the user key of the accumulative dictionary, that implies that the user was logged in when the process started. Understanding the Pipeline -------------------------- Reversing a URL like ``{% url 'social:begin' 'github' %}`` will give you a url like:: http://example.com/login/github And clicking on that link will cause the "pipeline" to be started. The pipeline is a list of functions that build up data about the user as we go through the steps of the authentication process. (If you really want to understand the pipeline, look at the source in ``social/backends/base.py``, and see the ``run_pipeline()`` function in ``BaseAuth``.) The design contract for each function in the pipeline is: 1) The pipeline starts with a four-item dictionary (the accumulative dictionary) which is updated with the results of each function in the pipeline. The initial four values are: ``strategy`` contains a strategy object ``backend`` contains the backend being used during this pipeline run ``request`` contains a dictionary of the request keys. Note to Django users -- this is not an HttpRequest object, it is actually the results of ``request.REQUEST``. ``details`` which is an empty dict. 2) If the function returns a dictionary or something False-ish, add the contents of the dictionary to an accumulative dictionary (called ``out`` in ``run_pipeline``), and call the next step in the pipeline with the accumulative dictionary. 3) If something else is returned (for example, a subclass of ``HttpResponse``), then return that to the browser. 4) If the pipeline completes, *THEN* the user is authenticated (logged in). So if you are finding an authenticated user object while the pipeline is running, that means that the user was logged in when the pipeline started. There is one pipeline for your site as a whole -- if you have backend-specific logic, you have to make your pipeline steps smart enough to skip the step if it is not relevant. This is as simple as:: def my_custom_step(strategy, backend, request, details, *args, **kwargs): if backend_name != 'my_custom_backend': return # otherwise, do the special steps for your custom backend Interrupting the Pipeline (and communicating with views) --------------------------------------------------------- Let's say you want to add a custom step in the pipeline -- you want the user to establish a password so that they can come directly to your site in the future. We can do that with the @partial decorator, which tells the pipeline to keep track of where it is so that it can be restarted. The first thing we need to do is set up a way for our views to communicate with the pipeline. That is done by adding a value to the settings file to tell us which values should be passed back and forth between the Django session and the pipeline:: SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['local_password',] In our pipeline code, we would have:: from django.shortcuts import redirect from django.contrib.auth.models import User from social.pipeline.partial import partial # partial says "we may interrupt, but we will come back here again" @partial def collect_password(strategy, backend, request, details, *args, **kwargs): # request['local_password'] is set by the pipeline infrastructure # because it exists in FIELDS_STORED_IN_SESSION if not request.get('local_password', None): # if we return something besides a dict or None, then that is # returned to the user -- in this case we will redirect to a # view that can be used to get a password return redirect("myapp.views.collect_password") # grab the user object from the database (remember that they may # not be logged in yet) and set their password. (Assumes that the # email address was captured in an earlier step.) user = User.objects.get(email=kwargs['email']) user.set_password(request['local_password']) user.save() # continue the pipeline return In our view code, we would have something like:: class PasswordForm(forms.Form): secret_word = forms.CharField(max_length=10) def get_user_password(request): if request.method == 'POST': form = PasswordForm(request.POST) if form.is_valid(): # because of FIELDS_STORED_IN_SESSION, this will get copied # to the request dictionary when the pipeline is resumed request.session['local_password'] = form.cleaned_data['secret_word'] # once we have the password stashed in the session, we can # tell the pipeline to resume by using the "complete" endpoint return redirect(reverse('social:complete', args=("backend_name,"))) else: form = PasswordForm() return render(request, "password_form.html") Note that the ``social:complete`` will re-enter the pipeline with the same function that interrupted it (in this case, collect_password). python-social-auth-0.2.21/docs/tests.rst0000644000175500017550000000276712754357263017734 0ustar debacledebacleTesting python-social-auth ========================== Testing the application is fair simple, just met the dependencies and run the testing suite. The testing suite uses HTTPretty_ to mock server responses, it's not a live test against the providers API, to do it that way, a browser and a tool like Selenium are needed, that's slow, prone to errors on some cases, and some of the application examples must be running to perform the testing. Plus real Key and Secret pairs, in the end it's a mess to test functionality which is the real point. By mocking the server responses, we can test the backends functionality (and other areas too) easily and quick. Installing dependencies ----------------------- Go to the tests_ directory and install the dependencies listed in the requirements.txt_. Then run with ``nosetests`` command, or with the ``run_tests.sh`` script. Tox --- You can use tox_ to test compatibility against all supported Python versions: .. code-block:: bash $ pip install tox # if not present $ tox Pending ------- At the moment only OAuth1, OAuth2 and OpenId backends are being tested, and just login and partial pipeline features are covered by the test. There's still a lot to work on, like: * Frameworks support .. _HTTPretty: https://github.com/gabrielfalcao/HTTPretty .. _tests: https://github.com/omab/python-social-auth/tree/master/tests .. _requirements.txt: https://github.com/omab/python-social-auth/blob/master/tests/requirements.txt .. _tox: http://tox.readthedocs.org/ python-social-auth-0.2.21/docs/installing.rst0000644000175500017550000000247312754357263020730 0ustar debacledebacleInstallation ============ Dependencies ------------ Dependencies that **must** be met to use the application: - OpenId_ support depends on python-openid_ - OAuth_ support depends on requests-oauthlib_ - Several backends demands application registration on their corresponding sites and other dependencies like sqlalchemy_ on Flask and Webpy. Get a copy ---------- From pypi_:: $ pip install python-social-auth Or:: $ easy_install python-social-auth Or clone from github_:: $ git clone git://github.com/omab/python-social-auth.git And add social to ``PYTHONPATH``:: $ export PYTHONPATH=$PYTHONPATH:$(pwd)/python-social-auth/ Or:: $ cd python-social-auth $ sudo python setup.py install .. _OpenId: http://openid.net/ .. _OAuth: http://oauth.net/ .. _pypi: http://pypi.python.org/pypi/python-social-auth/ .. _github: https://github.com/omab/python-social-auth .. _python-openid: http://pypi.python.org/pypi/python-openid/ .. _requests-oauthlib: https://requests-oauthlib.readthedocs.org/ .. _sqlalchemy: http://www.sqlalchemy.org/ Upgrading --------- Django with South ~~~~~~~~~~~~~~~~~ Upgrading from 0.1 to 0.2 is likely to cause problems trying to apply a migration when the tables already exist. In this case a fake migration needs to be applied: $ python manage.py migrate --fake default python-social-auth-0.2.21/docs/index.rst0000644000175500017550000000216112754357263017665 0ustar debacledebacleWelcome to Python Social Auth's documentation! ============================================== Python Social Auth aims to be an easy to setup social authentication and authorization mechanism for Python projects supporting protocols like OAuth (1 and 2), OpenId and others. The initial codebase is derived from django-social-auth_ with the idea of generalizing the process to suit the different frameworks around, providing the needed tools to bring support to new frameworks. django-social-auth_ itself was a product of modified code from django-twitter-oauth_ and django-openid-auth_ projects. Contents: .. toctree:: :maxdepth: 2 intro installing configuration/index pipeline strategies storage exceptions backends/index developer_intro logging_out tests use_cases thanks copyright Indices and Tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _django-social-auth: http://github.com/omab/django-social-auth .. _django-twitter-oauth: https://github.com/henriklied/django-twitter-oauth .. _django-openid-auth: https://launchpad.net/django-openid-auth python-social-auth-0.2.21/docs/backends/0000755000175500017550000000000012754357263017576 5ustar debacledebaclepython-social-auth-0.2.21/docs/backends/skyrock.rst0000644000175500017550000000122112754357263022011 0ustar debacledebacleSkyrock ======= OAuth based Skyrock Connect. Skyrock offers per application keys named ``Consumer Key`` and ``Consumer Secret``. To enable Skyrock these two keys are needed. Further documentation at `Skyrock developer resources`_: - Register a new application at `Skyrock App Creation`_, - Your callback domain should match your application URL in your application configuration. - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_SKYROCK_KEY = '' SOCIAL_AUTH_SKYROCK_SECRET = '' .. _Skyrock developer resources: http://www.skyrock.com/developer/ .. _Skyrock App Creation: https://wwwskyrock.com/developer/application python-social-auth-0.2.21/docs/backends/changetip.rst0000644000175500017550000000113212754357263022267 0ustar debacledebacleChangeTip ========= ChangeTip - Register a new application at ChangeTip_, set the callback URL to ``http://example.com/complete/changetip/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_CHANGETIP_KEY = '' SOCIAL_AUTH_CHANGETIP_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_CHANGETIP_SCOPE = [...] See auth scopes at `ChangeTip OAuth docs`_. .. _ChangeTip: https://www.changetip.com/api .. _ChangeTip OAuth docs: https://www.changetip.com/api/auth/#!#scopes python-social-auth-0.2.21/docs/backends/taobao.rst0000644000175500017550000000054012754357263021574 0ustar debacledebacleTaobao OAuth ============ Taobao OAuth 2.0 workflow. - Register a new application at Open `Open Taobao`_. - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_TAOBAO_KEY = '' SOCIAL_AUTH_TAOBAO_SECRET = '' By default ``token`` is stored in ``extra_data`` field. .. _Open Taobao: http://open.taobao.com python-social-auth-0.2.21/docs/backends/rdio.rst0000644000175500017550000000162712754357263021273 0ustar debacledebacleRdio ==== Rdio provides OAuth 1 and 2 support for their authentication process. OAuth 1.0a ---------- To setup Rdio OAuth 1.0a, add the following to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.rdio.RdioOAuth1', ... ) SOCIAL_AUTH_RDIO_OAUTH1_KEY = '' SOCIAL_AUTH_RDIO_OAUTH1_SECRET = '' OAuth 2.0 --------- To setup Rdio OAuth 2.0, add the following to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.rdio.RdioOAuth2', ... ) SOCIAL_AUTH_RDIO_OAUTH2_KEY = os.environ['RDIO_OAUTH2_KEY'] SOCIAL_AUTH_RDIO_OAUTH2_SECRET = os.environ['RDIO_OAUTH2_SECRET'] SOCIAL_AUTH_RDIO_OAUTH2_SCOPE = [] Extra Fields ------------ The following extra fields are automatically requested: - rdio_id - rdio_icon_url - rdio_profile_url - rdio_username - rdio_stream_region python-social-auth-0.2.21/docs/backends/shopify.rst0000644000175500017550000000173512754357263022017 0ustar debacledebacleShopify ======= Shopify uses OAuth 2 for authentication. To use this backend, you must install the package ``shopify`` from the `Github project`_. Currently supports v2+ - Register a new application at `Shopify Partners`_, and - Set the Auth Type to OAuth2 in the application settings - Set the Application URL to http://[your domain]/login/shopify/ - fill ``API Key`` and ``Shared Secret`` values in your django settings:: SOCIAL_AUTH_SHOPIFY_KEY = '' SOCIAL_AUTH_SHOPIFY_SECRET = '' - fill the scope permissions that you require into the settings `Shopify API`_:: SOCIAL_AUTH_SHOPIFY_SCOPE = ['write_script_tags', 'read_orders', 'write_customers', 'read_products'] .. _Shopify Partners: http://www.shopify.com/partners .. _Shopify API: http://api.shopify.com/authentication.html#scopes .. _Github project: https://github.com/Shopify/shopify_python_api python-social-auth-0.2.21/docs/backends/meetup.rst0000644000175500017550000000061212754357263021626 0ustar debacledebacleMeetup ====== Meetup.com uses OAuth2 for its auth mechanism. - Register a new OAuth Consumer at `Meetup Consumer Registration`_, set your consumer name, redirect uri. - Fill ``key`` and ``secret`` values in the settings:: SOCIAL_AUTH_MEETUP_KEY = '' SOCIAL_AUTH_MEETUP_SECRET = '' .. _Meetup Consumer Registration: https://secure.meetup.com/meetup_api/oauth_consumers/create python-social-auth-0.2.21/docs/backends/pinterest.rst0000644000175500017550000000143412754357263022347 0ustar debacledebaclePinterest ========= Pinterest implemented OAuth2 protocol for their authentication mechanism. To enable ``python-social-auth`` support follow this steps: 1. Go to `Pinterest developers zone`_ and create an application. 2. Fill App Id and Secret in your project settings:: SOCIAL_AUTH_PINTEREST_KEY = '...' SOCIAL_AUTH_PINTEREST_SECRET = '...' SOCIAL_AUTH_PINTEREST_SCOPE = [ 'read_public', 'write_public', 'read_relationships', 'write_relationships' ] 3. Enable the backend:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.pinterest.PinterestOAuth2', ... ) .. _Pinterest developers zone: https://developers.pinterest.com/apps/ .. _Pinterest Documentation: https://developers.pinterest.com/docs/ python-social-auth-0.2.21/docs/backends/readability.rst0000644000175500017550000000106612754357263022624 0ustar debacledebacleReadability =========== Readability works similarly to Twitter, in that you'll need a ``Consumer Key`` and ``Consumer Secret``. These can be obtained in the ``Connections`` section of your ``Account`` page. - Fill the **Consumer Key** and **Consumer Secret** values in your settings:: SOCIAL_AUTH_READABILITY_KEY = '' SOCIAL_AUTH_READABILITY_SECRET = '' That's it! By default you'll get back:: username first_name last_name with EXTRA_DATA, you can get:: date_joined kindle_email_address avatar_url email_into_address python-social-auth-0.2.21/docs/backends/beats.rst0000644000175500017550000000101612754357263021424 0ustar debacledebacleBeats ===== Beats supports OAuth 2. - Register a new application at `Beats Music API`_, and follow the instructions below. OAuth2 ------ Add the Beats OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.beats.BeatsOAuth2', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_BEATS_OAUTH2_KEY = '' SOCIAL_AUTH_BEATS_OAUTH2_SECRET = '' .. _Beats Music API: https://developer.beatsmusic.com/docs python-social-auth-0.2.21/docs/backends/vk.rst0000644000175500017550000001040212754357263020745 0ustar debacledebacleVK.com (former Vkontakte) ========================= VK.com (former Vkontakte) auth service support. OAuth2 ------ VK.com uses OAuth2 for Authentication. - Register a new application at the `VK.com API`_, - fill ``Application Id`` and ``Application Secret`` values in the settings:: SOCIAL_AUTH_VK_OAUTH2_KEY = '' SOCIAL_AUTH_VK_OAUTH2_SECRET = '' - Add ``'social.backends.vk.VKOAuth2'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. - Then you can start using ``/login/vk-oauth2`` in your link href. - Also it's possible to define extra permissions with:: SOCIAL_AUTH_VK_OAUTH2_SCOPE = [...] See the `VK.com list of permissions`_. OAuth2 Application ------------------ To support OAuth2 authentication for VK.com applications: - Create your IFrame application at VK.com. - In application settings specify your IFrame URL ``mysite.com/vk`` (current default). - Fill ``Application ID`` and ``Application Secret`` settings:: SOCIAL_AUTH_VK_APP_KEY = '' SOCIAL_AUTH_VK_APP_SECRET = '' - Fill ``user_mode``:: SOCIAL_AUTH_VK_APP_USER_MODE = 2 Possible values: - ``0``: there will be no check whether a user connected to your application or not - ``1``: ``python-social-auth`` will check ``is_app_user`` parameter VK.com sends when user opens application page one time - ``2``: (safest) ``python-social-auth`` will check status of user interactively (useful when you have interactive authentication via AJAX) - Add a snippet similar to this into your login template:: Click to authenticate To test, launch the server using ``sudo ./manage.py mysite.com:80`` for browser to be able to load it when VK.com calls IFrame URL. Open your VK.com application page via http://vk.com/app. Now you are able to connect to application and login automatically after connection when visiting application page. For more details see `authentication for VK.com applications`_ OpenAPI ------- You can also use VK.com's own OpenAPI to log in, but you need to provide HTML template with JavaScript code to authenticate, check below for an example. - Get an OpenAPI App Id and add it to the settings:: SOCIAL_AUTH_VK_OPENAPI_ID = '' This app id will be passed to the template as ``VK_APP_ID``. Snippet example:: Click to authorize .. _VK.com OAuth: http://vk.com/dev/authentication .. _VK.com list of permissions: http://vk.com/dev/permissions .. _VK.com API: http://vk.com/dev/methods .. _authentication for VK.com applications: http://www.ikrvss.ru/2011/11/08/django-social-auh-and-vkontakte-application/ python-social-auth-0.2.21/docs/backends/steam.rst0000644000175500017550000000066612754357263021451 0ustar debacledebacleSteam OpenId ============ Steam OpenId works quite straightforward, but to retrieve some user data (known as ``player`` on Steam API) a Steam API Key is needed. Configurable settings: - Supply a Steam API Key from `Steam Dev`_:: SOCIAL_AUTH_STEAM_API_KEY = key - To save ``player`` data provided by Steam into ``extra_data``:: SOCIAL_AUTH_STEAM_EXTRA_DATA = ['player'] .. _Steam Dev: http://steamcommunity.com/dev/apikey python-social-auth-0.2.21/docs/backends/persona.rst0000644000175500017550000000307412754357263022003 0ustar debacledebacleMozilla Persona =============== Support for `Mozilla Persona`_ is possible by posting the ``assertion`` code to ``/complete/persona/`` URL. The setup doesn't need any setting, just the usual `Mozilla Persona`_ javascript include in your document and the needed mechanism to trigger the POST to `python-social-auth`_::
    Mozilla Persona
    .. _python-social-auth: https://github.com/omab/python-social-auth .. _Mozilla Persona: http://www.mozilla.org/persona/ python-social-auth-0.2.21/docs/backends/zotero.rst0000644000175500017550000000123712754357263021655 0ustar debacledebacleZotero ====== Zotero implements OAuth1 as their authentication mechanism for their Web API v3. 1. Go to the `Zotero app registration page`_ to register your application. 2. Fill the **Client ID** and **Client Secret** in your project settings:: SOCIAL_AUTH_ZOTERO_KEY = '...' SOCIAL_AUTH_ZOTERO_SECRET = '...' 3. Enable the backend:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.zotero.ZoteroOAuth', ... ) Further documentation at `Zotero Web API v3 page`_. .. _Zotero app registration page: https://www.zotero.org/oauth/apps .. _Zotero Web API v3 page: https://www.zotero.org/support/dev/web_api/v3/start python-social-auth-0.2.21/docs/backends/withings.rst0000644000175500017550000000052512754357263022166 0ustar debacledebacleWithings ======== Withings uses OAuth v1 for Authentication. - Register a new application at the `Withings API`_, and - fill ``Client ID`` and ``Client Secret`` from withings.com values in the settings:: SOCIAL_AUTH_WITHINGS_KEY = '' SOCIAL_AUTH_WITHINGS_SECRET = '' .. _Withings API: https://oauth.withings.com/partner/add python-social-auth-0.2.21/docs/backends/github.rst0000644000175500017550000000316212754357263021614 0ustar debacledebacleGitHub ====== GitHub works similar to Facebook (OAuth). - Register a new application at `GitHub Developers`_, set the callback URL to ``http://example.com/complete/github/`` replacing ``example.com`` with your domain. - Fill the ``Client ID`` and ``Client Secret`` values from GitHub in the settings:: SOCIAL_AUTH_GITHUB_KEY = '' SOCIAL_AUTH_GITHUB_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_GITHUB_SCOPE = [...] GitHub for Organizations ------------------------ When defining authentication for organizations, use the ``GithubOrganizationOAuth2`` backend instead. The settings are the same as the non-organization backend, but the names must be:: SOCIAL_AUTH_GITHUB_ORG_* Be sure to define the organization name using the setting:: SOCIAL_AUTH_GITHUB_ORG_NAME = '' This name will be used to check that the user really belongs to the given organization and discard it if they're not part of it. GitHub for Teams ---------------- Similar to ``GitHub for Organizations``, there's a GitHub for Teams backend, use the backend ``GithubTeamOAuth2``. The settings are the same as the basic backend, but the names must be:: SOCIAL_AUTH_GITHUB_TEAM_* Be sure to define the ``Team ID`` using the setting:: SOCIAL_AUTH_GITHUB_TEAM_ID = '' This ``id`` will be used to check that the user really belongs to the given team and discard it if they're not part of it. Github for Enterprises ---------------------- Check the docs :ref:`github-enterprise` if planning to use Github Enterprises. .. _GitHub Developers: https://github.com/settings/applications/new python-social-auth-0.2.21/docs/backends/facebook.rst0000644000175500017550000000655512754357263022114 0ustar debacledebacleFacebook ======== OAuth2 ------ Facebook uses OAuth2 for its auth process. Further documentation at `Facebook development resources`_: - Register a new application at `Facebook App Creation`_, don't use ``localhost`` as ``App Domains`` and ``Site URL`` since Facebook won't allow them. Use a placeholder like ``myapp.com`` and define that domain in your ``/etc/hosts`` or similar file. - fill ``App Id`` and ``App Secret`` values in values:: SOCIAL_AUTH_FACEBOOK_KEY = '' SOCIAL_AUTH_FACEBOOK_SECRET = '' - Define ``SOCIAL_AUTH_FACEBOOK_SCOPE`` to get extra permissions from facebook. Email is not sent by default, to get it, you must request the ``email`` permission:: SOCIAL_AUTH_FACEBOOK_SCOPE = ['email'] - Define ``SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS`` to pass extra parameters to https://graph.facebook.com/me when gathering the user profile data (you need to explicitly ask for fields like ``email`` using ``fields`` key):: SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'locale': 'ru_RU', 'fields': 'id, name, email, age_range' } If you define a redirect URL in Facebook setup page, be sure to not define http://127.0.0.1:8000 or http://localhost:8000 because it won't work when testing. Instead I define http://myapp.com and setup a mapping on ``/etc/hosts``. Canvas Application ------------------ If you need to perform authentication from Facebook Canvas application: - Create your canvas application at http://developers.facebook.com/apps - In Facebook application settings specify your canvas URL ``mysite.com/fb`` (current default) - Setup your Python Social Auth settings and your application namespace:: SOCIAL_AUTH_FACEBOOK_APP_KEY = '' SOCIAL_AUTH_FACEBOOK_APP_SECRET = '' SOCIAL_AUTH_FACEBOOK_APP_NAMESPACE = '' - Launch your testing server on port 80 (use sudo or nginx or apache) for browser to be able to load it when Facebook calls canvas URL - Open your Facebook page via http://apps.facebook.com/app_namespace or better via http://www.facebook.com/pages/user-name/user-id?sk=app_app-id - After that you will see this page in a right way and will able to connect to application and login automatically after connection - Provide a template to be rendered, it must have this JavaScript snippet (or similar) in it:: More info on the topic at `Facebook Canvas Application Authentication`_. Graph 2.0 --------- If looking for `Graph 2.0`_ support, use the backends ``Facebook2OAuth2`` (OAuth2) and/or ``Facebook2AppOAuth2`` (Canvas application). .. _Facebook development resources: http://developers.facebook.com/docs/authentication/ .. _Facebook App Creation: http://developers.facebook.com/setup/ .. _Facebook Canvas Application Authentication: http://www.ikrvss.ru/2011/09/22/django-social-auth-and-facebook-canvas-applications/ .. _Graph 2.0: https://developers.facebook.com/blog/post/2014/04/30/the-new-facebook-login/ python-social-auth-0.2.21/docs/backends/dailymotion.rst0000644000175500017550000000136212754357263022662 0ustar debacledebacleDailyMotion =========== DailyMotion uses OAuth2. In order to enable the backend follow: - Register an application at `DailyMotion Developer Portal`_ - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_DAILYMOTION_KEY = '' SOCIAL_AUTH_DAILYMOTION_SECRET = '' - Set the ``Callback URL`` to ``http:///complete/dailymotion/`` - Specify scopes with:: SOCIAL_AUTH_DAILYMOTION_SCOPE = [...] Available scopes are listed in the `Requesting Extended Permissions`_ section. .. _DailyMotion Developer Portal: http://www.dailymotion.com/profile/developer/new .. _Requesting Extended Permissions: http://www.dailymotion.com/doc/api/authentication.html#requesting-extended-permissions python-social-auth-0.2.21/docs/backends/aol.rst0000644000175500017550000000033612754357263021105 0ustar debacledebacleAOL === AOL OpenId doesn't require major settings beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.aol.AOLOpenId', ... ) python-social-auth-0.2.21/docs/backends/implementation.rst0000644000175500017550000002671112754357263023364 0ustar debacledebacleAdding a new backend ==================== Add new backends is quite easy, usually adding just a ``class`` with a couple settings and methods overrides to retrieve user data from services API. Follow the details below. Common attributes ----------------- First, lets check the common attributes for all backend types. ``name = ''`` Any backend needs a name, usually the popular name of the service is used, like ``facebook``, ``twitter``, etc. It must be unique, otherwise another backend can take precedence if it's listed before in ``AUTHENTICATION_BACKENDS`` setting. ``ID_KEY = None`` Defines the attribute in the service response that identifies the user as unique in the service, the value is later stored in the ``uid`` attribute in the ``UserSocialAuth`` instance. ``REQUIRES_EMAIL_VALIDATION = False`` Flags the backend to enforce email validation during the pipeline (if the corresponding pipeline ``social.pipeline.mail.mail_validation`` was enabled). ``EXTRA_DATA = None`` During the auth process some basic user data is returned by the provider or retrieved by ``user_data()`` method which usually is used to call some API on the provider to retrieve it. This data will be stored under ``UserSocialAuth.extra_data`` attribute, but to make it accessible under some common names on different providers, this attribute defines a list of tuples in the form ``(name, alias)`` where ``name`` is the key in the user data (which should be a ``dict`` instance) and ``alias`` is the name to store it on ``extra_data``. OAuth ----- OAuth1 and OAuth2 provide share some common definitions based on the shared behavior during the auth process, like a successful API response from ``AUTHORIZATION_URL`` usually returns some basic user data like a user Id. Shared attributes ***************** ``name`` This defines the backend name and identifies it during the auth process. The name is used in the URLs ``/login/`` and ``/complete/``. ``ID_KEY = 'id'`` Default key name where user identification field is defined, it's used on auth process when some basic user data is returned. This Id is stored in ``UserSocialAuth.uid`` field, this together the ``UserSocialAuth.provider`` field is used to unique identify a user association. ``SCOPE_PARAMETER_NAME = 'scope'`` Scope argument is used to tell the provider the API endpoints you want to call later, it's a permissions request granted over the ``access_token`` later retrieved. Default value is ``scope`` since that's usually the name used in the URL parameter, but can be overridden if needed. ``DEFAULT_SCOPE = None`` Some providers give nothing about the user but some basic data in required like the user Id or an email address. Default scope attribute is used to specify a default value for ``scope`` argument to request those extra used bits. ``SCOPE_SEPARATOR = ' '`` The ``scope`` argument is usually a list of permissions to request, the list is joined used a separator, usually just a blank space, but differ from provider to provider, override the default value with this attribute if it differs. OAuth2 ****** OAuth2 backends are fairly simple to implement; just a few settings, a method override and it's mostly ready to go. The key points on this backends are: ``AUTHORIZATION_URL`` This is the entry point for the authorization mechanism, users must be redirected to this URL, used on ``auth_url`` method which builds the redirect address with ``AUTHORIZATION_URL`` plus some arguments (``client_id``, ``redirect_uri``, ``response_type``, and ``state``). ``ACCESS_TOKEN_URL`` Must point to the API endpoint that provides an ``access_token`` needed to authenticate in users behalf on future API calls. ``REFRESH_TOKEN_URL`` Some providers give the option to renew the ``access_token`` since they are usually limited in time, once that time runs out, the token is invalidated and cannot be used any more. This attribute should point to that API endpoint. ``RESPONSE_TYPE`` The response type expected on the auth process, default value is ``code`` as dictated by OAuth2 definition. Override it if default value doesn't fit the provider implementation. ``STATE_PARAMETER`` OAuth2 defines that an ``state`` parameter can be passed in order to validate the process, it's kind of a CSRF check to avoid man in the middle attacks. Some don't recognise it or don't return it which will make the auth process invalid. Set this attribute to ``False`` in that case. ``REDIRECT_STATE`` For those providers that don't recognise the ``state`` parameter, the app can add a ``redirect_state`` argument to the ``redirect_uri`` to mimic it. Set this value to ``False`` if the provider likes to verify the ``redirect_uri`` value and this parameter invalidates that check. Example code:: from social.backends.oauth import BaseOAuth2 class GithubOAuth2(BaseOAuth2): """Github OAuth authentication backend""" name = 'github' AUTHORIZATION_URL = 'https://github.com/login/oauth/authorize' ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token' SCOPE_SEPARATOR = ',' 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')} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'https://api.github.com/user?' + urlencode({ 'access_token': access_token }) try: return json.load(self.urlopen(url)) except ValueError: return None OAuth1 ****** OAuth1 process is a bit more trickier, `Twitter Docs`_ explains it quite well. Beside the ``AUTHORIZATION_URL`` and ``ACCESS_TOKEN_URL`` attributes, a third one is needed used when starting the process. ``REQUEST_TOKEN_URL = ''`` During the auth process an unauthorized token is needed to start the process, later this token is exchanged for an ``access_token``. This setting points to the API endpoint where that unauthorized token can be retrieved. Example code:: from xml.dom import minidom from social.backends.oauth import ConsumerBasedOAuth class TripItOAuth(ConsumerBasedOAuth): """TripIt OAuth authentication backend""" name = 'tripit' 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' 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} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" url = 'https://api.tripit.com/v1/get/profile' request = self.oauth_request(access_token, url) 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, } OpenId ------ OpenId is fair simpler that OAuth since it's used for authentication rather than authorization (regardless it's used for authorization too). A single attribute is usually needed, the authentication URL endpoint. ``URL = ''`` OpenId endpoint where to redirect the user. Sometimes the URL is user dependant, like in myOpenId_ where the URL is ``https://.myopenid.com``. For those cases where the user must input it's handle (or full URL). The backend must override the ``openid_url()`` method to retrieve it and return a full URL to where the user will be redirected. Example code:: from social.backends.open_id import OpenIdAuth from social.exceptions import AuthMissingParameter class LiveJournalOpenId(OpenIdAuth): """LiveJournal OpenID authentication backend""" name = 'livejournal' def get_user_details(self, response): """Generate username from identity url""" values = super(LiveJournalOpenId, self).get_user_details(response) values['username'] = values.get('username') or \ urlparse.urlsplit(response.identity_url)\ .netloc.split('.', 1)[0] return values def openid_url(self): """Returns LiveJournal authentication URL""" if not self.data.get('openid_lj_user'): raise AuthMissingParameter(self, 'openid_lj_user') return 'http://%s.livejournal.com' % self.data['openid_lj_user'] Auth APIs --------- For others authentication types, a ``BaseAuth`` class is defined to help. Those custom auth methods must override the ``auth_url()`` and ``auth_complete()`` methods. Example code:: from google.appengine.api import users from social.backends.base import BaseAuth from social.exceptions import AuthException class GoogleAppEngineAuth(BaseAuth): """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': ''} def auth_url(self): """Build and return complete URL.""" return users.create_login_url(self.redirect_uri) def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance.""" if not users.get_current_user(): raise AuthException('Authentication error') kwargs.update({'response': '', 'backend': self}) return self.strategy.authenticate(*args, **kwargs) .. _Twitter Docs: https://dev.twitter.com/docs/auth/implementing-sign-twitter .. _myOpenId: https://www.myopenid.com/ python-social-auth-0.2.21/docs/backends/tripit.rst0000644000175500017550000000075612754357263021653 0ustar debacledebacleTripIt ====== TripIt offers per application keys named ``API Key`` and ``API Secret``. To enable TripIt these two keys are needed. Further documentation at `TripIt Developer Center`_: - Register a new application at `TripIt App Registration`_, - fill **API Key** and **API Secret** values:: SOCIAL_AUTH_TRIPIT_KEY = '' SOCIAL_AUTH_TRIPIT_SECRET = '' .. _TripIt Developer Center: https://www.tripit.com/developer .. _TripIt App Registration: https://www.tripit.com/developer/create python-social-auth-0.2.21/docs/backends/odnoklassnikiru.rst0000644000175500017550000000331412754357263023550 0ustar debacledebacleOdnoklassniki.ru ================ There are two options with Odnoklassniki: either you use OAuth2 workflow to authenticate odnoklassniki users at external site, or you authenticate users within your IFrame application. OAuth2 ------ If you use OAuth2 workflow, you need to: - register a new application with `OAuth registration form`_ - fill out some settings:: SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_KEY = '' SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_SECRET = '' SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_PUBLIC_NAME = '' - add ``'social.backends.odnoklassniki.OdnoklassnikiOAuth2'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. IFrame applications ------------------- If you want to authenticate users in your IFrame application, - read `Rules for application developers`_ - fill out `Developers registration form`_ - get your personal sandbox - fill out some settings:: SOCIAL_AUTH_ODNOKLASSNIKI_APP_KEY = '' SOCIAL_AUTH_ODNOKLASSNIKI_APP_SECRET = '' SOCIAL_AUTH_ODNOKLASSNIKI_APP_PUBLIC_NAME = '' - add ``'social.backends.odnoklassniki.OdnoklassnikiApp'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS`` - sign a public offer and do some bureaucracy You may also use:: SOCIAL_AUTH_ODNOKLASSNIKI_APP_EXTRA_USER_DATA_LIST Defaults to empty tuple, for the list of available fields see `Documentation on user.getInfo`_ .. _OAuth registration form: https://apiok.ru/wiki/pages/viewpage.action?pageId=42476652 .. _Rules for application developers: https://apiok.ru/wiki/display/ok/Odnoklassniki.ru+Third+Party+Platform .. _Developers registration form: https://apiok.ru/wiki/pages/viewpage.action?pageId=5668937 .. _Documentation on user.getInfo: https://apiok.ru/wiki/display/ok/REST+API+-+users.getInfo python-social-auth-0.2.21/docs/backends/kakao.rst0000644000175500017550000000062112754357263021415 0ustar debacledebacleKakao ====== Kakao uses OAuth v2 for Authentication. - Register a new applicationat the `Kakao API`_, and - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_KAKAO_KEY = '' SOCIAL_AUTH_KAKAO_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_KAKAO_SCOPE = [...] .. _Kakao API: https://developers.kakao.com/docs/restapi python-social-auth-0.2.21/docs/backends/appsfuel.rst0000644000175500017550000000223612754357263022152 0ustar debacledebacleAppsfuel ======== Appsfuel uses OAuth v2 for Authentication check the `official docs`_ too. - Sign up at the `Appsfuel Developer Program`_ - Create and verify a new app - On the dashboard click on **Show API keys** - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_APPSFUEL_KEY = '' SOCIAL_AUTH_APPSFUEL_SECRET = '' Appsfuel gives you the chance to integrate with **Live** or **Sandbox** env. Appsfuel Live ------------- - Add 'social.backends.contrib.appsfuel.AppsfuelBackend' into your ``AUTHENTICATION_BACKENDS``. - Then you can start using ``{% url social:begin 'appsfuel' %}`` in your templates Appsfuel Sandbox ---------------- - Add ``'social.backends.appsfuel.AppsfuelOAuth2Sandbox'`` into your ``AUTHENTICATION_BACKENDS``. - Then you can start using ``{% url social:begin 'appsfuel-sandbox' %}`` in your templates - Define the settings:: SOCIAL_AUTH_APPSFUEL_SANDBOX_KEY = '' SOCIAL_AUTH_APPSFUEL_SANDBOX_SECRET = '' .. _official docs: http://docs.appsfuel.com/api_reference#api_integration .. _Appsfuel Developer Program: https://developer.appsfuel.com python-social-auth-0.2.21/docs/backends/khanacademy.rst0000644000175500017550000000137412754357263022602 0ustar debacledebacleKhan Academy ============ Khan Academy uses a variant of OAuth1 authentication flow. Check the API details at `Khan Academy API Authentication`_. Follow this steps in order to use the backend: - Register a new application at `Khan Academy API Apps`_, - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_KHANACADEMY_OAUTH1_KEY = '' SOCIAL_AUTH_KHANACADEMY_OAUTH1_SECRET = '' - Add the backend to ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.khanacademy.KhanAcademyOAuth1', ... ) .. _Khan Academy API Authentication: https://github.com/Khan/khan-api/wiki/Khan-Academy-API-Authentication .. _Khan Academy API Apps: http://www.khanacademy.org/api-apps/register python-social-auth-0.2.21/docs/backends/box.rst0000644000175500017550000000123612754357263021122 0ustar debacledebacleBox.net ======= Box works similar to Facebook (OAuth2). - Register an application at `Manage Box Applications`_ - Fill the **Consumer Key** and **Consumer Secret** values in your settings:: SOCIAL_AUTH_BOX_KEY = '' SOCIAL_AUTH_BOX_SECRET = '' - By default the token is not permanent, it will last an hour. To refresh the access token just do:: from social.apps.django_app.utils import load_strategy strategy = load_strategy(backend='box') user = User.objects.get(pk=foo) social = user.social_auth.filter(provider='box')[0] social.refresh_token(strategy=strategy) .. _Manage Box Applications: https://app.box.com/developers/services python-social-auth-0.2.21/docs/backends/drip.rst0000644000175500017550000000046312754357263021271 0ustar debacledebacleDrip ==== Drip uses OAuth v2 for Authentication. - Register a new application with `Drip`_, and - fill ``Client ID`` and ``Client Secret`` from getdrip.com values in the settings:: SOCIAL_AUTH_DRIP_KEY = '' SOCIAL_AUTH_DRIP_SECRET = '' .. _Drip: https://www.getdrip.com/user/applications python-social-auth-0.2.21/docs/backends/qq.rst0000644000175500017550000000157412754357263020760 0ustar debacledebacleQQ == QQ implemented OAuth2 protocol for their authentication mechanism. To enable ``python-social-auth`` support follow this steps: 1. Go to `QQ`_ and create an application. 2. Fill App Id and Secret in your project settings:: SOCIAL_AUTH_QQ_KEY = '...' SOCIAL_AUTH_QQ_SECRET = '...' 3. Enable the backend:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.qq.QQOauth2', ... ) The values for ``nickname``, ``figureurl_qq_1`` and ``gender`` will be stored in the ``extra_data`` field. The ``nickname`` will be used as the account username. ``figureurl_qq_1`` can be used as the profile image. Sometimes nickname will duplicate with another ``qq`` account, to avoid this issue it's possible to use ``openid`` as ``username`` by define this setting:: SOCIAL_AUTH_QQ_USE_OPENID_AS_USERNAME = True .. _QQ: http://connect.qq.com/ python-social-auth-0.2.21/docs/backends/twilio.rst0000644000175500017550000000101412754357263021633 0ustar debacledebacleTwilio ====== - Register a new application at `Twilio Connect Api`_ - Fill ``SOCIAL_AUTH_TWILIO_KEY`` and ``SOCIAL_AUTH_TWILIO_SECRET`` values in the settings:: SOCIAL_AUTH_TWILIO_KEY = '' SOCIAL_AUTH_TWILIO_SECRET = '' - Add desired authentication backends to Django's ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS`` setting:: 'social.backends.twilio.TwilioAuth', - Usage example:: Enter using Twilio .. _Twilio Connect API: https://www.twilio.com/user/account/connect/apps python-social-auth-0.2.21/docs/backends/vend.rst0000644000175500017550000000110412754357263021260 0ustar debacledebacleVend ==== Vend supports OAuth 2. - Register a new application at `Vend Developers Portal`_ - Add the Vend OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.vend.VendOAuth2', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_VEND_OAUTH2_KEY = '' SOCIAL_AUTH_VEND_OAUTH2_SECRET = '' More details on their docs_. .. _Vend Developers Portal: https://developers.vendhq.com/developer/applications .. _docs: https://developers.vendhq.com/documentation python-social-auth-0.2.21/docs/backends/oauth.rst0000644000175500017550000000212712754357263021452 0ustar debacledebacleOAuth ===== OAuth_ communication demands a set of keys exchange to validate the client authenticity prior to user approbation. Twitter, and Facebook facilitates these keys by application registration, Google works the same, but provides the option for unregistered applications. Check next sections for details. OAuth_ backends also can store extra data in ``UserSocialAuth.extra_data`` field by defining a set of values names to retrieve from service response. Settings is per backend and its name is dynamically checked using uppercase backend name as prefix:: SOCIAL_AUTH__EXTRA_DATA Example:: SOCIAL_AUTH_FACEBOOK_EXTRA_DATA = [(..., ...)] Settings must be a list of tuples mapping value name in response and value alias used to store. A third value (boolean) is supported, its purpose is to signal if the value should be discarded if it evaluates to ``False``, this is to avoid replacing old (needed) values when they don't form part of current response. If not present, then this check is avoided and the value will replace any data. .. _OAuth: http://oauth.net/ python-social-auth-0.2.21/docs/backends/coinbase.rst0000644000175500017550000000102412754357263022110 0ustar debacledebacleCoinbase ======== Coinbase uses OAuth2. - Register an application at Coinbase_ - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_COINBASE_KEY = '' SOCIAL_AUTH_COINBASE_SECRET = '' - Set the ``redirect_url`` on coinbase. Make sure to include the trailing slash, eg. ``http://hostname/complete/coinbase/`` - Specify scopes with:: SOCIAL_AUTH_COINBASE_SCOPE = [...] By default the scope is set to ``balance``. .. _Coinbase: https://coinbase.com/oauth/applications python-social-auth-0.2.21/docs/backends/pocket.rst0000644000175500017550000000042612754357263021617 0ustar debacledebaclePocket ====== Pocket uses a weird variant of OAuth v2 that only defines a consumer key. - Register a new application at the `Pocket API`_, and - fill ``consumer key`` value in the settings:: SOCIAL_AUTH_POCKET_KEY = '' .. _Pocket API: http://getpocket.com/developer/ python-social-auth-0.2.21/docs/backends/google.rst0000644000175500017550000002137312754357263021612 0ustar debacledebacleGoogle ====== This section describes how to setup the different services provided by Google. Google OAuth ------------ .. attention:: **Google OAuth deprecation** Important: OAuth 1.0 was officially deprecated on April 20, 2012, and will be shut down on April 20, 2015. We encourage you to migrate to any of the other protocols. Google provides ``Consumer Key`` and ``Consumer Secret`` keys to registered applications, but also allows unregistered application to use their authorization system with, but beware that this method will display a security banner to the user telling that the application is not trusted. Check `Google OAuth`_ and make your choice. - fill ``Consumer Key`` and ``Consumer Secret`` values:: SOCIAL_AUTH_GOOGLE_OAUTH_KEY = '' SOCIAL_AUTH_GOOGLE_OAUTH_SECRET = '' anonymous values will be used if not configured as described in their `OAuth reference`_ - setup any needed extra scope in:: SOCIAL_AUTH_GOOGLE_OAUTH_SCOPE = [...] Google OAuth2 ------------- Recently Google launched OAuth2 support following the definition at `OAuth2 draft`. It works in a similar way to plain OAuth mechanism, but developers **must** register an application and apply for a set of keys. Check `Google OAuth2`_ document for details. When creating the application in the Google Console be sure to fill the ``PRODUCT NAME`` at ``API & auth -> Consent screen`` form. To enable OAuth2 support: - fill ``Client ID`` and ``Client Secret`` settings, these values can be obtained easily as described on `OAuth2 Registering`_ doc:: SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '' SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '' - setup any needed extra scope:: SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [...] Check which applications can be included in their `Google Data Protocol Directory`_ Google+ Sign-In --------------- `Google+ Sign In`_ works a lot like OAuth2, but most of the initial work is done by their Javascript which thens calls a defined handler to complete the auth process. * To enable the backend create an application using the `Google console`_ and following the steps from the `official guide`_. Make sure to enable the Google+ API in the console. * Fill in the key settings looking inside the Google console the subsection ``Credentials`` inside ``API & auth``:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.google.GooglePlusAuth', ) SOCIAL_AUTH_GOOGLE_PLUS_KEY = '...' SOCIAL_AUTH_GOOGLE_PLUS_SECRET = '...' ``SOCIAL_AUTH_GOOGLE_PLUS_KEY`` corresponds to the variable ``CLIENT ID``. ``SOCIAL_AUTH_GOOGLE_PLUS_SECRET`` corresponds to the variable ``CLIENT SECRET``. * Add the sign-in button to your template, you can use the SDK button or add your own and attach the click handler to it (check `Google+ Identity Sign-In`_ documentation about it)::
    Google+ Sign In
    * Add the Javascript snippet in the same template as above:: * Logging out Logging-out can be tricky when using the the platform SDK because it can trigger an automatic sign-in when listening to the user status change. With the method show above, that won't happen, but if the UI depends more in the SDK values than the backend, then things can get out of sync easilly. To prevent this, the user should be logged-out from Google+ platform too. This can be accomplished by doing:: Google OpenId ------------- Google OpenId works straightforward, not settings are needed. Domains or emails whitelists can be applied too, check the whitelists_ settings for details. Orkut ----- As of September 30, 2014, Orkut has been `shut down`_. User identification ------------------- Optional support for static and unique Google Profile ID identifiers instead of using the e-mail address for account association can be enabled with:: SOCIAL_AUTH_GOOGLE_OAUTH_USE_UNIQUE_USER_ID = True or:: SOCIAL_AUTH_GOOGLE_OAUTH2_USE_UNIQUE_USER_ID = True depending on the backends in use. Refresh Tokens -------------- To get an OAuth2 refresh token along with the access token, you must pass an extra argument: ``access_type=offline``. To do this with Google+ sign-in:: SOCIAL_AUTH_GOOGLE_PLUS_AUTH_EXTRA_ARGUMENTS = { 'access_type': 'offline' } Scopes deprecation ------------------ Google is deprecating the full-url scopes from `Sept 1, 2014`_ in favor of ``Google+ API`` and the recently introduced shorter scopes names. But ``python-social-auth`` already introduced the scopes change at e3525187_ which was released at ``v0.1.24``. But, to enable the new scopes the application requires ``Google+ API`` to be enabled in the `Google console`_ dashboard, the change is quick and quite simple, but if any developer desires to keep using the old scopes, it's possible with the following settings:: # Google OAuth2 (google-oauth2) SOCIAL_AUTH_GOOGLE_OAUTH2_IGNORE_DEFAULT_SCOPE = True SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile' ] # Google+ SignIn (google-plus) SOCIAL_AUTH_GOOGLE_PLUS_IGNORE_DEFAULT_SCOPE = True SOCIAL_AUTH_GOOGLE_PLUS_SCOPE = [ 'https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile' ] To ease the change, the old API and scopes is still supported by the application, the new values are the default option but if having troubles supporting them you can default to the old values by defining this setting:: SOCIAL_AUTH_GOOGLE_OAUTH2_USE_DEPRECATED_API = True SOCIAL_AUTH_GOOGLE_PLUS_USE_DEPRECATED_API = True .. _Google support: http://www.google.com/support/a/bin/answer.py?hl=en&answer=162105 .. _Google OpenID: http://code.google.com/apis/accounts/docs/OpenID.html .. _Google OAuth: http://code.google.com/apis/accounts/docs/OAuth.html .. _Google OAuth2: http://code.google.com/apis/accounts/docs/OAuth2.html .. _OAuth2 Registering: http://code.google.com/apis/accounts/docs/OAuth2.html#Registering .. _OAuth2 draft: http://tools.ietf.org/html/draft-ietf-oauth-v2-10 .. _OAuth reference: http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth .. _shut down: https://support.google.com/orkut/?csw=1#Authenticating .. _Google Data Protocol Directory: http://code.google.com/apis/gdata/docs/directory.html .. _whitelists: ../configuration/settings.html#whitelists .. _Google+ Sign In: https://developers.google.com/+/web/signin/ .. _Google console: https://code.google.com/apis/console .. _official guide: https://developers.google.com/+/web/signin/#step_1_create_a_client_id_and_client_secret .. _Sept 1, 2014: https://developers.google.com/+/api/auth-migration#timetable .. _e3525187: https://github.com/omab/python-social-auth/commit/e35251878a88954cecf8e575eca27c63164b9f67 .. _Google+ Identity Sign-In: https://developers.google.com/identity/sign-in/web/sign-in python-social-auth-0.2.21/docs/backends/launchpad.rst0000644000175500017550000000043712754357263022273 0ustar debacledebacleLaunchpad ========= `Ubuntu Launchpad `_ OpenId doesn't require major settings beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.launchpad.LaunchpadOpenId', ... ) python-social-auth-0.2.21/docs/backends/yahoo.rst0000644000175500017550000000142212754357263021446 0ustar debacledebacleYahoo ===== Yahoo supports OpenId and OAuth2 for their auth flow. Yahoo OpenId ------------ OpenId doesn't require any particular configuration beside enabling the backend in the ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.yahoo.YahooOpenId', ... ) Yahoo OAuth2 ------------ OAuth 2.0 workflow, useful if you are planning to use Yahoo's API. - Register a new application at `Yahoo Developer Center`_, set your app domain and configure scopes (they can't be overriden by application). - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_YAHOO_OAUTH2_KEY = '' SOCIAL_AUTH_YAHOO_OAUTH2_SECRET = '' .. _Yahoo Developer Center: https://developer.yahoo.com/ python-social-auth-0.2.21/docs/backends/soundcloud.rst0000644000175500017550000000151012754357263022504 0ustar debacledebacleSoundCloud ========== SoundCloud uses OAuth2 for its auth mechanism. - Register a new application at `SoundCloud App Registration`_, set your application name, website and redirect URI. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_SOUNDCLOUD_KEY = '' SOCIAL_AUTH_SOUNDCLOUD_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_SOUNDCLOUD_SCOPE = [...] Possible scope values are `*` or `non-expiring` according to their `/connect documentation`_. Check the rest of their doc at `SoundCloud Developer Documentation`_. .. _SoundCloud App Registration: http://soundcloud.com/you/apps/new .. _SoundCloud Developer Documentation: http://developers.soundcloud.com/docs .. _/connect documentation: http://developers.soundcloud.com/docs/api/reference#connect python-social-auth-0.2.21/docs/backends/behance.rst0000644000175500017550000000205412754357263021716 0ustar debacledebacleBehance ======= DEPRECATED NOTICE ----------------- **NOTE:** IT SEEMS THAT BEHANCE HAS DROPPED THEIR OAUTH2 SUPPORT WITHOUT MUCH NOTICE BESIDE A `BLOG POST`_ ON SEPTEMBER 2014 MENTIONING THAT IT WILL BE INTRODUCED "SOON". THIS BACKEND IS IN DEPRECATED STATE FOR NOW. Behance uses OAuth2 for its auth mechanism. - Register a new application at `Behance App Registration`_, set your application name, website and redirect URI. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_BEHANCE_KEY = '' SOCIAL_AUTH_BEHANCE_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_BEHANCE_SCOPE = [...] Check available permissions at `Possible Scopes`_. Also check the rest of their doc at `Behance Developer Documentation`_. .. _Behance App Registration: http://www.behance.net/dev/register .. _Possible Scopes: http://www.behance.net/dev/authentication#scopes .. _Behance Developer Documentation: http://www.behance.net/dev .. _BLOG POST: http://blog.behance.net/dev/introducing-the-behance-api python-social-auth-0.2.21/docs/backends/nationbuilder.rst0000644000175500017550000000163412754357263023173 0ustar debacledebacleNationBuilder ============= `NationBuilder supports OAuth2`_ as their authentication mechanism. Follow these steps in order to use it: - Register a new application at your `Nation Admin panel`_ (define the `Callback URL` to ``http://example.com/complete/nationbuilder/`` where ``example.com`` is your domain). - Fill the ``Client ID`` and ``Client Secret`` values from the newly created application:: SOCIAL_AUTH_NATIONBUILDER_KEY = '' SOCIAL_AUTH_NATIONBUILDER_SECRET = '' - Also define your NationBuilder slug:: SOCIAL_AUTH_NATIONBUILDER_SLUG = 'your-nationbuilder-slug' - Enable the backend in ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.nationbuilder.NationBuilderOAuth2' ... ) .. _Nation Admin panel: https://psa.nationbuilder.com/admin/apps .. _NationBuilder supports OAuth2: http://nationbuilder.com/api_quickstart python-social-auth-0.2.21/docs/backends/digitalocean.rst0000644000175500017550000000171412754357263022756 0ustar debacledebacleDigitalOcean ============ DigitalOcean uses OAuth2 for its auth process. See the full `DigitalOcean developer's documentation`_ for more information. - Register a new application in the `Apps & API page`_ in the DigitalOcean control panel, setting the callback URL to ``http://example.com/complete/digitalocean/`` replacing ``example.com`` with your domain. - Fill the ``Client ID`` and ``Client Secret`` values from GitHub in the settings:: SOCIAL_AUTH_DIGITALOCEAN_KEY = '' SOCIAL_AUTH_DIGITALOCEAN_SECRET = '' - By default, only ``read`` permissions are granted. In order to create, destroy, and take other actions on the user's resources, you must request ``read write`` permissions like so:: SOCIAL_AUTH_DIGITALOCEAN_AUTH_EXTRA_ARGUMENTS = {'scope': 'read write'} .. _DigitalOcean developer's documentation: https://developers.digitalocean.com/documentation/ .. _Apps & API page: https://cloud.digitalocean.com/settings/applications python-social-auth-0.2.21/docs/backends/xing.rst0000644000175500017550000000052012754357263021272 0ustar debacledebacleXING ==== XING uses OAuth1 for their auth mechanism, in order to enable the backend follow: - Register a new application at `XING Apps Dashboard`_, - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_XING_KEY = '' SOCIAL_AUTH_XING_SECRET = '' .. _XING Apps Dashboard: https://dev.xing.com/applications python-social-auth-0.2.21/docs/backends/bitbucket.rst0000644000175500017550000000364512754357263022314 0ustar debacledebacleBitbucket ========= Bitbucket supports both OAuth2 and OAuth1 logins. 1. Register a new OAuth Consumer by following the instructions in the Bitbucket documentation: `OAuth on Bitbucket`_ Note: For OAuth2, your consumer MUST have the "account" scope otherwise the user profile information (username, name, etc.) won't be accessible. 2. Configure the appropriate settings for OAuth2 or OAuth1 (see below). OAuth2 ------ - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_BITBUCKET_OAUTH2_KEY = '' SOCIAL_AUTH_BITBUCKET_OAUTH2_SECRET = '' - If you would like to restrict access to only users with verified e-mail addresses, set ``SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY = True`` By default the setting is set to ``False`` since it's possible for a project to gather this information by other methods. OAuth1 ------ - OAuth1 works similarly to OAuth2, but you must fill in the following settings instead:: SOCIAL_AUTH_BITBUCKET_KEY = '' SOCIAL_AUTH_BITBUCKET_SECRET = '' - If you would like to restrict access to only users with verified e-mail addresses, set ``SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY = True``. By default the setting is set to ``False`` since it's possible for a project to gather this information by other methods. User ID ------- Bitbucket recommends the use of UUID_ as the user identifier instead of ``username`` since they can change and impose a security risk. For that reason ``UUID`` is used by default, but for backward compatibility reasons, it's possible to get the old behavior again by defining this setting:: SOCIAL_AUTH_BITBUCKET_USERNAME_AS_ID = True .. _UUID: https://confluence.atlassian.com/display/BITBUCKET/Use+the+Bitbucket+REST+APIs .. _OAuth on Bitbucket: https://confluence.atlassian.com/display/BITBUCKET/OAuth+on+Bitbucket python-social-auth-0.2.21/docs/backends/untappd.rst0000644000175500017550000000146012754357263022004 0ustar debacledebacleUntappd ======= Untappd uses OAuth v2 for Authentication, check the `official docs`_. - Create an app by filling out the form here: `Add App`_ - Apps are approved on a one-by-one basis, so you'll need to wait a few days to get your client ID and secret. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_UNTAPPD_KEY = '' SOCIAL_AUTH_UNTAPPD_SECRET = '' - Add the backend to the ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.untappd.UntappdOAuth2', ... ) - Then you can start using ``{% url social:begin 'untappd' %}`` in your templates .. _official docs: https://untappd.com/api/docs .. _Add App: https://untappd.com/api/register?register=new python-social-auth-0.2.21/docs/backends/trello.rst0000644000175500017550000000130312754357263021626 0ustar debacledebacleTrello ====== Trello provides OAuth1 support for their authentication process. In order to enable it, follow: - Generate an Application Key pair at `Trello Developers API Keys`_ - Fill **Consumer Key** and **Consumer Secret** settings:: SOCIAL_AUTH_TRELLO_KEY = '...' SOCIAL_AUTH_TRELLO_SECRET = '...' There are also two optional settings: - your app name, otherwise the authorization page will say "Let An unknown application use your account?":: SOCIAL_AUTH_TRELLO_APP_NAME = 'My App' - the expiration period, social auth defaults to 'never', but you can change it:: SOCIAL_AUTH_TRELLO_EXPIRATION = '30days' .. _Trello Developers API Keys: https://trello.com/1/appKey/generate python-social-auth-0.2.21/docs/backends/index.rst0000644000175500017550000000366312754357263021447 0ustar debacledebacleBackends ======== Here's a list and detailed instruction on how to setup the support for each backend. Adding new backend support -------------------------- Add new backends is quite easy, usually adding just a ``class`` with a couple methods overrides to retrieve user data from services API. Follow the details in the *Implementation* docs. .. toctree:: :maxdepth: 2 implementation Supported backends ------------------ Here's the list of currently supported backends. Non-social backends ******************* .. toctree:: :maxdepth: 2 email username Base OAuth and OpenId classes ***************************** .. toctree:: :maxdepth: 2 oauth openid saml Social backends *************** .. toctree:: :maxdepth: 2 amazon angel aol appsfuel arcgis azuread battlenet beats behance belgium_eid bitbucket box changetip clef coinbase coursera dailymotion digitalocean disqus docker douban dribbble drip dropbox edmodo eveonline evernote facebook fedora fitbit flickr foursquare github github_enterprise google instagram itembase jawbone justgiving kakao khanacademy lastfm launchpad line linkedin livejournal live loginradius mailru mapmyfitness meetup mendeley mineid mixcloud moves naszaklasa nationbuilder naver ngpvan_actionid odnoklassnikiru openstreetmap orbi persona pinterest pixelpin pocket podio qiita qq rdio readability reddit runkeeper salesforce shopify sketchfab skyrock slack soundcloud spotify suse stackoverflow steam stocktwits strava stripe taobao thisismyjam trello tripit tumblr twilio twitch twitter uber untappd upwork vend vimeo vk weibo withings wunderlist xing yahoo yammer zotero python-social-auth-0.2.21/docs/backends/docker.rst0000644000175500017550000000112412754357263021575 0ustar debacledebacleDocker ====== Docker.io OAuth2 ---------------- Docker.io now supports OAuth2 for their API. In order to set it up: - Register a new application by following the instructions in their website: `Register Your Application`_ - Fill **Consumer Key** and **Consumer Secret** values in settings:: SOCIAL_AUTH_DOCKER_KEY = '' SOCIAL_AUTH_DOCKER_SECRET = '' - Add ``'social.backends.docker.DockerOAuth2'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. .. _Register Your Application: http://docs.docker.io/en/latest/reference/api/docker_io_oauth_api/#register-your-application python-social-auth-0.2.21/docs/backends/mixcloud.rst0000644000175500017550000000201412754357263022151 0ustar debacledebacleMixcloud OAuth2 =============== The `Mixcloud API`_ offers support for authorization. To this backend support: - Register a new application at `Mixcloud Developers`_ - Add Mixcloud backend to ``AUTHENTICATION_BACKENDS`` in settings:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.mixcloud.MixcloudOAuth2', ) - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_MIXCLOUD_KEY = '' SOCIAL_AUTH_MIXCLOUD_SECRET = '' - Similar to the other OAuth backends you can define:: SOCIAL_AUTH_MIXCLOUD_EXTRA_DATA = [('username', 'username'), ('name', 'name'), ('pictures', 'pictures'), ('url', 'url')] as a list of tuples ``(response name, alias)`` to store user profile data on the ``UserSocialAuth.extra_data``. .. _Mixcloud API: http://www.mixcloud.com/developers/documentation .. _Mixcloud Developers: http://www.mixcloud.com/developers python-social-auth-0.2.21/docs/backends/stripe.rst0000644000175500017550000000170712754357263021643 0ustar debacledebacleStripe ====== Stripe uses OAuth2 for its authorization service. To setup Stripe backend: - Register a new application at `Stripe App Creation`_, and - Grab the ``client_id`` value in ``Applications`` tab and fill the ``App Id`` setting:: SOCIAL_AUTH_STRIPE_KEY = 'ca_...' - Grab the ``Test Secret Key`` in the ``API Keys`` tab and fille the ``App Secret`` setting:: SOCIAL_AUTH_STRIPE_SECRET = '...' - Define ``SOCIAL_AUTH_STRIPE_SCOPE`` with the desired scope (options are ``read_only`` and ``read_write``):: SOCIAL_AUTH_STRIPE_SCOPE = ['read_only'] - Add the needed backend to ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.stripe.StripeOAuth2', ... ) More info on Stripe OAuth2 at `Integrating OAuth`_. .. _Stripe App Creation: https://manage.stripe.com/#account/applications/settings .. _Integrating OAuth: https://stripe.com/docs/connect/oauth python-social-auth-0.2.21/docs/backends/vimeo.rst0000644000175500017550000000132112754357263021444 0ustar debacledebacleVimeo ===== Vimeo uses OAuth1 to grant access to their API. In order to get the backend running follow: - Register an application at `Vimeo Developer Portal`_ filling the required settings. Ensure to fill ``App Callback URL`` field with ``http:///complete/vimeo/`` - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_VIMEO_KEY = '' SOCIAL_AUTH_VIMEO_SECRET = '' - Specify scopes with:: SOCIAL_AUTH_VIMEO_SCOPE = [...] - Add the backend to ``AUTHENTICATION_BACKENDS``:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.vimeo.VimeoOAuth1', ... ) .. _Vimeo Developer Portal: https://developer.vimeo.com/apps/new python-social-auth-0.2.21/docs/backends/linkedin.rst0000644000175500017550000000537212754357263022134 0ustar debacledebacleLinkedIn ======== LinkedIn supports OAuth1 and OAuth2. Migration between each type is fair simple since the same Key / Secret pair is used for both authentication types. LinkedIn OAuth setup is similar to any other OAuth service. The auth flow is explained on `LinkedIn Developers`_ docs. First you will need to register an app att `LinkedIn Developer Network`_. OAuth1 ------ - Fill the application key and secret in your settings:: SOCIAL_AUTH_LINKEDIN_KEY = '' SOCIAL_AUTH_LINKEDIN_SECRET = '' - Application scopes can be specified by:: SOCIAL_AUTH_LINKEDIN_SCOPE = [...] Check the available options at `LinkedIn Scopes`_. If you want to request a user's email address, you'll need specify that your application needs access to the email address use the ``r_emailaddress`` scope. - To request extra fields using `LinkedIn fields selectors`_ just define this setting:: SOCIAL_AUTH_LINKEDIN_FIELD_SELECTORS = [...] with the needed fields selectors, also define ``SOCIAL_AUTH_LINKEDIN_EXTRA_DATA`` properly as described in `OAuth `_, that way the values will be stored in ``UserSocialAuth.extra_data`` field. By default ``id``, ``first-name`` and ``last-name`` are requested and stored. For example, to request a user's email, headline, and industry from the Linkedin API and store the information in ``UserSocialAuth.extra_data``, you would add these settings:: # Add email to requested authorizations. SOCIAL_AUTH_LINKEDIN_SCOPE = ['r_basicprofile', 'r_emailaddress', ...] # Add the fields so they will be requested from linkedin. SOCIAL_AUTH_LINKEDIN_FIELD_SELECTORS = ['email-address', 'headline', 'industry'] # Arrange to add the fields to UserSocialAuth.extra_data SOCIAL_AUTH_LINKEDIN_EXTRA_DATA = [('id', 'id'), ('firstName', 'first_name'), ('lastName', 'last_name'), ('emailAddress', 'email_address'), ('headline', 'headline'), ('industry', 'industry')] OAuth2 ------ OAuth2 works exacly the same than OAuth1, but the settings must be named as:: SOCIAL_AUTH_LINKEDIN_OAUTH2_* Looks like LinkedIn is forcing the definition of the callback URL in the application when OAuth2 is used. Be sure to set the proper values, otherwise a ``(400) Client Error: Bad Request`` might be returned by their service. .. _LinkedIn fields selectors: http://developer.linkedin.com/docs/DOC-1014 .. _LinkedIn Scopes: https://developer.linkedin.com/documents/authentication#granting .. _LinkedIn Developer Network: https://www.linkedin.com/secure/developer .. _LinkedIn Developers: http://developer.linkedin.com/documents/authentication python-social-auth-0.2.21/docs/backends/qiita.rst0000644000175500017550000000107112754357263021436 0ustar debacledebacleQiita ===== Qiita - Register a new application at Qiita_, set the callback URL to ``http://example.com/complete/qiita/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_QIITA_KEY = '' SOCIAL_AUTH_QIITA_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_QIITA_SCOPE = [...] See auth scopes at `Qiita Scopes docs`_. .. _Qiita: https://qiita.com/settings/applications .. _Qiita Scopes docs: https://qiita.com/api/v2/docs#スコープ python-social-auth-0.2.21/docs/backends/edmodo.rst0000644000175500017550000000116612754357263021603 0ustar debacledebacleEdmodo ====== Edmodo supports OAuth 2. - Register a new application at `Edmodo Connect API`_, and follow the instructions below. - Add the Edmodo OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.edmodo.EdmodoOAuth2', ... ) - Fill ``App Key``, ``App Secret`` and ``App Scope`` values in the settings:: SOCIAL_AUTH_EDMODO_OAUTH2_KEY = '' SOCIAL_AUTH_EDMODO_OAUTH2_SECRET = '' SOCIAL_AUTH_EDMODO_SCOPE = ['basic'] .. _Edmodo Connect API: https://developers.edmodo.com/edmodo-connect/edmodo-connect-overview-getting-started/ python-social-auth-0.2.21/docs/backends/fedora.rst0000644000175500017550000000035512754357263021573 0ustar debacledebacleFedora ====== Fedora OpenId doesn't require major settings beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.fedora.FedoraOpenId', ... ) python-social-auth-0.2.21/docs/backends/naszaklasa.rst0000644000175500017550000000135212754357263022461 0ustar debacledebacleNationBuilder ============= `NaszaKlasa supports OAuth2`_ as their authentication mechanism. Follow these steps in order to use it: - Register a new application at your `NK Developers`_ (define the `Callback URL` to ``http://example.com/complete/nk/`` where ``example.com`` is your domain). - Fill the ``Client ID`` and ``Client Secret`` values from the newly created application:: SOCIAL_AUTH_NK_KEY = '' SOCIAL_AUTH_NK_SECRET = '' - Enable the backend in ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.nk.NKOAuth2', ... ) .. _NaszaKlasa supports OAuth2: https://developers.nk.pl .. _NK Developers: https://developers.nk.pl/developers/oauth2client/formpython-social-auth-0.2.21/docs/backends/eveonline.rst0000644000175500017550000000135012754357263022313 0ustar debacledebacleEVE Online Single Sign-On (SSO) =============================== The EVE Single Sign-On (SSO) works similar to GitHub (OAuth2). - Register a new application at `EVE Developers`_, set the callback URL to ``http://example.com/complete/eveonline/`` replacing ``example.com`` with your domain. - Fill the ``Client ID`` and ``Secret Key`` values from EVE Developers in the settings:: SOCIAL_AUTH_EVEONLINE_KEY = '' SOCIAL_AUTH_EVEONLINE_SECRET = '' - If you want to use EVE Character names as user names, use this setting:: SOCIAL_AUTH_CLEAN_USERNAMES = False - If you want to access EVE Online's CREST API, use:: SOCIAL_AUTH_EVEONLINE_SCOPE = ['publicData'] .. _EVE Developers: https://developers.eveonline.com/ python-social-auth-0.2.21/docs/backends/saml.rst0000644000175500017550000001622512754357263021272 0ustar debacledebacleSAML ==== The SAML backend allows users to authenticate with any provider that supports the SAML 2.0 protocol (commonly used for corporate or academic single sign on). The SAML backend for python-social-auth allows your web app to act as a SAML Service Provider. You can configure one or more SAML Identity Providers that users can use for authentication. For example, if your users are students, you could enable Harvard and MIT as identity providers, so that students of either of those two universities can use their campus login to access your app. Required Dependency ------------------- You must install python-saml_ 2.1.3 or higher in order to use this backend. Required Configuration ---------------------- At a minimum, you must add the following to your project's settings: - ``SOCIAL_AUTH_SAML_SP_ENTITY_ID``: The SAML Entity ID for your app. This should be a URL that includes a domain name you own. It doesn't matter what the URL points to. Example: ``http://saml.yoursite.com`` - ``SOCIAL_AUTH_SAML_SP_PUBLIC_CERT``: The X.509 certificate string for the key pair that your app will use. You can generate a new self-signed key pair with:: openssl req -new -x509 -days 3652 -nodes -out saml.crt -keyout saml.key The contents of ``saml.crt`` should then be used as the value of this setting (you can omit the first and last lines, which aren't required). - ``SOCIAL_AUTH_SAML_SP_PRIVATE_KEY``: The private key to be used by your app. If you used the example openssl command given above, set this to the contents of ``saml.key`` (again, you can omit the first and last lines). - ``SOCIAL_AUTH_SAML_ORG_INFO``: A dictionary that contains information about your app. You must specify values for English at a minimum. Each language's entry should specify a ``name`` (not shown to the user), a ``displayname`` (shown to the user), and a URL. See the following example:: { "en-US": { "name": "example", "displayname": "Example Inc.", "url": "http://example.com", } } - ``SOCIAL_AUTH_SAML_TECHNICAL_CONTACT``: A dictionary with two values, ``givenName`` and ``emailAddress``, describing the name and email of a technical contact responsible for your app. Example:: {"givenName": "Tech Gal", "emailAddress": "technical@example.com"} - ``SOCIAL_AUTH_SAML_TECHNICAL_CONTACT``: A dictionary with two values, ``givenName`` and ``emailAddress``, describing the name and email of a support contact for your app. Example:: SOCIAL_AUTH_SAML_SUPPORT_CONTACT = { "givenName": "Support Guy", "emailAddress": "support@example.com", } - ``SOCIAL_AUTH_SAML_ENABLED_IDPS``: The most important setting. List the Entity ID, SSO URL, and x.509 public key certificate for each provider that your app wants to support. The SSO URL must support the ``HTTP-Redirect`` binding. You can get these values from the provider's XML metadata. Here's an example, for TestShib_ (the values come from TestShib's metadata_):: { "testshib": { "entity_id": "https://idp.testshib.org/idp/shibboleth", "url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", "x509cert": "MIIEDjCCAvagAwIBAgIBADA ... 8Bbnl+ev0peYzxFyF5sQA==", } } Basic Usage ----------- - Set all of the required configuration variables described above. - Generate the SAML XML metadata for your app. The best way to do this is to create a new view/page/URL in your app that will call the backend's ``generate_metadata_xml()`` method. Here's an example of how to do this in Django:: def saml_metadata_view(request): complete_url = reverse('social:complete', args=("saml", )) saml_backend = load_backend( load_strategy(request), "saml", redirect_uri=complete_url, ) metadata, errors = saml_backend.generate_metadata_xml() if not errors: return HttpResponse(content=metadata, content_type='text/xml') - Download the metadata for your app that was generated by the above method, and send it to each Identity Provider (IdP) that you wish to use. Each IdP must install and configure your metadata on their system before it will work. - Now everything is set! To allow users to login with any given IdP, you need to give them a link to the python-social-auth "begin"/"auth" URL and include an ``idp`` query parameter that specifies the name of the IdP to use. This is needed since the backend supports multiple IdPs. The names of the IdPs are the keys used in the ``SOCIAL_AUTH_SAML_ENABLED_IDPS`` setting. Django example:: # In view: context['testshib_url'] = u"{base}?{params}".format( base=reverse('social:begin', kwargs={'backend': 'saml'}), params=urllib.urlencode({'next': '/home', 'idp': 'testshib'}) ) # In template: TestShib Login # Result: TestShib Login - Testing with the TestShib_ provider is recommended, as it is known to work well. Advanced Settings ----------------- - ``SOCIAL_AUTH_SAML_SP_EXTRA``: This can be set to a dict, and any key/value pairs specified here will be passed to the underlying ``python-saml`` library configuration's ``sp`` setting. Refer to the ``python-saml`` documentation for details. - ``SOCIAL_AUTH_SAML_SECURITY_CONFIG``: This can be set to a dict, and any key/value pairs specified here will be passed to the underlying ``python-saml`` library configuration's ``security`` setting. Two useful keys that you can set are ``metadataCacheDuration`` and ``metadataValidUntil``, which control the expiry time of your XML metadata. By default, a cache duration of 10 days will be used, which means that IdPs are allowed to cache your metadata for up to 10 days, but no longer. ``metadataCacheDuration`` must be specified as an ISO 8601 duration string (e.g. `P1D` for one day). Advanced Usage -------------- You can subclass the ``SAMLAuth`` backend to provide custom functionality. In particular, there are two methods that are designed for subclasses to override: - ``get_idp(self, idp_name)``: Given the name of an IdP, return an instance of ``SAMLIdentityProvider`` with the details of the IdP. Override this method if you wish to use some other method for configuring the available identity providers, such as fetching them at runtime from another server, or using a list of providers from a Shibboleth federation. - ``_check_entitlements(self, idp, attributes)``: This method gets called during the login process and is where you can decide to accept or reject a user based on the user's SAML attributes. For example, you can restrict access to your application to only accept users who belong to a certain department. After inspecting the passed attributes parameter, do nothing to allow the user to login, or raise ``social.exceptions.AuthForbidden`` to reject the user. .. _python-saml: https://github.com/onelogin/python-saml .. _TestShib: https://www.testshib.org/ .. _metadata: https://www.testshib.org/metadata/testshib-providers.xml python-social-auth-0.2.21/docs/backends/username.rst0000644000175500017550000000416112754357263022151 0ustar debacledebacleUsername Auth ============= python-social-auth_ comes with an UsernameAuth_ backend which comes handy when your site uses requires the plain old username and password authentication mechanism. Actually that's a lie since the backend doesn't handle password at all, that's up to the developer to validate the password in and the proper place to do it is the pipeline, right after the user instance was retrieved or created. The reason to leave password handling to the developer is because too many things are really tied to the project, like the field where the password is stored, salt handling, password hashing algorithm and validation. So just add the pipeline functions that will do that following the needs of your project. Backend settings ---------------- ``SOCIAL_AUTH_USERNAME_FORM_URL = '/login-form/'`` Used to redirect the user to the login/signup form, it must have at least one field named ``username``. Form submit should go to ``/complete/username``, or if it goes to your view, then your view should complete the process calling ``social.actions.do_complete``. ``SOCIAL_AUTH_USERNAME_FORM_HTML = 'login_form.html'`` The template will be used to render the login/signup form to the user, it must have at least one field named ``username``. Form submit should go to ``/complete/username``, or if it goes to your view, then your view should complete the process calling ``social.actions.do_complete``. Password handling ----------------- Here's an example of password handling to add to the pipeline:: def user_password(strategy, user, is_new=False, *args, **kwargs): if strategy.backend.name != 'username': return password = strategy.request_data()['password'] if is_new: user.set_password(password) user.save() elif not user.validate_password(password): # return {'user': None, 'social': None} raise AuthException(strategy.backend) .. _python-social-auth: https://github.com/omab/python-social-auth .. _UsernameAuth: https://github.com/omab/python-social-auth/blob/master/social/backends/username.py#L5 python-social-auth-0.2.21/docs/backends/mapmyfitness.rst0000644000175500017550000000050412754357263023046 0ustar debacledebacleMapMyFitness ============ MapMyFitness uses OAuth v2 for authentication. - Register a new application at the `MapMyFitness API`_, and - fill ``key`` and ``secret`` values in the settings:: SOCIAL_AUTH_MAPMYFITNESS_KEY = '' SOCIAL_AUTH_MAPMYFITNESS_SECRET = '' .. _MapMyFitness API: https://www.mapmyapi.com python-social-auth-0.2.21/docs/backends/spotify.rst0000644000175500017550000000103012754357263022017 0ustar debacledebacleSpotify ======= Spotify supports OAuth 2. - Register a new application at `Spotify Web API`_, and follow the instructions below. OAuth2 ------ Add the Spotify OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.spotify.SpotifyOAuth2', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_SPOTIFY_KEY = '' SOCIAL_AUTH_SPOTIFY_SECRET = '' .. _Spotify Web API: https://developer.spotify.com/spotify-web-api python-social-auth-0.2.21/docs/backends/upwork.rst0000644000175500017550000000117012754357263021656 0ustar debacledebacleUpwork ====== Upwork supports only OAuth 1. - Register a new application at `Upwork Developers`_. OAuth1 ------ Add the Upwork OAuth backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.upwork.UpworkOAuth', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_UPWORK_KEY = '' SOCIAL_AUTH_UPWORK_SECRET = '' **Note:** For more information please go to `Upwork API Reference`_. .. _Upwork Developers: https://www.upwork.com/services/api/apply .. _Upwork API Reference: https://developers.upwork.com/?lang=python python-social-auth-0.2.21/docs/backends/thisismyjam.rst0000644000175500017550000000104212754357263022666 0ustar debacledebacleThisIsMyJam =========== ThisIsMyJam uses OAuth1 for its auth mechanism. - Register a new application at `ThisIsMyJam App Registration`_, set your application name, website and redirect URI. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_THISISMYJAM_KEY = '' SOCIAL_AUTH_THISISMYJAM_SECRET = '' Check the rest of their doc at `ThisIsMyJam API Docs`_. .. _ThisIsMyJam App Registration: https://www.thisismyjam.com/developers .. _ThisIsMyJam API Docs: https://www.thisismyjam.com/developers/docs python-social-auth-0.2.21/docs/backends/clef.rst0000644000175500017550000000061612754357263021244 0ustar debacledebacleClef ====== Clef works similar to Facebook (OAuth). - Register a new application at `Clef Developers`_, set the callback URL to ``http://example.com/complete/clef/`` replacing ``example.com`` with your domain. - Fill ``App Id`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_CLEF_KEY = '' SOCIAL_AUTH_CLEF_SECRET = '' .. _Clef Developers: https://getclef.com/developerpython-social-auth-0.2.21/docs/backends/suse.rst0000644000175500017550000000054012754357263021306 0ustar debacledebacleSUSE ==== This section describes how to setup the different services provided by SUSE and openSUSE. openSUSE OpenId --------------- openSUSE OpenId works straightforward, not settings are needed. Domains or emails whitelists can be applied too, check the whitelists_ settings for details. .. _whitelists: ../configuration/settings.html#whitelists python-social-auth-0.2.21/docs/backends/podio.rst0000644000175500017550000000050112754357263021436 0ustar debacledebaclePodio ===== Podio offers OAuth2 as their auth mechanism. In order to enable it, follow: - Register a new application at `Podio API Keys`_ - Fill **Client Id** and **Client Secret** values:: SOCIAL_AUTH_PODIO_KEY = '' SOCIAL_AUTH_PODIO_SECRET = '' .. _Podio API Keys: https://developers.podio.com/api-key python-social-auth-0.2.21/docs/backends/slack.rst0000644000175500017550000000105012754357263021421 0ustar debacledebacleSlack ===== Slack - Register a new application at Slack_, set the callback URL to ``http://example.com/complete/slack/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_SLACK_KEY = '' SOCIAL_AUTH_SLACK_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_SLACK_SCOPE = [...] See auth scopes at `Slack OAuth docs`_. .. _Slack: https://api.slack.com/applications .. _Slack OAuth docs: https://api.slack.com/docs/oauth python-social-auth-0.2.21/docs/backends/orbi.rst0000644000175500017550000000055312754357263021266 0ustar debacledebacleOrbi ==== Orbi OAuth v2 for Authentication. - Register a new applicationat the `Orbi API`_, and - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_ORBI_KEY = '' SOCIAL_AUTH_ORBI_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_KAKAO_SCOPE = ['all'] .. _Orbi API: http://orbi.kr python-social-auth-0.2.21/docs/backends/line.rst0000644000175500017550000000020312754357263021252 0ustar debacledebacleLine.me ======= Fill App Id and Secret in your project settings:: SOCIAL_AUTH_LINE_KEY = '...' SOCIAL_AUTH_LINE_SECRET = '...' python-social-auth-0.2.21/docs/backends/twitch.rst0000644000175500017550000000105512754357263021633 0ustar debacledebacleTwitch ====== Twitch works similar to Facebook (OAuth). - Register a new application in the `connections tab`_ of your Twitch settings page, set the callback URL to ``http://example.com/complete/twitch/`` replacing ``example.com`` with your domain. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_TWITCH_KEY = '' SOCIAL_AUTH_TWITCH_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_TWITCH_SCOPE = [...] .. _connections tab: http://www.twitch.tv/settings/connections python-social-auth-0.2.21/docs/backends/wunderlist.rst0000644000175500017550000000055612754357263022536 0ustar debacledebacleWunderlist ========== Wunderlist uses OAuth v2 for Authentication. - Register a new application at `Wunderlist Developer Portal`_, and - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_WUNDERLIST_KEY = '' SOCIAL_AUTH_WUNDERLIST_SECRET = '' .. _Wunderlist Developer Portal: https://developer.wunderlist.com/applications python-social-auth-0.2.21/docs/backends/arcgis.rst0000644000175500017550000000075312754357263021605 0ustar debacledebacleArcGIS ====== ArcGIS uses OAuth2 for authentication. - Register a new application at `ArcGIS Developer Center`_. OAuth2 ------ 1. Add the OAuth2 backend to your settings page:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.arcgis.ArcGISOAuth2', ... ) 2. Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_ARCGIS_KEY = '' SOCIAL_AUTH_ARCGIS_SECRET = '' .. _ArcGIS Developer Center: https://developers.arcgis.com/ python-social-auth-0.2.21/docs/backends/mineid.rst0000644000175500017550000000123712754357263021600 0ustar debacledebacleMineID ====== MineID works similar to Facebook (OAuth). - Register a new application at `MineID.org`_, set the callback URL to ``http://example.com/complete/mineid/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_MINEID_KEY = '' SOCIAL_AUTH_MINEID_SECRET = '' Self-hosted MineID ------------------ Since MineID is an Open Source software and can be self-hosted, you can change settings to point to your instance:: SOCIAL_AUTH_MINEID_HOST = 'www.your-mineid-instance.com' SOCIAL_AUTH_MINEID_SCHEME = 'https' # or 'http' .. _MineID.org: https://www.mineid.org/ python-social-auth-0.2.21/docs/backends/weibo.rst0000644000175500017550000000130412754357263021433 0ustar debacledebacleWeibo OAuth =========== Weibo OAuth 2.0 workflow. - Register a new application at Weibo_. - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_WEIBO_KEY = '' SOCIAL_AUTH_WEIBO_SECRET = '' By default ``account id``, ``profile_image_url`` and ``gender`` are stored in extra_data field. The user name is used by default to build the user instance ``username``, sometimes this contains non-ASCII characters which might not be desirable for the website. To avoid this issue it's possible to use the Weibo ``domain`` which will be inside the ASCII range by defining this setting:: SOCIAL_AUTH_WEIBO_DOMAIN_AS_USERNAME = True .. _Weibo: http://open.weibo.com python-social-auth-0.2.21/docs/backends/email.rst0000644000175500017550000000433612754357263021425 0ustar debacledebacleEmail Auth ========== python-social-auth_ comes with an EmailAuth_ backend which comes handy when your site uses requires the plain old email and password authentication mechanism. Actually that's a lie since the backend doesn't handle password at all, that's up to the developer to validate the password in and the proper place to do it is the pipeline, right after the user instance was retrieved or created. The reason to leave password handling to the developer is because too many things are really tied to the project, like the field where the password is stored, salt handling, password hashing algorithm and validation. So just add the pipeline functions that will do that following the needs of your project. Backend settings ---------------- ``SOCIAL_AUTH_EMAIL_FORM_URL = '/login-form/'`` Used to redirect the user to the login/signup form, it must have at least one field named ``email``. Form submit should go to ``/complete/email``, or if it goes to your view, then your view should complete the process calling ``social.actions.do_complete``. ``SOCIAL_AUTH_EMAIL_FORM_HTML = 'login_form.html'`` The template will be used to render the login/signup form to the user, it must have at least one field named ``email``. Form submit should go to ``/complete/email``, or if it goes to your view, then your view should complete the process calling ``social.actions.do_complete``. Email validation ---------------- Check *Email validation* pipeline in the `pipeline docs`_. Password handling ----------------- Here's an example of password handling to add to the pipeline:: def user_password(strategy, user, is_new=False, *args, **kwargs): if strategy.backend.name != 'email': return password = strategy.request_data()['password'] if is_new: user.set_password(password) user.save() elif not user.validate_password(password): # return {'user': None, 'social': None} raise AuthException(strategy.backend) .. _python-social-auth: https://github.com/omab/python-social-auth .. _EmailAuth: https://github.com/omab/python-social-auth/blob/master/social/backends/email.py#L5 .. _pipeline docs: ../pipeline.html#email-validation python-social-auth-0.2.21/docs/backends/instagram.rst0000644000175500017550000000117012754357263022314 0ustar debacledebacleInstagram ========= Instagram uses OAuth v2 for Authentication. - Register a new application at the `Instagram API`_, and - Add instagram backend to ``AUTHENTICATION_SETTINGS``:: AUTHENTICATION_SETTINGS = ( ... 'social.backends.instagram.InstagramOAuth2', ... ) - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_INSTAGRAM_KEY = '' SOCIAL_AUTH_INSTAGRAM_SECRET = '' - extra scopes can be defined by using:: SOCIAL_AUTH_INSTAGRAM_AUTH_EXTRA_ARGUMENTS = {'scope': 'likes comments relationships'} .. _Instagram API: http://instagr.am/developer/ python-social-auth-0.2.21/docs/backends/github_enterprise.rst0000644000175500017550000000366112754357263024060 0ustar debacledebacle.. _github-enterprise: GitHub Enterprise ================= GitHub Enterprise works similar to regular Github, which is in turn based on Facebook (OAuth). - Register a new application on your instance of `GitHub Enterprise Developers`_, set the callback URL to ``http://example.com/complete/github/`` replacing ``example.com`` with your domain. - Set the API URL for your Github Enterprise appliance: SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL = 'https://git.example.com/api/v3/' - Fill the ``Client ID`` and ``Client Secret`` values from GitHub in the settings: SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY = '' SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_GITHUB_ENTERPRISE_SCOPE = [...] GitHub Enterprise for Organizations ----------------------------------- When defining authentication for organizations, use the ``GithubEnterpriseOrganizationOAuth2`` backend instead. The settings are the same as the non-organization backend, but the names must be:: SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_* Be sure to define the organization name using the setting:: SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME = '' This name will be used to check that the user really belongs to the given organization and discard it if they're not part of it. GitHub Enterprise for Teams --------------------------- Similar to ``GitHub Enterprise for Organizations``, there's a GitHub for Teams backend, use the backend ``GithubEnterpriseTeamOAuth2``. The settings are the same as the basic backend, but the names must be:: SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_* Be sure to define the ``Team ID`` using the setting:: SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID = '' This ``id`` will be used to check that the user really belongs to the given team and discard it if they're not part of it. .. _GitHub Enterprise Developers: https:///settings/applications/new python-social-auth-0.2.21/docs/backends/mailru.rst0000644000175500017550000000025212754357263021620 0ustar debacledebacleMail.ru OAuth ============= Mail.ru uses OAuth2 workflow, to use it fill in settings:: SOCIAL_AUTH_MAILRU_OAUTH2_KEY = '' SOCIAL_AUTH_MAILRU_OAUTH2_SECRET = '' python-social-auth-0.2.21/docs/backends/stocktwits.rst0000644000175500017550000000077012754357263022552 0ustar debacledebacleStockTwits ========== StockTwits uses OAuth 2 for authentication. - Register a new application at https://stocktwits.com/developers/apps - Set the Website URL to http://[your domain]/ - fill ``Consumer Key`` and ``Consumer Secret`` values in your django settings:: SOCIAL_AUTH_STOCKTWITS_KEY = '' SOCIAL_AUTH_STOCKTWITS_SECRET = '' .. _StockTwits authentication docs: http://stocktwits.com/developers/docs/authentication .. _StockTwits API: http://stocktwits.com/developers/docs/api python-social-auth-0.2.21/docs/backends/stackoverflow.rst0000644000175500017550000000102312754357263023215 0ustar debacledebacleStackoverflow ============= Stackoverflow uses OAuth 2.0 - "Register For An App Key" at the `Stack Exchange API`_ site. Set your OAuth domain and application website settings. - Add the ``Client Id``, ``Client Secret`` and ``API Key`` values in settings:: SOCIAL_AUTH_STACKOVERFLOW_KEY = '' SOCIAL_AUTH_STACKOVERFLOW_SECRET = '' SOCIAL_AUTH_STACKOVERFLOW_API_KEY = '' - You can ask for extra permissions with:: SOCIAL_AUTH_STACKOVERFLOW_SCOPE = [...] .. _Stack Exchange API: https://api.stackexchange.com/ python-social-auth-0.2.21/docs/backends/naver.rst0000644000175500017550000000122512754357263021443 0ustar debacledebacleNaver ===== Naver uses OAuth v2 for Authentication. - Register a new application at the `Naver API`_, and - add naver oauth backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.naver.NaverOAuth2', ... ) - fill ``Client ID`` and ``Client Secret`` from developer.naver.com values in the settings:: SOCIAL_AUTH_NAVER_KEY = '' SOCIAL_AUTH_NAVER_SECRET = '' - you can get EXTRA_DATA:: SOCIAL_AUTH_NAVER_EXTRA_DATA = ['nickname', 'gender', 'age', 'birthday', 'profile_image'] .. _Naver API: https://nid.naver.com/devcenter/docs.nhn?menu=API python-social-auth-0.2.21/docs/backends/salesforce.rst0000644000175500017550000000255612754357263022466 0ustar debacledebacleSalesforce ========== Salesforce uses OAuth v2 for Authentication, check the `official docs`_. - Create an app following the steps in the `Defining Connected Apps`_ docs. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_SALESFORCE_OAUTH2_KEY = '' SOCIAL_AUTH_SALESFORCE_OAUTH2_SECRET = '' - Add the backend to the ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.salesforce.SalesforceOAuth2', ... ) - Then you can start using ``{% url social:begin 'salesforce-oauth2' %}`` in your templates If using the sandbox mode: - Fill these settings instead:: SOCIAL_AUTH_SALESFORCE_OAUTH2_SANDBOX_KEY = '' SOCIAL_AUTH_SALESFORCE_OAUTH2_SANDBOX_SECRET = '' - And this backend:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.salesforce.SalesforceOAuth2Sandbox', ... ) - Then you can start using ``{% url social:begin 'salesforce-oauth2-sandbox' %}`` in your templates .. _official docs: https://www.salesforce.com/us/developer/docs/api_rest/Content/intro_understanding_web_server_oauth_flow.htm .. _Defining Connected Apps: https://www.salesforce.com/us/developer/docs/api_rest/Content/intro_defining_remote_access_applications.htm python-social-auth-0.2.21/docs/backends/mendeley.rst0000644000175500017550000000210012754357263022123 0ustar debacledebacleMendeley ======== Mendeley supports OAuth1 and OAuth2, they are in the process of deprecating OAuth1 API (which should be fully deprecated on April 2014, check their announcement_). OAuth1 ------ In order to support OAuth1 (not recomended, use OAuth2 instead): - Register a new application at `Mendeley Application Registration`_ - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_MENDELEY_KEY = '' SOCIAL_AUTH_MENDELEY_SECRET = '' OAuth2 ------ In order to support OAuth2: - Register a new application at `Mendeley Application Registration`_, or migrate your OAuth1 application, check their `migration steps here`_. - Fill **Application ID** and **Application Secret** values:: SOCIAL_AUTH_MENDELEY_OAUTH2_KEY = '' SOCIAL_AUTH_MENDELEY_OAUTH2_SECRET = '' .. _Mendeley Application Registration: http://dev.mendeley.com/applications/register/ .. _announcement: https://sites.google.com/site/mendeleyapi/home/authentication .. _migration steps here: https://groups.google.com/forum/#!topic/mendeley-open-api-developers/KmUQW9I0ST0 python-social-auth-0.2.21/docs/backends/belgium_eid.rst0000644000175500017550000000040412754357263022573 0ustar debacledebacleBelgium EID =========== Belgium EID OpenId doesn't require major settings beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.belgiumeid.BelgiumEIDOpenId', ... ) python-social-auth-0.2.21/docs/backends/evernote.rst0000644000175500017550000000122112754357263022153 0ustar debacledebacleEvernote OAuth ============== Evernote OAuth 1.0 for its authentication workflow. - Register a new application at `Evernote API Key form`_. - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_EVERNOTE_KEY = '' SOCIAL_AUTH_EVERNOTE_SECRET = '' Sandbox ------- Evernote supports a sandbox mode for testing, there's a custom backend for it which name is ``evernote-sandbox`` instead of ``evernote``. Same settings apply but use these instead:: SOCIAL_AUTH_EVERNOTE_SANDBOX_KEY = '' SOCIAL_AUTH_EVERNOTE_SANDBOX_SECRET = '' .. _Evernote API Key form: http://dev.evernote.com/support/api_key.php python-social-auth-0.2.21/docs/backends/uber.rst0000644000175500017550000000115712754357263021271 0ustar debacledebacleUber ========= Uber uses OAuth v2 for Authentication. - Register a new application at the `Uber API`_, and follow the instructions below OAuth2 ========= 1. Add the Uber OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.uber.UberOAuth2', ... ) 2. Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_UBER_KEY = '' SOCIAL_AUTH_UBER_SECRET = '' 3. Scope should be defined by using:: SOCIAL_AUTH_UBER_SCOPE = ['profile', 'request'] .. _Uber API: https://developer.uber.com/dashboard python-social-auth-0.2.21/docs/backends/angel.rst0000644000175500017550000000104012754357263021411 0ustar debacledebacleAngel List ========== Angel uses OAuth v2 for Authentication. - Register a new application at the `Angel List API`_, and - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_ANGEL_KEY = '' SOCIAL_AUTH_ANGEL_SECRET = '' - extra scopes can be defined by using:: SOCIAL_AUTH_ANGEL_AUTH_EXTRA_ARGUMENTS = {'scope': 'email messages'} **Note:** Angel List does not currently support returning ``state`` parameter used to validate the auth process. .. _Angel List API: https://angel.co/api/oauth/faq python-social-auth-0.2.21/docs/backends/itembase.rst0000644000175500017550000000357112754357263022127 0ustar debacledebacleItembase ========= Itembase uses OAuth2 for authentication. - Register a new application for the `Itembase API`_, and - Add itembase live backend and/or sandbox backend to ``AUTHENTICATION_BACKENDS``:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.itembase.ItembaseOAuth2', 'social.backends.itembase.ItembaseOAuth2Sandbox', ... ) - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_ITEMBASE_KEY = '' SOCIAL_AUTH_ITEMBASE_SECRET = '' SOCIAL_AUTH_ITEMBASE_SANDBOX_KEY = '' SOCIAL_AUTH_ITEMBASE_SANDBOX_SECRET = '' - extra scopes can be defined by using:: SOCIAL_AUTH_ITEMBASE_SCOPE = ['connection.transaction', 'connection.product', 'connection.profile', 'connection.buyer'] SOCIAL_AUTH_ITEMBASE_SANDBOX_SCOPE = SOCIAL_AUTH_ITEMBASE_SCOPE To use data from the extra scopes, you need to do an extra activation step that is not in the usual OAuth flow. For that you can extend your pipeline and add a function that sends the user to an activation URL that Itembase provides. The method to retrieve the activation data is included in the backend:: @partial def activation(strategy, backend, response, *args, **kwargs): if backend.name.startswith("itembase"): if strategy.session_pop('itembase_activation_in_progress'): strategy.session_set('itembase_activated', True) if not strategy.session_get('itembase_activated'): activation_data = backend.activation_data(response) strategy.session_set('itembase_activation_in_progress', True) return HttpResponseRedirect(activation_data['activation_url']) .. _Itembase API: http://developers.itembase.com/authentication/index python-social-auth-0.2.21/docs/backends/dribbble.rst0000644000175500017550000000114312754357263022074 0ustar debacledebacleDribbble ======== Dribbble - Register a new application at Dribbble_, set the callback URL to ``http://example.com/complete/dribbble/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_DRIBBBLE_KEY = '' SOCIAL_AUTH_DRIBBBLE_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_DRIBBBLE_SCOPE = [...] See auth scopes at `Dribbble Developer docs`_. .. _Dribbble: https://dribbble.com/account/applications/new .. _Dribbble Developer docs: http://developer.dribbble.com/v1/oauth/ python-social-auth-0.2.21/docs/backends/ngpvan_actionid.rst0000644000175500017550000000215012754357263023471 0ustar debacledebacleNGP VAN ActionID ================ `NGP VAN`_'s ActionID_ service provides an OpenID 1.1 endpoint, which provides first name, last name, email address, and phone number. ActionID doesn't require major settings beside being defined on ``AUTHENTICATION_BACKENDS`` .. code-block:: python SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.ngpvan.ActionIDOpenID', ... ) If you want to be able to access the "phone" attribute offered by NGP VAN within ``extra_data`` you can add the following to your settings: .. code-block:: python SOCIAL_AUTH_ACTIONID_OPENID_AX_EXTRA_DATA = [ ('http://openid.net/schema/contact/phone/business', 'phone') ] NGP VAN offers the ability to have your domain whitelisted, which will disable the "{domain} is requesting a link to your ActionID" warning when your app attempts to login using an ActionID account. Contact `NGP VAN Developer Support`_ for more information .. _NGP VAN: http://www.ngpvan.com/ .. _ActionID: http://developers.ngpvan.com/action-id .. _NGP VAN Developer Support: http://developers.ngpvan.com/support/contact python-social-auth-0.2.21/docs/backends/azuread.rst0000644000175500017550000000135612754357263021770 0ustar debacledebacleMicrosoft Azure Active Directory ================================ To enable OAuth2 support: - Fill in ``Client ID`` and ``Client Secret`` settings. These values can be obtained easily as described in `Azure AD Application Registration`_ doc:: SOCIAL_AUTH_AZUREAD_OAUTH2_KEY = '' SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_AZUREAD_OAUTH2_RESOURCE = '' This is the resource you would like to access after authentication succeeds. Some of the possible values are: ``https://graph.windows.net`` or ``https://-my.sharepoint.com``. .. _Azure AD Application Registration: https://msdn.microsoft.com/en-us/library/azure/dn132599.aspx python-social-auth-0.2.21/docs/backends/livejournal.rst0000644000175500017550000000104012754357263022655 0ustar debacledebacleLiveJournal =========== LiveJournal provides OpenId, it doesn't require any major settings in order to work, beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.aol.AOLOpenId', ... ) LiveJournal OpenId is provided by URLs in the form ``http://.livejournal.com``, this application retrieves the ``username`` from the data in the current request by checking a parameter named ``openid_lj_user`` which can be sent by ``POST`` or ``GET``. python-social-auth-0.2.21/docs/backends/lastfm.rst0000644000175500017550000000103612754357263021616 0ustar debacledebacleLast.fm ======= Last.fm uses a similar authentication process than OAuth2 but it's not. In order to enable the support for it just: - Register an application at `Get an API Account`_, set the Last.fm callback to something sensible like http://your.site/complete/lastfm - Fill in the **API Key** and **API Secret** values in your settings:: SOCIAL_AUTH_LASTFM_KEY = '' SOCIAL_AUTH_LASTFM_SECRET = '' - Enable the backend in ``AUTHENTICATION_BACKENDS`` setting. .. _Get an API Account: http://www.last.fm/api/account/create python-social-auth-0.2.21/docs/backends/sketchfab.rst0000644000175500017550000000063112754357263022262 0ustar debacledebacleSketchfab ========= Sketchfab uses OAuth 2 for authentication. To use: - Follow the steps at `Sketchfab Oauth`_, and ask for an ``Authorization code`` grant type. - Fill the ``Client id/key`` and ``Client Secret`` values you received in your django settings:: SOCIAL_AUTH_SKETCHFAB_KEY = '' SOCIAL_AUTH_SKETCHFAB_SECRET = '' .. _Sketchfab Oauth: https://sketchfab.com/developers/oauth python-social-auth-0.2.21/docs/backends/flickr.rst0000644000175500017550000000103112754357263021575 0ustar debacledebacleFlickr ====== Flickr uses OAuth v1.0 for authentication. - Register a new application at the `Flickr App Garden`_, and - fill ``Key`` and ``Secret`` values in the settings:: SOCIAL_AUTH_FLICKR_KEY = '' SOCIAL_AUTH_FLICKR_SECRET = '' - Flickr might show a messages saying "Oops! Flickr doesn't recognise the permission set.", if encountered with this error, just define this setting:: SOCIAL_AUTH_FLICKR_AUTH_EXTRA_ARGUMENTS = {'perms': 'read'} .. _Flickr App Garden: http://www.flickr.com/services/apps/create/ python-social-auth-0.2.21/docs/backends/amazon.rst0000644000175500017550000000162712754357263021623 0ustar debacledebacleAmazon ====== Amazon implemented OAuth2 protocol for their authentication mechanism. To enable ``python-social-auth`` support follow this steps: 1. Go to `Amazon App Console`_ and create an application. 2. Fill App Id and Secret in your project settings:: SOCIAL_AUTH_AMAZON_KEY = '...' SOCIAL_AUTH_AMAZON_SECRET = '...' 3. Enable the backend:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.amazon.AmazonOAuth2', ... ) Further documentation at `Website Developer Guide`_ and `Getting Started for Web`_. **Note:** This backend supports TLSv1 protocol since SSL will be deprecated from May 25, 2015 .. _Amazon App Console: http://login.amazon.com/manageApps .. _Website Developer Guide: https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf .. _Getting Started for Web: http://login.amazon.com/website python-social-auth-0.2.21/docs/backends/foursquare.rst0000644000175500017550000000071612754357263022530 0ustar debacledebacleFoursquare ========== Foursquare uses OAuth2. In order to enable the backend follow: - Register an application at `Foursquare Developers Portal`_, set the ``Redirect URI`` to ``http:///complete/foursquare/`` - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_FOURSQUARE_KEY = '' SOCIAL_AUTH_FOURSQUARE_SECRET = '' .. _Foursquare Developers Portal: https://foursquare.com/developers/register python-social-auth-0.2.21/docs/backends/runkeeper.rst0000644000175500017550000000051612754357263022332 0ustar debacledebacleRunKeeper ========= RunKeeper uses OAuth v2 for authentication. - Register a new application at the `RunKeeper API`_, and - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_RUNKEEPER_KEY = '' SOCIAL_AUTH_RUNKEEPER_SECRET = '' .. _RunKeeper API: http://developer.runkeeper.com/healthgraph python-social-auth-0.2.21/docs/backends/pixelpin.rst0000644000175500017550000000211312754357263022155 0ustar debacledebaclePixelPin ======== PixelPin only supports OAuth2. PixelPin OAuth2 --------------- Developer documentation for PixelPin can be found at http://developer.pixelpin.co.uk/. To setup OAuth2 do the following: - Register a new developer account at `PixelPin Developers`_. You require a PixelPin account to create developer accounts. Sign up at `PixelPin Account Page`_ For the value of redirect uri, use whatever path you need to return to on your web application. The example code provided with the plugin uses ``http:///complete/pixelpin-oauth2/``. Once verified by email, record the values of client id and secret for the next step. - Fill **Consumer Key** and **Consumer Secret** values in your settings.py file:: SOCIAL_AUTH_PIXELPIN_OAUTH2_KEY = '' SOCIAL_AUTH_PIXELPIN_OAUTH2_SECRET = '' - Add ``'social.backends.pixelpin.PixelPinOAuth2'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. .. _PixelPin homepage: http://pixelpin.co.uk/ .. _PixelPin Account Page: https://login.pixelpin.co.uk/ .. _PixelPin Developers: http://developer.pixelpin.co.uk/ python-social-auth-0.2.21/docs/backends/battlenet.rst0000644000175500017550000000234112754357263022312 0ustar debacledebacleBattle.net ========== Blizzard implemented OAuth2 protocol for their authentication mechanism. To enable ``python-social-auth`` support follow this steps: 1. Go to `Battlenet Developer Portal`_ and create an application. 2. Fill App Id and Secret in your project settings:: SOCIAL_AUTH_BATTLENET_OAUTH2_KEY = '...' SOCIAL_AUTH_BATTLENET_OAUTH2_SECRET = '...' 3. Enable the backend:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.battlenet.BattleNetOAuth2', ... ) Note: If you want to allow the user to choose a username from his own characters, some further steps are required, see the use cases part of the documentation. To get the account id and battletag use the user_data function, as `account id is no longer passed inherently`_. Another note: If you get a 500 response "Internal Server Error" the API now requires `https on callback endpoints`_. Further documentation at `Developer Guide`_. .. _Battlenet Developer Portal: https://dev.battle.net/ .. _Developer Guide: https://dev.battle.net/docs/read/oauth .. _https on callback endpoints: http://us.battle.net/en/forum/topic/17085510584 .. _account id is no longer passed inherently: http://us.battle.net/en/forum/topic/18300183303 python-social-auth-0.2.21/docs/backends/strava.rst0000644000175500017550000000064012754357263021630 0ustar debacledebacleStrava ========= Strava uses OAuth v2 for Authentication. - Register a new application at the `Strava API`_, and - fill ``Client ID`` and ``Client Secret`` from strava.com values in the settings:: SOCIAL_AUTH_STRAVA_KEY = '' SOCIAL_AUTH_STRAVA_SECRET = '' - extra scopes can be defined by using:: SOCIAL_AUTH_STRAVA_SCOPE = ['view_private'] .. _Strava API: https://www.strava.com/settings/api python-social-auth-0.2.21/docs/backends/tumblr.rst0000644000175500017550000000061412754357263021636 0ustar debacledebacleTumblr ====== Tumblr uses OAuth 1.0a for authentication. - Register a new application at http://www.tumblr.com/oauth/apps - Set the ``Default callback URL`` to http://[your domain]/ - fill ``OAuth Consumer Key`` and ``Secret Key`` values in your Django settings:: SOCIAL_AUTH_TUMBLR_KEY = '' SOCIAL_AUTH_TUMBLR_SECRET = '' .. _Tumblr API: http://www.tumblr.com/docs/en/api/v2 python-social-auth-0.2.21/docs/backends/douban.rst0000644000175500017550000000265212754357263021605 0ustar debacledebacleDouban ====== Douban supports OAuth 1 and 2. Douban OAuth1 ------------- Douban OAuth 1 works similar to Twitter OAuth. Douban offers per application keys named ``Consumer Key`` and ``Consumer Secret``. To enable Douban OAuth these two keys are needed. Further documentation at `Douban Services & API`_: - Register a new application at `Douban API Key`_, make sure to mark the **web application** checkbox. - Fill **Consumer Key** and **Consumer Secret** values in settings:: SOCIAL_AUTH_DOUBAN_KEY = '' SOCIAL_AUTH_DOUBAN_SECRET = '' - Add ``'social.backends.douban.DoubanOAuth'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. Douban OAuth2 ------------- Recently Douban launched their OAuth2 support and the new developer site, you can find documentation at `Douban Developers`_. To setup OAuth2 follow: - Register a new application at `Create A Douban App`_, make sure to mark the **web application** checkbox. - Fill **Consumer Key** and **Consumer Secret** values in settings:: SOCIAL_AUTH_DOUBAN_OAUTH2_KEY = '' SOCIAL_AUTH_DOUBAN_OAUTH2_SECRET = '' - Add ``'social.backends.douban.DoubanOAuth2'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. .. _Douban Services & API: http://www.douban.com/service/ .. _Douban API Key: http://www.douban.com/service/apikey/apply .. _Douban Developers: http://developers.douban.com/ .. _Create A Douban App : http://developers.douban.com/apikey/apply python-social-auth-0.2.21/docs/backends/disqus.rst0000644000175500017550000000102412754357263021635 0ustar debacledebacleDisqus ====== Disqus uses OAuth v2 for Authentication. - Register a new application at the `Disqus API`_, and - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_DISQUS_KEY = '' SOCIAL_AUTH_DISQUS_SECRET = '' - extra scopes can be defined by using:: SOCIAL_AUTH_DISQUS_AUTH_EXTRA_ARGUMENTS = {'scope': 'likes comments relationships'} Check `Disqus Auth API`_ for details. .. _Disqus Auth API: http://disqus.com/api/docs/auth/ .. _Disqus API: http://disqus.com/api/applications/ python-social-auth-0.2.21/docs/backends/live.rst0000644000175500017550000000144212754357263021270 0ustar debacledebacleMSN Live Connect ================ Live uses OAuth2 for its connect workflow, notice that it isn't OAuth WRAP. - Register a new application at `Live Connect Developer Center`_, set your site domain as redirect domain, - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_LIVE_KEY = '' SOCIAL_AUTH_LIVE_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_LIVE_SCOPE = [...] Defaults are ``wl.basic`` and ``wl.emails``. Latter one is necessary to retrieve user email. - Ensure to have a valid ``Redirect URL`` (``http://your-domain/complete/live``) defined in the application if ``Enhanced security redirection`` is enabled. .. _Live Connect Developer Center: https://account.live.com/developers/applications/create python-social-auth-0.2.21/docs/backends/openid.rst0000644000175500017550000000337312754357263021614 0ustar debacledebacleOpenId ====== OpenId_ support is simpler to implement than OAuth_. Google and Yahoo providers are supported by default, others are supported by POST method providing endpoint URL. OpenId_ backends can store extra data in ``UserSocialAuth.extra_data`` field by defining a set of values names to retrieve from any of the used schemas, AttributeExchange and SimpleRegistration. As their keywords differ we need two settings. Settings is per backend, so we have two possible values for each one. Name is dynamically checked using uppercase backend name as prefix:: SOCIAL_AUTH__SREG_EXTRA_DATA SOCIAL_AUTH__AX_EXTRA_DATA Example:: SOCIAL_AUTH_GOOGLE_SREG_EXTRA_DATA = [(..., ...)] SOCIAL_AUTH_GOOGLE_AX_EXTRA_DATA = [(..., ...)] Settings must be a list of tuples mapping value name in response and value alias used to store. A third value (boolean) is supported to, it's purpose is to signal if the value should be discarded if it evaluates to ``False``, this is to avoid replacing old (needed) values when they don't form part of current response. If not present, then this check is avoided and the value will replace any data. Username -------- The OpenId_ backend will check for a ``username`` key in the values returned by the server, but default to ``first-name`` + ``last-name`` if that key is missing. It's possible to indicate the username key in the values If the username is under a different key with a setting, but backends should have defined a default value. For example:: SOCIAL_AUTH_FEDORA_USERNAME_KEY = 'nickname' This setting indicates that the username should be populated by the ``nickname`` value in the Fedora OpenId_ provider. .. _OpenId: http://openid.net/ .. _OAuth: http://oauth.net/ python-social-auth-0.2.21/docs/backends/yammer.rst0000644000175500017550000000126412754357263021625 0ustar debacledebacleYammer ====== Yammer users OAuth2 for their auth mechanism, this application supports Yammer OAuth2 in production and staging modes. Production Mode --------------- In order to enable the backend, follow: - Register an application at `Client Applications`_, set the ``Redirect URI`` to ``http:///complete/yammer/`` - Fill **Client Key** and **Client Secret** settings:: SOCIAL_AUTH_YAMMER_KEY = '...' SOCIAL_AUTH_YAMMER_SECRET = '...' Staging Mode ------------ Staging mode is configured the same as ``Production Mode``, but settings are prefixed with:: SOCIAL_AUTH_YAMMER_STAGING_* .. _Client Applications: https://www.yammer.com/client_applications python-social-auth-0.2.21/docs/backends/reddit.rst0000644000175500017550000000225512754357263021607 0ustar debacledebacleReddit ====== Reddit implements `OAuth2 authentication workflow`_. To enable it, just follow: - Register an application at `Reddit Preferences Apps`_ - Fill the **Consumer Key** and **Consumer Secret** values in your settings:: SOCIAL_AUTH_REDDIT_KEY = '' SOCIAL_AUTH_REDDIT_SECRET = '' - By default the token is not permanent, it will last an hour. To get a refresh token just define:: SOCIAL_AUTH_REDDIT_AUTH_EXTRA_ARGUMENTS = {'duration': 'permanent'} This will store the ``refresh_token`` in ``UserSocialAuth.extra_data`` attribute, to refresh the access token just do:: from social.apps.django_app.utils import load_strategy strategy = load_strategy(backend='reddit') user = User.objects.get(pk=foo) social = user.social_auth.filter(provider='reddit')[0] social.refresh_token(strategy=strategy, redirect_uri='http://localhost:8000/complete/reddit/') Reddit requires ``redirect_uri`` when refreshing the token and it must be the same value used during the auth process. .. _Reddit Preferences Apps: https://ssl.reddit.com/prefs/apps/ .. _OAuth2 authentication workflow: https://github.com/reddit/reddit/wiki/OAuth2 python-social-auth-0.2.21/docs/backends/jawbone.rst0000644000175500017550000000126312754357263021757 0ustar debacledebacleJawbone ======= Jawbone uses OAuth2. In order to enable the backend follow: - Register an application at `Jawbone Developer Portal`_, set the ``OAuth redirect URIs`` to ``http:///complete/jawbone/`` - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_JAWBONE_KEY = '' SOCIAL_AUTH_JAWBONE_SECRET = '' - Specify scopes with:: SOCIAL_AUTH_JAWBONE_SCOPE = [...] Available scopes are listed in the `Jawbone Authentication Reference`_, "socpes" section. .. _Jawbone Developer Portal: https://jawbone.com/up/developer/account/ .. _Jawbone Authentication Reference: https://jawbone.com/up/developer/authentication python-social-auth-0.2.21/docs/backends/moves.rst0000644000175500017550000000161012754357263021457 0ustar debacledebacleMoves ===== Moves_ provides an OAuth2 authentication flow. In order to enable it: - Register an application at `Manage Your Apps`_, remember to fill the ``Redirect URI`` once the application was created. - Fill **Client ID** and **Client secret** in the settings:: SOCIAL_AUTH_MOVES_KEY = '' SOCIAL_AUTH_MOVES_SECRET = '' - Define the mandatory scope for your application:: SOCIAL_AUTH_MOVES_SCOPE = ['activity', 'location'] The scope parameter is required by Moves_ but the backend doesn't set a default one to minimize the application permissions request, so it's mandatory for the developer to define this setting. - Add the backend to the ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.moves.MovesOAuth2', ... ) .. _Moves: http://moves-app.com/ .. _Manage Your Apps: https://dev.moves-app.com/apps python-social-auth-0.2.21/docs/backends/openstreetmap.rst0000644000175500017550000000127612754357263023224 0ustar debacledebacleOpenStreetMap ============= OpenStreetMap supports OAuth 1.0 and 1.0a but 1.0a should be used for the new applications, as 1.0 is for support of legacy clients only. Access tokens currently do not expire automatically. More documentation at `OpenStreetMap Wiki`_: - Login to your account - Register your application as OAuth consumer on your `OpenStreetMap user settings page`_, and - Set ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_OPENSTREETMAP_KEY = '' SOCIAL_AUTH_OPENSTREETMAP_SECRET = '' .. _OpenStreetMap Wiki: http://wiki.openstreetmap.org/wiki/OAuth .. _OpenStreetMap user settings page: http://www.openstreetmap.org/user/username/oauth_clients/new python-social-auth-0.2.21/docs/backends/loginradius.rst0000644000175500017550000000357112754357263022656 0ustar debacledebacleLoginRadius =========== LoginRadius uses OAuth2 for Authentication with other providers with an HTML widget used to trigger the auth process. - Register a new application at the `LoginRadius Website`_, and - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_LOGINRADIUS_KEY = '' SOCIAL_AUTH_LOGINRADIUS_SECRET = '' - Since the auth process is triggered by LoginRadius JS script, you need to sever such content to the user, all you need to do that is a template with the following content::
    Put that content in a template named ``loginradius.html`` (accessible to your framework), or define a name with ``SOCIAL_AUTH_LOGINRADIUS_TEMPLATE`` setting, like:: SOCIAL_AUTH_LOGINRADIUS_LOCAL_HTML = 'loginradius.html' The template context will have the current backend instance under the ``backend`` name, also the application key (``LOGINRADIUS_KEY``) and the redirect URL (``LOGINRADIUS_REDIRECT_URL``). - Further documentation can be found at `LoginRadius API Documentation`_ and `LoginRadius Datapoints`_ .. _LoginRadius Website: https://loginradius.com/ .. _LoginRadius API Documentation: http://api.loginradius.com/help/ .. _LoginRadius Datapoints: http://www.loginradius.com/datapoints/ python-social-auth-0.2.21/docs/backends/justgiving.rst0000644000175500017550000000102512754357263022517 0ustar debacledebacleJust Giving =========== OAuth2 ------ Follow the steps at `Just Giving API Docs`_ to register your application and get the needed keys. - Add the Just Giving OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.justgiving.JustGivingOAuth2', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_JUSTGIVING_KEY = '' SOCIAL_AUTH_JUSTGIVING_SECRET = '' .. _Just Giving API Docs: https://api.justgiving.com/docs python-social-auth-0.2.21/docs/backends/fitbit.rst0000644000175500017550000000200512754357263021606 0ustar debacledebacleFitbit ====== Fitbit supports both OAuth 2.0 and OAuth 1.0a logins. OAuth 2 is preferred for new integrations, as OAuth 1.0a does not support getting heartrate or location and will be deprecated in the future. 1. Register a new OAuth Consumer `here`_ 2. Configure the appropriate settings for OAuth 2.0 or OAuth 1.0a (see below). OAuth 2.0 or OAuth 1.0a ----------------------- - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_FITBIT_KEY = '' SOCIAL_AUTH_FITBIT_SECRET = '' OAuth 2.0 specific settings --------------------------- By default, only the ``profile`` scope is requested. To request more scopes, set SOCIAL_AUTH_FITBIT_SCOPE:: SOCIAL_AUTH_FITBIT_SCOPE = [ 'activity', 'heartrate', 'location', 'nutrition', 'profile', 'settings', 'sleep', 'social', 'weight' ] The above will request all permissions from the user. .. _here: https://dev.fitbit.com/apps/new python-social-auth-0.2.21/docs/backends/coursera.rst0000644000175500017550000000150512754357263022154 0ustar debacledebacleCoursera ============ Coursera uses a variant of OAuth2 authentication. The details of the API can be found at `OAuth2-based APIs - Coursera Technology`_. Take the following steps in order to use the backend: 1. Create an account at `Coursera`_. 2. Open `Developer Console`_, create an organisation and application. 3. Set **Client ID** as a ``SOCIAL_AUTH_COURSERA_KEY`` and **Secret Key** as a ``SOCIAL_AUTH_COURSERA_SECRET`` in your local settings. 4. Add the backend to ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.coursera.CourseraOAuth2', ... ) .. _OAuth2-based APIs - Coursera Technology: https://tech.coursera.org/app-platform/oauth2/ .. _Coursera: https://accounts.coursera.org/console .. _Developer Console: https://accounts.coursera.org/console python-social-auth-0.2.21/docs/backends/twitter.rst0000644000175500017550000000256612754357263022043 0ustar debacledebacleTwitter ======= Twitter offers per application keys named ``Consumer Key`` and ``Consumer Secret``. To enable Twitter these two keys are needed. Further documentation at `Twitter development resources`_: - Register a new application at `Twitter App Creation`_, - Check the **Allow this application to be used to Sign in with Twitter** checkbox. If you don't check this box, Twitter will force your user to login every time. - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_TWITTER_KEY = '' SOCIAL_AUTH_TWITTER_SECRET = '' - You need to specify an URL callback or the application will be marked as Client type instead of the Browser. Almost any dummy value will work if you plan some test. - You can request user's Email address (consult `Twitter verify credentials`_), the parameter is sent automatically, but the applicaton needs to be whitelisted in order to get a valid value. Twitter usually fails with a 401 error when trying to call the request-token URL, this is usually caused by server datetime errors (check miscellaneous section). Installing ``ntp`` and syncing the server date with some pool does the trick. .. _Twitter development resources: http://dev.twitter.com/pages/auth .. _Twitter App Creation: http://twitter.com/apps/new .. _Twitter verify credentials: https://dev.twitter.com/rest/reference/get/account/verify_credentials python-social-auth-0.2.21/docs/backends/dropbox.rst0000644000175500017550000000166612754357263022016 0ustar debacledebacleDropbox ======= Dropbox supports both OAuth 1 and 2. - Register a new application at `Dropbox Developers`_, and follow the instructions below for the version of OAuth for which you are adding support. OAuth1 ------ Add the Dropbox OAuth backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.dropbox.DropboxOAuth', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_DROPBOX_KEY = '' SOCIAL_AUTH_DROPBOX_SECRET = '' OAuth2 ------ Add the Dropbox OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.dropbox.DropboxOAuth2', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_DROPBOX_OAUTH2_KEY = '' SOCIAL_AUTH_DROPBOX_OAUTH2_SECRET = '' .. _Dropbox Developers: https://www.dropbox.com/developers/apps python-social-auth-0.2.21/docs/logging_out.rst0000644000175500017550000000262012754357263021073 0ustar debacledebacleDisconnect and Logging Out ========================== It's a common misconception that the ``disconnect`` action is the same as logging the user out, but this is not the case. ``Disconnect`` is the way that your users can ask your project to "forget about my account". This implies removing the ``UserSocialAuth`` instance that was created, this also implies that the user won't be able to login back into your site with the social account. Instead the action will be a signup, a new user instance will be created, not related to the previous one. Logging out is just a way to say "forget my current session", and usually implies removing cookies, invalidating a session hash, etc. The many frameworks have their own ways to logout an account (Django has ``django.contrib.auth.logout``), ``flask-login`` has it's own way too with `logout_user()`_. Since disconnecting a social account means that the user won't be able to log back in with that social provider into the same user, python-social-auth will check that the user account is in a valid state for disconnection (it has at least one more social account associated, or a password, etc). This behavior can be overridden by changing the `Disconnection Pipeline`_. .. _logout_user(): https://github.com/maxcountryman/flask-login/blob/a96de342eae560deec008a02179f593c3799b3ba/flask_login.py#L718-L739 .. _Disconnection Pipeline: pipeline.html#disconnection-pipeline python-social-auth-0.2.21/docs/thanks.rst0000644000175500017550000001412712754357263020053 0ustar debacledebacleThanks ====== python-social-auth_ is the result of almost 3 years of development done on django-social-auth_ which is the result of my initial work and the thousands lines of code contributed by so many developers that took time to work on improvements, report bugs and hunt them down to propose a fix. So, here is a big list of users that helped to build this library (if somebody is missed let me know and I'll update the list): * kjoconnor_ * krvss_ * estebistec_ * mrmch_ * uruz_ * maraujop_ * bacher09_ * dokterbob_ * hassek_ * andrusha_ * vicalloy_ * caioariede_ * danielgtaylor_ * stephenmcd_ * gugu_ * yrik_ * dhendo_ * yekibud_ * tmackenzie_ * LuanP_ * jezdez_ * serdardalgic_ * Jolmberg_ * ChrisCooper_ * marselester_ * eshellman_ * micrypt_ * revolunet_ * dasevilla_ * seansay_ * hepochen_ * gibuloto_ * crodjer_ * sidmitra_ * ryr_ * inve1_ * mback2k_ * hannesstruss_ * NorthIsUp_ * tonyxiao_ * dhepper_ * Troytft_ * gardaud_ * oinopion_ * gameguy43_ * vinigracindo_ * syabro_ * bashmish_ * ggreer_ * avillavi_ * r4vi_ * roderyc_ * daonb_ * slon7_ * JasonGiedymin_ * tymofij_ * Cassus_ * martey_ * t0m_ * johnthedebs_ * jammons_ * stefanw_ * maxgrosse_ * mattucf_ * tadeo_ * haxoza_ * bradbeattie_ * henward0_ * bernardokyotoku_ * czpython_ * glasscube42_ * assiotis_ * dbaxa_ * JasonSanford_ * originell_ * cihann_ * niftynei_ * mikesun_ * 1st_ * betonimig_ * ozexpert_ * stephenLee_ * julianvargasalvarez_ * youngrok_ * garrypolley_ * amirouche_ * fmoga_ * pydanny_ * pygeek_ * dgouldin_ * kotslon_ * kirkchris_ * barracel_ * sayar_ * kulbir_ * Morgul_ * spstpl_ * bluszcz_ * vbsteven_ * sbassi_ * aspcanada_ * browniebroke_ .. _python-social-auth: https://github.com/omab/python-social-auth .. _django-social-auth: https://github.com/omab/django-social-auth .. _kjoconnor: https://github.com/kjoconnor .. _krvss: https://github.com/krvss .. _estebistec: https://github.com/estebistec .. _mrmch: https://github.com/mrmch .. _uruz: https://github.com/uruz .. _maraujop: https://github.com/maraujop .. _bacher09: https://github.com/bacher09 .. _dokterbob: https://github.com/dokterbob .. _hassek: https://github.com/hassek .. _andrusha: https://github.com/andrusha .. _vicalloy: https://github.com/vicalloy .. _caioariede: https://github.com/caioariede .. _danielgtaylor: https://github.com/danielgtaylor .. _stephenmcd: https://github.com/stephenmcd .. _gugu: https://github.com/gugu .. _yrik: https://github.com/yrik .. _dhendo: https://github.com/dhendo .. _yekibud: https://github.com/yekibud .. _tmackenzie: https://github.com/tmackenzie .. _LuanP: https://github.com/LuanP .. _jezdez: https://github.com/jezdez .. _serdardalgic: https://github.com/serdardalgic .. _Jolmberg: https://github.com/Jolmberg .. _ChrisCooper: https://github.com/ChrisCooper .. _marselester: https://github.com/marselester .. _eshellman: https://github.com/eshellman .. _micrypt: https://github.com/micrypt .. _revolunet: https://github.com/revolunet .. _dasevilla: https://github.com/dasevilla .. _seansay: https://github.com/seansay .. _hepochen: https://github.com/hepochen .. _gibuloto: https://github.com/gibuloto .. _crodjer: https://github.com/crodjer .. _sidmitra: https://github.com/sidmitra .. _ryr: https://github.com/ryr .. _inve1: https://github.com/inve1 .. _mback2k: https://github.com/mback2k .. _hannesstruss: https://github.com/hannesstruss .. _NorthIsUp: https://github.com/NorthIsUp .. _tonyxiao: https://github.com/tonyxiao .. _dhepper: https://github.com/dhepper .. _Troytft: https://github.com/Troytft .. _gardaud: https://github.com/gardaud .. _oinopion: https://github.com/oinopion .. _gameguy43: https://github.com/gameguy43 .. _vinigracindo: https://github.com/vinigracindo .. _syabro: https://github.com/syabro .. _bashmish: https://github.com/bashmish .. _ggreer: https://github.com/ggreer .. _avillavi: https://github.com/avillavi .. _r4vi: https://github.com/r4vi .. _roderyc: https://github.com/roderyc .. _daonb: https://github.com/daonb .. _slon7: https://github.com/slon7 .. _JasonGiedymin: https://github.com/JasonGiedymin .. _tymofij: https://github.com/tymofij .. _Cassus: https://github.com/Cassus .. _martey: https://github.com/martey .. _t0m: https://github.com/t0m .. _johnthedebs: https://github.com/johnthedebs .. _jammons: https://github.com/jammons .. _stefanw: https://github.com/stefanw .. _maxgrosse: https://github.com/maxgrosse .. _mattucf: https://github.com/mattucf .. _tadeo: https://github.com/tadeo .. _haxoza: https://github.com/haxoza .. _bradbeattie: https://github.com/bradbeattie .. _henward0: https://github.com/henward0 .. _bernardokyotoku: https://github.com/bernardokyotoku .. _czpython: https://github.com/czpython .. _glasscube42: https://github.com/glasscube42 .. _assiotis: https://github.com/assiotis .. _dbaxa: https://github.com/dbaxa .. _JasonSanford: https://github.com/JasonSanford .. _originell: https://github.com/originell .. _cihann: https://github.com/cihann .. _niftynei: https://github.com/niftynei .. _mikesun: https://github.com/mikesun .. _1st: https://github.com/1st .. _betonimig: https://github.com/betonimig .. _ozexpert: https://github.com/ozexpert .. _stephenLee: https://github.com/stephenLee .. _julianvargasalvarez: https://github.com/julianvargasalvarez .. _youngrok: https://github.com/youngrok .. _garrypolley: https://github.com/garrypolley .. _amirouche: https://github.com/amirouche .. _fmoga: https://github.com/fmoga .. _pydanny: https://github.com/pydanny .. _pygeek: https://github.com/pygeek .. _dgouldin: https://github.com/dgouldin .. _kotslon: https://github.com/kotslon .. _kirkchris: https://github.com/kirkchris .. _barracel: https://github.com/barracel .. _sayar: https://github.com/sayar .. _kulbir: https://github.com/kulbir .. _Morgul: https://github.com/Morgul .. _spstpl: https://github.com/spstpl .. _bluszcz: https://github.com/bluszcz .. _vbsteven: https://github.com/vbsteven .. _sbassi: https://github.com/sbassi .. _aspcanada: https://github.com/aspcanada .. _browniebroke: https://github.com/browniebroke python-social-auth-0.2.21/docs/use_cases.rst0000644000175500017550000003001512754357263020527 0ustar debacledebacleUse Cases ========= Some miscellaneous options and use cases for python-social-auth_. Return the user to the original page ------------------------------------ There's a common scenario to return the user back to the original page from where they requested to login. For that purpose, the usual ``next`` query-string argument is used. The value of this parameter will be stored in the session and later used to redirect the user when login was successful. In order to use it, just define it with your link. For instance, when using Django:: Login with Facebook Pass custom GET/POST parameters and retrieve them on authentication ------------------------------------------------------------------- In some cases, you might need to send data over the URL, and retrieve it while processing the after-effect. For example, for conditionally executing code in custom pipelines. In such cases, add it to ``FIELDS_STORED_IN_SESSION``. In your settings:: FIELDS_STORED_IN_SESSION = ['key'] In template:: Login with Facebook In your custom pipeline, retrieve it using:: strategy.session_get('key') Retrieve Google+ Friends ------------------------ Google provides a `People API endpoint`_ to retrieve the people in your circles on Google+. In order to access that API first we need to define the needed scope:: SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [ 'https://www.googleapis.com/auth/plus.login' ] Once we have the ``access token`` we can call the API like this:: import requests user = User.objects.get(...) social = user.social_auth.get(provider='google-oauth2') response = requests.get( 'https://www.googleapis.com/plus/v1/people/me/people/visible', params={'access_token': social.extra_data['access_token']} ) friends = response.json()['items'] Associate users by email ------------------------ Sometimes it's desirable that social accounts are automatically associated if the email already matches a user account. For example, if a user signed up with his Facebook account, then logged out and next time tries to use Google OAuth2 to login, it could be nice (if both social sites have the same email address configured) that the user gets into his initial account created by Facebook backend. This scenario is possible by enabling the ``associate_by_email`` pipeline function, like this:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.social_auth.associate_by_email', # <--- enable this one 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', ) This feature is disabled by default because it's not 100% secure to automate this process with all the backends. Not all the providers will validate your email account and others users could take advantage of that. Take for instance User A registered in your site with the email ``foo@bar.com``. Then a malicious user registers into another provider that doesn't validate his email with that same account. Finally this user will turn to your site (which supports that provider) and sign up to it, since the email is the same, the malicious user will take control over the User A account. Signup by OAuth access_token ---------------------------- It's a common scenario that mobile applications will use an SDK to signup a user within the app, but that signup won't be reflected by python-social-auth_ unless the corresponding database entries are created. In order to do so, it's possible to create a view / route that creates those entries by a given ``access_token``. Take the following code for instance (the code follows Django conventions, but versions for others frameworks can be implemented easily):: from django.contrib.auth import login from social.apps.django_app.utils import psa # Define an URL entry to point to this view, call it passing the # access_token parameter like ?access_token=. The URL entry must # contain the backend, like this: # # url(r'^register-by-token/(?P[^/]+)/$', # 'register_by_access_token') @psa('social:complete') def register_by_access_token(request, backend): # This view expects an access_token GET parameter, if it's needed, # request.backend and request.strategy will be loaded with the current # backend and strategy. token = request.GET.get('access_token') user = request.backend.do_auth(request.GET.get('access_token')) if user: login(request, user) return 'OK' else: return 'ERROR' The snippet above is quite simple, it doesn't return JSON and usually this call will be done by AJAX. It doesn't return the user information, but that's something that can be extended and filled to suit the project where it's going to be used. Multiple scopes per provider ---------------------------- At the moment python-social-auth_ doesn't provide a method to define multiple scopes for single backend, this is usually desired since it's recommended to ask the user for the minimum scope possible and increase the access when it's really needed. It's possible to add a new backend extending the original one to accomplish that behavior. There are two ways to do it. 1. Overriding ``get_scope()`` method:: from social.backends.facebook import FacebookOAuth2 class CustomFacebookOAuth2(FacebookOauth2): def get_scope(self): scope = super(CustomFacebookOAuth2, self).get_scope() if self.data.get('extrascope'): scope = scope + [('foo', 'bar')] return scope This method is quite simple, it overrides the method that returns the scope value in a backend (``get_scope()``) and adds extra values tot he list if it was indicated by a parameter in the ``GET`` or ``POST`` data (``self.data``). Put this new backend in some place in your project and replace the original ``FacebookOAuth2`` in ``AUTHENTICATION_BACKENDS`` with this new version. When overriding this method, take into account that the default output the base class for ``get_scope()`` is the raw value from the settings (whatever they are defined), doing this will actually update the value in your settings for all the users:: scope = super(CustomFacebookOAuth2, self).get_scope() scope += ['foo', 'bar'] Instead do it like this:: scope = super(CustomFacebookOAuth2, self).get_scope() scope = scope + ['foo', 'bar'] 2. It's possible to do the same by defining a second backend which extends from the original but overrides the name, this will imply new URLs and also new settings for the new backend (since the name is used to build the settings names), it also implies a new application in the provider since not all providers give you the option of defining multiple redirect URLs. To do it just add a backend like:: from social.backends.facebook import FacebookOAuth2 class CustomFacebookOAuth2(FacebookOauth2): name = 'facebook-custom' Put this new backend in some place in your project keeping the original ``FacebookOAuth2`` in ``AUTHENTICATION_BACKENDS``. Now a new set of URLs will be functional:: /login/facebook-custom /complete/facebook-custom /disconnect/facebook-custom And also a new set of settings:: SOCIAL_AUTH_FACEBOOK_CUSTOM_KEY = '...' SOCIAL_AUTH_FACEBOOK_CUSTOM_SECRET = '...' SOCIAL_AUTH_FACEBOOK_CUSTOM_SCOPE = [...] When the extra permissions are needed, just redirect the user to ``/login/facebook-custom`` and then get the social auth entry for this new backend with ``user.social_auth.get(provider='facebook-custom')`` and use the ``access_token`` in it. Enable a user to choose a username from his World of Warcraft characters ------------------------------------------------------------------------ If you want to register new users on your site via battle.net, you can enable these users to choose a username from their own World-of-Warcraft characters. To do this, use the ``battlenet-oauth2`` backend along with a small form to choose the username. The form is rendered via a partial pipeline item like this:: @partial def pick_character_name(backend, details, response, is_new=False, *args, **kwargs): if backend.name == 'battlenet-oauth2' and is_new: data = backend.strategy.request_data() if data.get('character_name') is None: # New user and didn't pick a character name yet, so we render # and send a form to pick one. The form must do a POST/GET # request to the same URL (/complete/battlenet-oauth2/). In this # example we expect the user option under the key: # character_name # you have to filter the result list according to your needs. # In this example, only guild members are allowed to sign up. char_list = [ c['name'] for c in backend.get_characters(response.get('access_token')) if 'guild' in c and c['guild'] == '' ] return render_to_response('pick_character_form.html', {'charlist': char_list, }) else: # The user selected a character name return {'username': data.get('character_name')} Don't forget to add the partial to the pipeline:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'path.to.pick_character_name', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', ) It needs to be somewhere before create_user because the partial will change the username according to the users choice. Re-prompt Google OAuth2 users to refresh the ``refresh_token`` -------------------------------------------------------------- A ``refresh_token`` also expire, a ``refresh_token`` can be lost, but they can also be refreshed (or re-fetched) if you ask to Google the right way. In order to do so, set this setting:: SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = { 'access_type': 'offline', 'approval_prompt': 'auto' } Then link the users to ``/login/google-oauth2?approval_prompt=force``. If you want to refresh the ``refresh_token`` only on those users that don't have it, do it with a pipeline function:: def redirect_if_no_refresh_token(backend, response, social, *args, **kwargs): if backend.name == 'google-oauth2' and social and \ response.get('refresh_token') is None and \ social.extra_data.get('refresh_token') is None: return redirect('/login/google-oauth2?approval_prompt=force') Set this pipeline after ``social_user``:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'path.to.redirect_if_no_refresh_token', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', ) .. _python-social-auth: https://github.com/omab/python-social-auth .. _People API endpoint: https://developers.google.com/+/api/latest/people/list python-social-auth-0.2.21/docs/intro.rst0000644000175500017550000001222312754357263017711 0ustar debacledebacleIntroduction ============ Python Social Auth aims to be an easy to setup social authentication and authorization mechanism for Python projects supporting protocols like OAuth_ (1 and 2), OpenId_ and others. Features -------- This application provides user registration and login using social sites credentials, here are some features, probably not a full list yet. Supported frameworks ******************** Multiple frameworks support: * Django_ * Flask_ * Pyramid_ * Webpy_ * Tornado_ More frameworks can be added easily (and should be even easier in the future once the code matures). Auth providers ************** Several supported service by simple backends definition (easy to add new ones or extend current one): * Angel_ OAuth2 * Beats_ OAuth2 * Behance_ OAuth2 * Bitbucket_ OAuth1 * Box_ OAuth2 * Dailymotion_ OAuth2 * Deezer_ OAuth2 * Disqus_ OAuth2 * Douban_ OAuth1 and OAuth2 * Dropbox_ OAuth1 * Evernote_ OAuth1 * Facebook_ OAuth2 and OAuth2 for Applications * Fitbit_ OAuth2 and OAuth1 * Flickr_ OAuth1 * Foursquare_ OAuth2 * `Google App Engine`_ Auth * Github_ OAuth2 * Google_ OAuth1, OAuth2 and OpenId * Instagram_ OAuth2 * Kakao_ OAuth2 * Linkedin_ OAuth1 * Live_ OAuth2 * Livejournal_ OpenId * Mailru_ OAuth2 * MineID_ OAuth2 * Mixcloud_ OAuth2 * `Mozilla Persona`_ * NaszaKlasa_ OAuth2 * `NGPVAN ActionID`_ OpenId * Odnoklassniki_ OAuth2 and Application Auth * OpenId_ * Podio_ OAuth2 * Pinterest_ OAuth2 * Rdio_ OAuth1 and OAuth2 * Readability_ OAuth1 * Shopify_ OAuth2 * Skyrock_ OAuth1 * Soundcloud_ OAuth2 * Spotify_ OAuth2 * ThisIsMyJam_ OAuth1 * Stackoverflow_ OAuth2 * Steam_ OpenId * Stocktwits_ OAuth2 * Stripe_ OAuth2 * Tripit_ OAuth1 * Tumblr_ OAuth1 * Twilio_ Auth * Twitch_ OAuth2 * Twitter_ OAuth1 * Upwork_ OAuth1 * Vimeo_ OAuth1 * VK.com_ OpenAPI, OAuth2 and OAuth2 for Applications * Weibo_ OAuth2 * Wunderlist_ OAuth2 * Xing_ OAuth1 * Yahoo_ OpenId and OAuth1 * Yammer_ OAuth2 * Yandex_ OAuth1, OAuth2 and OpenId User data ********* Basic user data population, to allow custom fields values from providers response. Social accounts association *************************** Multiple social accounts can be associated to a single user. Authentication and disconnection processing ******************************************* Extensible pipeline to handle authentication, association and disconnection mechanism in ways that suits your project. Check `Authentication Pipeline`_ section. .. _OpenId: http://openid.net/ .. _OAuth: http://oauth.net/ .. _myOpenID: https://www.myopenid.com/ .. _Angel: https://angel.co .. _Beats: https://www.beats.com .. _Behance: https://www.behance.net .. _Bitbucket: https://bitbucket.org .. _Box: https://www.box.com .. _Dailymotion: https://dailymotion.com .. _Deezer: https://www.deezer.com .. _Disqus: https://disqus.com .. _Douban: http://www.douban.com .. _Dropbox: https://dropbox.com .. _Evernote: https://www.evernote.com .. _Facebook: https://www.facebook.com .. _Fitbit: https://fitbit.com .. _Flickr: http://www.flickr.com .. _Foursquare: https://foursquare.com .. _Google App Engine: https://developers.google.com/appengine/ .. _Github: https://github.com .. _Google: http://google.com .. _Instagram: https://instagram.com .. _Kakao: https://kakao.com .. _Linkedin: https://www.linkedin.com .. _Live: https://www.live.com .. _Livejournal: http://livejournal.com .. _Mailru: https://mail.ru .. _MineID: https://www.mineid.org .. _Mixcloud: https://www.mixcloud.com .. _Mozilla Persona: http://www.mozilla.org/persona/ .. _NaszaKlasa: https://developers.nk.pl/ .. _NGPVAN ActionID: http://developers.ngpvan.com/action-id .. _Odnoklassniki: http://www.odnoklassniki.ru .. _Podio: https://podio.com .. _Shopify: http://shopify.com .. _Skyrock: https://skyrock.com .. _Soundcloud: https://soundcloud.com .. _Spotify: https://www.spotify.com .. _ThisIsMyJam: https://thisismyjam.com .. _Stocktwits: https://stocktwits.com .. _Stripe: https://stripe.com .. _Tripit: https://www.tripit.com .. _Twilio: https://www.twilio.com .. _Twitch: http://www.twitch.tv/ .. _Twitter: http://twitter.com .. _VK.com: http://vk.com .. _Weibo: http://weibo.com .. _Wunderlist: http://wunderlist.com .. _Xing: https://www.xing.com .. _Yahoo: http://yahoo.com .. _Yammer: https://www.yammer.com .. _Yandex: https://yandex.ru .. _Pinterest: https://www.pinterest.com .. _Readability: http://www.readability.com/ .. _Stackoverflow: http://stackoverflow.com/ .. _Steam: http://steamcommunity.com/ .. _Rdio: https://www.rdio.com .. _Vimeo: https://vimeo.com/ .. _Tumblr: http://www.tumblr.com/ .. _Django: https://github.com/omab/python-social-auth/tree/master/social/apps/django_app .. _Flask: https://github.com/omab/python-social-auth/tree/master/social/apps/flask_app .. _Pyramid: http://www.pylonsproject.org/projects/pyramid/about .. _Webpy: https://github.com/omab/python-social-auth/tree/master/social/apps/webpy_app .. _Tornado: http://www.tornadoweb.org/ .. _Authentication Pipeline: pipeline.html .. _Upwork: https://www.upwork.com python-social-auth-0.2.21/docs/Makefile0000644000175500017550000001102612754357263017464 0ustar debacledebacle# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DjangoSocialAuth.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoSocialAuth.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/DjangoSocialAuth" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DjangoSocialAuth" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." python-social-auth-0.2.21/docs/strategies.rst0000644000175500017550000000417212754357263020734 0ustar debacledebacleStrategies ========== Different strategies are defined to encapsulate the different frameworks capabilities under a common API to reuse as much code as possible. Description ----------- A strategy's responsibility is to provide access to: * Request data and host information and URI building * Session access * Project settings * Response types (HTML and redirects) * HTML rendering Different frameworks implement these features on different ways, thus the need for these interfaces. Implementing a new Strategy --------------------------- The following methods must be defined on strategies sub-classes. Request:: def request_data(self): """Return current request data (POST or GET)""" raise NotImplementedError('Implement in subclass') def request_host(self): """Return current host value""" raise NotImplementedError('Implement in subclass') def build_absolute_uri(self, path=None): """Build absolute URI with given (optional) path""" raise NotImplementedError('Implement in subclass') Session:: def session_get(self, name): """Return session value for given key""" raise NotImplementedError('Implement in subclass') def session_set(self, name, value): """Set session value for given key""" raise NotImplementedError('Implement in subclass') def session_pop(self, name): """Pop session value for given key""" raise NotImplementedError('Implement in subclass') Settings:: def get_setting(self, name): """Return value for given setting name""" raise NotImplementedError('Implement in subclass') Responses:: def html(self, content): """Return HTTP response with given content""" raise NotImplementedError('Implement in subclass') def redirect(self, url): """Return a response redirect to the given URL""" raise NotImplementedError('Implement in subclass') def render_html(self, tpl=None, html=None, context=None): """Render given template or raw html with given context""" raise NotImplementedError('Implement in subclass') python-social-auth-0.2.21/docs/conf.py0000644000175500017550000000133312754357263017323 0ustar debacledebacle# -*- coding: utf-8 -*- extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode'] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'Python Social Auth' copyright = u'2012, Matías Aguirre' exclude_patterns = ['_build'] pygments_style = 'sphinx' html_theme = 'nature' html_static_path = [] htmlhelp_basename = 'PythonSocialAuthdoc' latex_documents = [ ('index', 'PythonSocialAuth.tex', u'Python Social Auth Documentation', u'Matías Aguirre', 'manual'), ] man_pages = [ ('index', 'pythonsocialauth', u'Python Social Auth Documentation', [u'Matías Aguirre'], 1) ] intersphinx_mapping = {'http://docs.python.org/': None} python-social-auth-0.2.21/docs/copyright.rst0000644000175500017550000000076112754357263020572 0ustar debacledebacleCopyrights and Licence ====================== ``python-social-auth`` is protected by BSD licence. Check the LICENCE_ for details. The base work was derived from django-social-auth_ work and copyrighted too, check `django-social-auth LICENCE`_ for details: .. _LICENCE: https://github.com/omab/python-social-auth/blob/master/LICENSE .. _django-social-auth: https://github.com/omab/django-social-auth .. _django-social-auth LICENCE: https://github.com/omab/django-social-auth/blob/master/LICENSE python-social-auth-0.2.21/docs/exceptions.rst0000644000175500017550000000321612754357263020741 0ustar debacledebacleExceptions ========== This set of exceptions were introduced to describe the situations a bit more than just the ``ValueError`` usually raised. ``SocialAuthBaseException`` Base class for all social auth exceptions. ``AuthException`` Base exception class for authentication process errors. ``AuthFailed`` Authentication failed for some reason. ``AuthCanceled`` Authentication was canceled by the user. ``AuthUnknownError`` An unknown error stoped the authentication process. ``AuthTokenError`` Unauthorized or access token error, it was invalid, impossible to authenticate or user removed permissions to it. ``AuthMissingParameter`` A needed parameter to continue the process was missing, usually raised by the services that need some POST data like myOpenID. ``AuthAlreadyAssociated`` A different user has already associated the social account that the current user is trying to associate. ``WrongBackend`` Raised when the backend given in the URLs is invalid (not enabled or registered). ``NotAllowedToDisconnect`` Raised on disconnect action when it's not safe for the user to disconnect the social account, probably because the user lacks a password or another social account. ``AuthStateMissing`` The state parameter is missing from the server response. ``AuthStateForbidden`` The state parameter returned by the server is not the one sent. ``AuthTokenRevoked`` Raised when the user revoked the access_token in the provider. ``AuthUnreachableProvider`` Raised when server couldn't communicate with backend. These are a subclass of ``ValueError`` to keep backward compatibility. python-social-auth-0.2.21/docs/storage.rst0000644000175500017550000001576412754357263020237 0ustar debacledebacleStorage ======= Different frameworks support different ORMs, Storage solves the different interfaces moving the common API to mixins classes. These mixins are used on apps when defining the different models used by ``python-social-auth``. Social User ----------- This model associates a social account data with a user in the system, it contains the provider name and user ID (``uid``) which should identify the social account in the remote provider, plus some extra data (``extra_data``) which is JSON encoded field with extra information from the provider (usually avatars and similar). When implementing this model, it must inherits from UserMixin_ and extend the needed methods: * Username:: @classmethod def get_username(cls, user): """Return the username for given user""" raise NotImplementedError('Implement in subclass') @classmethod def username_max_length(cls): """Return the max length for username""" raise NotImplementedError('Implement in subclass') * User model:: @classmethod def user_model(cls): """Return the user model""" raise NotImplementedError('Implement in subclass') @classmethod def changed(cls, user): """The given user instance is ready to be saved""" raise NotImplementedError('Implement in subclass') @classmethod def user_exists(cls, username): """ Return True/False if a User instance exists with the given arguments. Arguments are directly passed to filter() manager method. """ raise NotImplementedError('Implement in subclass') @classmethod def create_user(cls, username, email=None): """Create a user with given username and (optional) email""" raise NotImplementedError('Implement in subclass') @classmethod def get_user(cls, pk): """Return user instance for given id""" raise NotImplementedError('Implement in subclass') * Social user:: @classmethod def get_social_auth(cls, provider, uid): """Return UserSocialAuth for given provider and uid""" raise NotImplementedError('Implement in subclass') @classmethod def get_social_auth_for_user(cls, user): """Return all the UserSocialAuth instances for given user""" raise NotImplementedError('Implement in subclass') @classmethod def create_social_auth(cls, user, uid, provider): """Create a UserSocialAuth instance for given user""" raise NotImplementedError('Implement in subclass') * Social disconnection:: @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): """Return if it's safe to disconnect the social account for the given user""" raise NotImplementedError('Implement in subclass') @classmethod def disconnect(cls, name, user, association_id=None): """Disconnect the social account for the given user""" raise NotImplementedError('Implement in subclass') Nonce ----- This is a helper class for OpenId mechanism, it stores a one-use number, shouldn't be used by the project since it's for internal use only. When implementing this model, it must inherits from NonceMixin_, and override the needed method:: @classmethod def use(cls, server_url, timestamp, salt): """Create a Nonce instance""" raise NotImplementedError('Implement in subclass') Association ----------- Another OpenId helper class, it stores basic data to keep the OpenId association. Like Nonce_ this is for internal use only. When implementing this model, it must inherits from AssociationMixin_, and override the needed methods:: @classmethod def store(cls, server_url, association): """Create an Association instance""" raise NotImplementedError('Implement in subclass') @classmethod def get(cls, *args, **kwargs): """Get an Association instance""" raise NotImplementedError('Implement in subclass') @classmethod def remove(cls, ids_to_delete): """Remove an Association instance""" raise NotImplementedError('Implement in subclass') Validation code --------------- This class is used to keep track of email validations codes following the usual email validation mechanism of sending an email to the user with a unique code. This model is used by the partial pipeline ``social.pipeline.mail.mail_validation``. Check the docs at *Email validation* in `pipeline docs`_. When implementing the model for your framework only one method needs to be overridden:: @classmethod def get_code(cls, code): """Return the Code instance with the given code value""" raise NotImplementedError('Implement in subclass') Storage interface ----------------- There's a helper class used by strategies to hide the real models names under a common API, an instance of this class is used by strategies to access the storage modules. When implementing this class it must inherits from BaseStorage_, add the needed models references and implement the needed method:: class StorageImplementation(BaseStorage): user = UserModel nonce = NonceModel association = AssociationModel code = CodeModel @classmethod def is_integrity_error(cls, exception): """Check if given exception flags an integrity error in the DB""" raise NotImplementedError('Implement in subclass') SQLAlchemy and Django mixins ---------------------------- Currently there are partial implementations of mixins for `SQLAlchemy ORM`_ and `Django ORM`_ with common code used later on current implemented applications. **When using `SQLAlchemy ORM`_ and ``ZopeTransactionExtension``, it's recommended to use the transaction_ application to handle them.** Models Examples --------------- Check for current implementations for `Django App`_, `Flask App`_, `Pyramid App`_, and `Webpy App`_ for examples of implementations. .. _UserMixin: https://github.com/omab/python-social-auth/blob/master/social/storage/base.py#L15 .. _NonceMixin: https://github.com/omab/python-social-auth/blob/master/social/storage/base.py#L149 .. _AssociationMixin: https://github.com/omab/python-social-auth/blob/master/social/storage/base.py#L161 .. _BaseStorage: https://github.com/omab/python-social-auth/blob/master/social/storage/base.py#L201 .. _SQLAlchemy ORM: https://github.com/omab/python-social-auth/blob/master/social/storage/sqlalchemy_orm.py .. _Django ORM: https://github.com/omab/python-social-auth/blob/master/social/storage/django_orm.py .. _Django App: https://github.com/omab/python-social-auth/blob/master/social/apps/django_app/default/models.py .. _Flask App: https://github.com/omab/python-social-auth/blob/master/social/apps/flask_app/models.py .. _Pyramid App: https://github.com/omab/python-social-auth/blob/master/social/apps/pyramid_app/models.py .. _Webpy App: https://github.com/omab/python-social-auth/blob/master/social/apps/webpy_app/models.py .. _pipeline docs: pipeline.html#email-validation .. _transaction: https://pypi.python.org/pypi/transaction python-social-auth-0.2.21/docs/pipeline.rst0000644000175500017550000003354112754357263020371 0ustar debacledebaclePipeline ======== python-social-auth_ uses an extendible pipeline mechanism where developers can introduce their functions during the authentication, association and disconnection flows. The functions will receive a variable set of arguments related to the current process, common arguments are the current ``strategy``, ``user`` (if any) and ``request``. It's recommended that all the function also define an ``**kwargs`` in the parameters to avoid errors for unexpected arguments. Each pipeline entry can return a ``dict`` or ``None``, any other type of return value is treated as a response instance and returned directly to the client, check *Partial Pipeline* below for details. If a ``dict`` is returned, the value in the set will be merged into the ``kwargs`` argument for the next pipeline entry, ``None`` is taken as if ``{}`` was returned. Authentication Pipeline ----------------------- The final process of the authentication workflow is handled by an operations pipeline where custom functions can be added or default items can be removed to provide a custom behavior. The default pipeline is a mechanism that creates user instances and gathers basic data from providers. The default pipeline is composed by:: ( # Get the information we can about the user and return it in a simple # format to create the user instance later. On some cases the details are # already part of the auth response from the provider, but sometimes this # could hit a provider API. 'social.pipeline.social_auth.social_details', # Get the social uid from whichever service we're authing thru. The uid is # the unique identifier of the given user in the provider. 'social.pipeline.social_auth.social_uid', # Verifies that the current auth process is valid within the current # project, this is where emails and domains whitelists are applied (if # defined). 'social.pipeline.social_auth.auth_allowed', # Checks if the current social-account is already associated in the site. 'social.pipeline.social_auth.social_user', # Make up a username for this person, appends a random string at the end if # there's any collision. 'social.pipeline.user.get_username', # Send a validation email to the user to verify its email address. # Disabled by default. # 'social.pipeline.mail.mail_validation', # Associates the current social details with another user account with # a similar email address. Disabled by default. # 'social.pipeline.social_auth.associate_by_email', # Create a user account if we haven't found one yet. 'social.pipeline.user.create_user', # Create the record that associates the social account with the user. 'social.pipeline.social_auth.associate_user', # Populate the extra_data field in the social record with the values # specified by settings (and the default ones like access_token, etc). 'social.pipeline.social_auth.load_extra_data', # Update the user record with any changed info from the auth service. 'social.pipeline.user.user_details', ) It's possible to override it by defining the setting ``SOCIAL_AUTH_PIPELINE``. For example, a pipeline that won't create users, just accept already registered ones would look like this:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', ) Note that this assumes the user is already authenticated, and thus the ``user`` key in the dict is populated. In cases where the authentication is purely external, a pipeline method must be provided that populates the ``user`` key. Example:: SOCIAL_AUTH_PIPELINE = ( 'myapp.pipeline.load_user', 'social.pipeline.social_auth.social_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', ) Each pipeline function will receive the following parameters: * Current strategy (which gives access to current store, backend and request) * User ID given by authentication provider * User details given by authentication provider * ``is_new`` flag (initialized as ``False``) * Any arguments passed to ``auth_complete`` backend method, default views pass these arguments: * current logged in user (if it's logged in, otherwise ``None``) * current request Disconnection Pipeline ---------------------- Like the authentication pipeline, it's possible to define a disconnection pipeline if needed. For example, this can be useful on sites where a user that disconnects all the related social account is required to fill a password to ensure the authentication process in the future. This can be accomplished by overriding the default disconnection pipeline and setup a function that checks if the user has a password, in case it doesn't a redirect to a fill-your-password form can be returned and later continue the disconnection process, take into account that disconnection ensures the POST method by default, a simple method to ensure this, is to make your form POST to ``/disconnect/`` and set the needed password in your pipeline function. Check *Partial Pipeline* below. In order to override the disconnection pipeline, just define the setting:: SOCIAL_AUTH_DISCONNECT_PIPELINE = ( # Verifies that the social association can be disconnected from the current # user (ensure that the user login mechanism is not compromised by this # disconnection). 'social.pipeline.disconnect.allowed_to_disconnect', # Collects the social associations to disconnect. 'social.pipeline.disconnect.get_entries', # Revoke any access_token when possible. 'social.pipeline.disconnect.revoke_tokens', # Removes the social associations. 'social.pipeline.disconnect.disconnect', ) Partial Pipeline ---------------- It's possible to cut the pipeline process to return to the user asking for more data and resume the process later. To accomplish this decorate the function that will cut the process with the ``@partial`` decorator located at ``social/pipeline/partial.py``. The old ``social.pipeline.partial.save_status_to_session`` is now deprecated. When it's time to resume the process just redirect the user to ``/complete//`` or ``/disconnect//`` view. The pipeline will resume in the same function that cut the process. ``@partial`` and ``save_status_to_session`` stores needed data into user session under the key ``partial_pipeline``. To get the backend in order to redirect to any social view, just do:: backend = session['partial_pipeline']['backend'] Check the `example applications`_ to check a basic usage. Email validation ---------------- There's a pipeline to validate email addresses, but it relies a lot on your project. The pipeline is at ``social.pipeline.mail.mail_validation`` and it's a partial pipeline, it will return a redirect to a URL that you can use to tell the users that an email validation was sent to them. If you want to mention the email address you can get it from the session under the key ``email_validation_address``. In order to send the validation python-social-auth_ needs a function that will take care of it, this function is defined by the developer with the setting ``SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION``. It should be an import path. This function should take three arguments ``strategy``, ``backend`` and ``code``. ``code`` is a model instance used to validate the email address, it contains three fields: ``code = '...'`` Holds an ``uuid.uuid4()`` value and it's the code used to identify the validation process. ``email = '...'`` Email address trying to be validate. ``verified = True / False`` Flag marking if the email was verified or not. You should use the code in this instance to build the link for email validation which should go to ``/complete/email?verification_code=``. If you are using Django, you can do it with:: from django.core.urlresolvers import reverse url = strategy.build_absolute_uri( reverse('social:complete', args=(strategy.backend_name,)) ) + '?verification_code=' + code.code On Flask:: from flask import url_for url = url_for('social.complete', backend=strategy.backend_name, _external=True) + '?verification_code=' + code This pipeline can be used globally with any backend if this setting is defined:: SOCIAL_AUTH_FORCE_EMAIL_VALIDATION = True Or individually by defining the setting per backend basis like ``SOCIAL_AUTH_TWITTER_FORCE_EMAIL_VALIDATION = True``. Extending the Pipeline ====================== The main purpose of the pipeline (either creation or deletion pipelines) is to allow extensibility for developers. You can jump in the middle of it, do changes to the data, create other models instances, ask users for extra data, or even halt the whole process. Extending the pipeline implies: 1. Writing a function 2. Locating the function in an accessible path (accessible in the way that it can be imported) 3. Overriding the default pipeline definition with one that includes newly created function. The part of writing the function is quite simple. However please be careful when placing your function in the pipeline definition, because order does matter in this case! Ordering of functions in ``SOCIAL_AUTH_PIPELINE`` will determine the value of arguments that each function will receive. For example, adding your function after ``social.pipeline.user.create_user`` ensures that your function will get the user instance (created or already existent) instead of a ``None`` value. The pipeline functions will get quite a lot of arguments, ranging from the backend in use, different model instances, server requests and provider responses. To enumerate a few: ``strategy`` The current strategy instance. ``backend`` The current backend instance. ``uid`` User ID in the provider, this ``uid`` should identify the user in the current provider. ``response = {} or object()`` The server user-details response, it depends on the protocol in use (and sometimes the provider implementation of such protocol), but usually it's just a ``dict`` with the user profile details in such provider. Lots of information related to the user is provided here, sometimes the ``scope`` will increase the amount of information in this response on OAuth providers. ``details = {}`` Basic user details generated by the backend, used to create/update the user model details (this ``dict`` will contain values like ``username``, ``email``, ``first_name``, ``last_name`` and ``fullname``). ``user = None`` The user instance (or ``None`` if it wasn't created or retrieved from the database yet). ``social = None`` This is the associated ``UserSocialAuth`` instance for the given user (or ``None`` if it wasn't created or retrieved from the DB yet). Usually when writing your custom pipeline function, you just want to get some values from the ``response`` parameter. But you can do even more, like call other APIs endpoints to retrieve even more details about the user, store them on some other place, etc. Here's an example of a simple pipeline function that will create a ``Profile`` class instance, related to the current user. This profile will store some simple details returned by the provider (``Facebook`` in this example). The usual Facebook ``response`` looks like this:: { 'username': 'foobar', 'access_token': 'CAAD...', 'first_name': 'Foo', 'last_name': 'Bar', 'verified': True, 'name': 'Foo Bar', 'locale': 'en_US', 'gender': 'male', 'expires': '5183999', 'email': 'foo@bar.com', 'updated_time': '2014-01-14T15:58:35+0000', 'link': 'https://www.facebook.com/foobar', 'timezone': -3, 'id': '100000126636010', } Let's say we are interested in storing the user profile link, the gender and the timezone in our ``Profile`` model:: def save_profile(backend, user, response, *args, **kwargs): if backend.name == 'facebook': profile = user.get_profile() if profile is None: profile = Profile(user_id=user.id) profile.gender = response.get('gender') profile.link = response.get('link') profile.timezone = response.get('timezone') profile.save() Now all that's needed is to tell ``python-social-auth`` to use our function in the pipeline. Since the function uses user instance, we need to put it after ``social.pipeline.user.create_user``:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'path.to.save_profile', # <--- set the path to the function 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', ) So far the function we created returns ``None``, which is taken as if ``{}`` was returned. If you want the ``profile`` object to be available to the next function in the pipeline, all you need to do is return ``{'profile': profile}``. .. _python-social-auth: https://github.com/omab/python-social-auth .. _example applications: https://github.com/omab/python-social-auth/tree/master/examples python-social-auth-0.2.21/LICENSE0000644000175500017550000000300112754357263016073 0ustar debacledebacleCopyright (c) 2012-2015, Matías Aguirre All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of this project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-social-auth-0.2.21/requirements.txt0000644000175500017550000000014712754357263020362 0ustar debacledebaclepython-openid>=2.2.5 requests>=2.9.1 oauthlib>=1.0.3 requests-oauthlib>=0.6.1 six>=1.10.0 PyJWT>=1.4.0 python-social-auth-0.2.21/Makefile0000644000175500017550000000103212754357263016530 0ustar debacledebacledocs: sphinx-build docs/ docs/_build/ site: docs rsync -avkz site/ tarf:sites/psa/ build: python setup.py sdist python setup.py bdist_wheel --python-tag py2 BUILD_VERSION=3 python setup.py bdist_wheel --python-tag py3 publish: python setup.py sdist upload python setup.py bdist_wheel --python-tag py2 upload BUILD_VERSION=3 python setup.py bdist_wheel --python-tag py3 upload clean: find . -name '*.py[co]' -delete find . -name '__pycache__' -delete rm -rf python_social_auth.egg-info dist build .PHONY: site docs publish python-social-auth-0.2.21/run_tox.sh0000755000175500017550000000063412754357263017134 0ustar debacledebacle#!/bin/bash # 1. Install pyenv # 2. Install python versions # pyenv install 2.7.11 # pyenv install 3.3.6 # pyenv install 3.4.4 # pyenv install pypy-4.0.1 # 3. Switch to each version and install / update setuptools, pip, tox # pip install -U setuptools pip tox # 4. Enable versions # pyenv local 2.7.11 3.3.6 3.4.4 pypy-4.0.1 # 5. Run tox which pyenv && eval "$(pyenv init -)" tox python-social-auth-0.2.21/.travis.yml0000644000175500017550000000152012754357263017203 0ustar debacledebaclelanguage: python sudo: false env: global: - REQUIREMENTS=requirements.txt - TEST_REQUIREMENTS=social/tests/requirements.txt python: - "2.7" matrix: include: - python: "pypy" env: - TEST_REQUIREMENTS=social/tests/requirements-pypy.txt - python: "3.3" env: - REQUIREMENTS=requirements-python3.txt - TEST_REQUIREMENTS=social/tests/requirements-python3.txt - python: "3.4" env: - REQUIREMENTS=requirements-python3.txt - TEST_REQUIREMENTS=social/tests/requirements-python3.txt addons: apt: packages: - libxmlsec1-dev - swig install: - "python setup.py -q install" - "travis_retry pip install -r $REQUIREMENTS" - "travis_retry pip install -r $TEST_REQUIREMENTS" script: - "nosetests --with-coverage --cover-package=social --where=social/tests" python-social-auth-0.2.21/.landscape.yaml0000644000175500017550000000013112754357263017763 0ustar debacledebacledoc-warnings: no test-warnings: no strictness: medium max-line-length: 80 autodetect: no