PKAmG2ZFFsocial/utils.pyimport 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.Logger('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(host, redirect_to): """ Given the hostname and an untrusted URL to redirect to, this method tests it to make sure it isn't garbage/harmful and returns it, else returns None, similar as how's it done on django.contrib.auth.views. """ if redirect_to: try: # Don't redirect to a different host netloc = urlparse(redirect_to)[1] or host except (TypeError, AttributeError): pass else: if netloc == host: 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) if backend_name == backend.name: 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]) 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 PKAmG5wqsocial/actions.pyfrom 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): redirect_uri = sanitize_redirect(backend.strategy.request_host(), 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): url = sanitize_redirect(backend.strategy.request_host(), 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.request_data().get(redirect_name, '') or backend.setting('DISCONNECT_REDIRECT_URL') or backend.setting('LOGIN_REDIRECT_URL') ) return response PKVz9HUsocial/__init__.py""" python-social-auth application, allows OpenId or OAuth user registration/authentication just adding a few configurations. """ version = (0, 2, 14) extra = '' __version__ = '.'.join(map(str, version)) + extra PKAmG*WW social/p3.py# 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 PK%D_] social/store.pyimport 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 PKAmG7 G G social/exceptions.pyclass 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 __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' PK$Dsocial/apps/__init__.pyPKAmG(nc!social/apps/tornado_app/routes.pyfrom 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'), ] PKAmG   social/apps/tornado_app/utils.pyimport 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) PK$D#social/apps/tornado_app/__init__.pyPKAmGԛ!social/apps/tornado_app/models.py"""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 PKAmGz?#social/apps/tornado_app/handlers.pyfrom 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() PK$DӴ,social/apps/django_app/context_processors.pyfrom 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 } PKAmGopb b social/apps/django_app/utils.pyimport 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) PKAmG:>$social/apps/django_app/middleware.py# -*- 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') PK.HXXsocial/apps/django_app/urls.py"""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'), ] PKAmGjyy"social/apps/django_app/__init__.py""" 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) PKAmGƥpsocial/apps/django_app/views.pyfrom 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) PK$Dep p social/apps/django_app/tests.pyfrom 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_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 * PKAmG Y&#social/apps/django_app/me/config.pyfrom 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) PKAmGrJ%social/apps/django_app/me/__init__.py""" 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' PKAmG уLL#social/apps/django_app/me/models.py""" 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 PK$Dr++"social/apps/django_app/me/tests.pyfrom social.apps.django_app.tests import * PKAmGt  (social/apps/django_app/default/config.pyfrom django.apps import AppConfig class PythonSocialAuthConfig(AppConfig): name = 'social.apps.django_app.default' 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) PK$D< (social/apps/django_app/default/fields.pyimport json import six 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 class JSONField(six.with_metaclass(models.SubfieldBase, 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 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 PKAmGl""*social/apps/django_app/default/__init__.py""" 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' PKAmGOyH(social/apps/django_app/default/models.py"""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) 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' class Code(models.Model, DjangoCodeMixin): email = models.EmailField(max_length=254) 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 PKAmGzz-m*social/apps/django_app/default/managers.pyfrom 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 PKAmG6aϷ'social/apps/django_app/default/admin.py"""Admin settings""" 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 = _User._meta.get_all_field_names() search_fields = [name for name in fieldnames if name and name in all_names] return ['user__' + name for name in search_fields] 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) PK$Dr++'social/apps/django_app/default/tests.pyfrom social.apps.django_app.tests import * PKAmGdS1zz;social/apps/django_app/default/south_migrations/__init__.pyfrom 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 PKAmGF**?social/apps/django_app/default/south_migrations/0001_initial.py# -*- 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'] PKAmG`Bsocial/apps/django_app/default/migrations/0002_add_related_name.py# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings class Migration(migrations.Migration): dependencies = [ ('default', '0001_initial'), ] operations = [ migrations.AlterField( model_name='usersocialauth', name='user', field=models.ForeignKey(related_name='social_auth', to=settings.AUTH_USER_MODEL) ), ] PKAmG>5ۏHsocial/apps/django_app/default/migrations/0003_alter_email_max_length.py# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations class Migration(migrations.Migration): dependencies = [ ('default', '0002_add_related_name'), ] operations = [ migrations.AlterField( model_name='code', name='email', field=models.EmailField(max_length=254), ), ] PKAmG5social/apps/django_app/default/migrations/__init__.pyPKAmG#9social/apps/django_app/default/migrations/0001_initial.py# -*- 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' class Migration(migrations.Migration): 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=255)), ('handle', models.CharField(max_length=255)), ('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=255)), ('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=255)), ('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=set([('provider', 'uid')]), ), migrations.AlterUniqueTogether( name='code', unique_together=set([('email', 'code')]), ), migrations.AlterUniqueTogether( name='nonce', unique_together=set([('server_url', 'timestamp', 'salt')]), ), ] PKAmGQƕ!social/apps/cherrypy_app/utils.pyimport 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) PK$D$social/apps/cherrypy_app/__init__.pyPKAmGu]"social/apps/cherrypy_app/models.py"""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 PKAmGtzF!social/apps/cherrypy_app/views.pyimport 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) PKAmGwp9NNsocial/apps/flask_app/routes.pyfrom flask import g, Blueprint, request from flask.ext.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) PKAmG9ّ[[social/apps/flask_app/utils.pyimport 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) PKv DGNN)social/apps/flask_app/template_filters.pyfrom 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 '' } PK$DO+Q!social/apps/flask_app/__init__.pyfrom social.strategies.utils import set_current_strategy_getter from social.apps.flask_app.utils import load_strategy set_current_strategy_getter(load_strategy) PKAmG$social/apps/flask_app/me/__init__.pyPKAmGPhh"social/apps/flask_app/me/models.py"""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 PKAmG)social/apps/flask_app/default/__init__.pyPKAmG`A A 'social/apps/flask_app/default/models.py"""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')) PKAmGziisocial/apps/webpy_app/app.pyimport 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()) PKAmGGUu^^social/apps/webpy_app/utils.pyimport 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) PK%D&!social/apps/webpy_app/__init__.pyfrom social.strategies.utils import set_current_strategy_getter from social.apps.webpy_app.utils import load_strategy set_current_strategy_getter(load_strategy) PKAmGLy]social/apps/webpy_app/models.py"""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 PKAmG:" social/apps/pyramid_app/utils.pyimport 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) PK}sFz#social/apps/pyramid_app/__init__.pyfrom 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) PKAmG2GG!social/apps/pyramid_app/models.py"""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): @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 PyramidStorage.user = UserSocialAuth PyramidStorage.nonce = Nonce PyramidStorage.association = Association PyramidStorage.code = Code PKAmGfCC social/apps/pyramid_app/views.pyfrom 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') @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') PKAmGN((social/backends/coursera.py""" 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 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)} PKAmGۯsocial/backends/khanacademy.py""" 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) PKRGxuW social/backends/odnoklassniki.py""" 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 = 'http://www.odnoklassniki.ru/oauth/authorize' ACCESS_TOKEN_URL = 'http://api.odnoklassniki.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, 'http://api.odnoklassniki.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 http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=12878032, search for "little bit different way" """ suffix = md5( '{0:s}{1:s}'.format(data['access_token'], client_secret).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: http://dev.odnoklassniki.ru/wiki/display/ok/ Authentication+and+Authorization If API method requires session context, request is signed with session secret key. Otherwise it is signed with application secret key """ param_list = sorted(['{0:s}={1:s}'.format(key, value) for key, value in data.items()]) return md5( (''.join(param_list) + client_secret_or_session_secret).encode('utf-8') ).hexdigest() def odnoklassniki_api(backend, data, api_url, public_key, client_secret, request_type='oauth'): """Calls Odnoklassniki REST API method http://dev.odnoklassniki.ru/wiki/display/ok/Odnoklassniki+Rest+API""" data.update({ 'application_key': public_key, 'format': 'JSON' }) if request_type == 'oauth': data['sig'] = odnoklassniki_oauth_sig(data, client_secret) elif request_type == 'iframe_session': data['sig'] = odnoklassniki_iframe_sig(data, data['session_secret_key']) elif request_type == 'iframe_nosession': data['sig'] = odnoklassniki_iframe_sig(data, client_secret) else: msg = 'Unknown request type {0}. How should it be signed?' raise AuthFailed(backend, msg.format(request_type)) return backend.get_json(api_url + 'fb.do', params=data) PK%DSsocial/backends/legacy.pyfrom 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 } PKAmGp;social/backends/disqus.py""" 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} ) PKAmGԗsocial/backends/azuread.py""" 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.windows.net/common/oauth2/authorize' ACCESS_TOKEN_URL = 'https://login.windows.net/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 { '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 PKAmG[ƶvsocial/backends/spotify.py""" 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): name = 'spotify' SCOPE_SEPARATOR = ' ' ID_KEY = 'id' AUTHORIZATION_URL = 'https://accounts.spotify.com/authorize' ACCESS_TOKEN_URL = 'https://accounts.spotify.com/api/token' ACCESS_TOKEN_METHOD = 'POST' 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)} ) PKAmGt*social/backends/fitbit.py""" Fitbit OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/fitbit.html """ from social.backends.oauth import BaseOAuth1 class FitbitOAuth(BaseOAuth1): """Fitbit OAuth 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'] PKAmG@i#social/backends/meetup.py""" 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}) PKAmGsocial/backends/goclioeu.pyfrom 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} ) PK%Dֻ/social/backends/appsfuel.py""" 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' PK%D6bsocial/backends/coinbase.py""" 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}) PK%DP||social/backends/taobao.pyfrom 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')} PKAmGsocial/backends/mapmyfitness.py""" 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) PKAmGasocial/backends/eveonline.py""" 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)} ) PKAmG)n n social/backends/slack.py""" 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 PKAmGIKsOOsocial/backends/docker.py""" 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} ) PK%Dsocial/backends/suse.py""" 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'] PKAmG social/backends/mineid.pyfrom 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'), } PK%Dsocial/backends/thisismyjam.py""" 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)) PKAmG9social/backends/vend.py""" 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 {} PKG+social/backends/qq.py""" 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) PK.wGm 00social/backends/saml.py""" 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 # 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""" idp_name = self.strategy.request_data()['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 PKAmGZ8vvsocial/backends/changetip.pyfrom 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 }) PK%DZ/ social/backends/rdio.py""" 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'] PKAmGsocial/backends/beats.py""" 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)} ) PK%Dsocial/backends/mixcloud.py""" 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'}) PKAmGS/uss social/backends/professionali.py# -*- 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) PKAmG55social/backends/persona.py""" 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) PKAmGKRR social/backends/nationbuilder.py""" 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'] PK%DϗGGsocial/backends/readability.py""" 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)) PKz4F@qhhsocial/backends/twilio.py""" Amazon 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) PKAmGl social/backends/launchpad.py""" Launchpad OpenId backend """ from social.backends.open_id import OpenIdAuth class LaunchpadOpenId(OpenIdAuth): name = 'launchpad' URL = 'https://login.launchpad.net' USERNAME_KEY = 'nickname' PKAmG}=F F social/backends/utils.pyfrom 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 PKAmGHAsocial/backends/zotero.py""" 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', '') } PK.wG_U2@@social/backends/oauth.pyimport 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 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 {}) 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() PK%D9I44social/backends/douban.py""" 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)} ) PK%DMsocial/backends/skyrock.py""" 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)) PK%DͯVsocial/backends/box.py""" 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) PK%D1Psocial/backends/aol.py""" 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' PKAmG5jjsocial/backends/github.py""" 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') ) ) PKAmGCsocial/backends/withings.pyfrom 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': ''} PKAmGҶsocial/backends/goclio.pyfrom 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') PKAmG)qj4 4 social/backends/shopify.py""" 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) PKAmG8Nsocial/backends/live.py""" 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 }) PKAmG8=, social/backends/jawbone.py""" 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) PKAmG+::social/backends/strava.py""" 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 PKRG诛social/backends/vk.py# -*- 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 PKAmG4social/backends/dribbble.py""" 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) }) PKWEuQ Q social/backends/dropbox.py""" 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)} ) PK%DBsocial/backends/podio.py""" 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}) PKAmGZ__social/backends/bitbucket.py""" 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)) PK%Dsocial/backends/__init__.pyPKAmGSMMsocial/backends/pushbullet.pyimport 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'] PK%Dh0asocial/backends/twitch.py""" 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} ) PKAmG/*IIsocial/backends/wunderlist.pyfrom 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) PKRG|xT social/backends/evernote.py""" 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) 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' PKAmGgLppsocial/backends/yahoo.py""" 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) } PKAmGgOiisocial/backends/justgiving.pyfrom 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) PK%DJllsocial/backends/soundcloud.py""" 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) PK%Dz>wwsocial/backends/dailymotion.py""" 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}) PKAmGx~Ksocial/backends/pocket.py""" 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('POCKET_CONSUMER_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('POCKET_CONSUMER_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) PKAmGRsocial/backends/mendeley.py""" 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)} ) PK%D {social/backends/fedora.py""" 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' PKAmG76social/backends/moves.py""" 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}) PKAmGCgsocial/backends/trello.py""" 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') } PKAmGSsocial/backends/flickr.py""" 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 PKAmGsocial/backends/orbi.py""" 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 }) PK%D``social/backends/foursquare.py""" 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}) PKAmGnx--social/backends/behance.py""" 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) PKAmG22$social/backends/github_enterprise.py""" 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'] PK%DbOsocial/backends/pixelpin.pyfrom 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} ) PKAmGsocial/backends/qiita.py""" 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) }) PK%D   social/backends/vimeo.pyfrom 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, ) PKAmG :$$social/backends/battlenet.pyfrom 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/battletag', params={'access_token': access_token} ) PKAmGI!]))social/backends/angel.py""" 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 }) PK%DͯZe social/backends/yandex.py""" 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, response, *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, response, *args, **kwargs): return self.get_json('https://login.yandex.ru/info', params={'oauth_token': access_token, 'format': 'json'}) PKAmG2 social/backends/weixin.py# -*- coding: utf-8 -*- # author:duoduo3369@gmail.com https://github.com/duoduo369 """ Weixin OAuth2 backend """ 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) 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) PKAmG^Rsocial/backends/weibo.py# coding:utf8 # 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 PK%Dr3social/backends/tripit.py""" 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 } PKAmG(social/backends/linkedin.py""" 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 ) PKAmGlsocial/backends/digitalocean.pyfrom 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 PK%D>J9 9 social/backends/loginradius.py""" 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)) PK%D 1Ysocial/backends/livejournal.py""" 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']) PKAmG D``social/backends/lastfm.pyimport 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 } PKRGP&&social/backends/base.pyfrom 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') PKAmG*[[social/backends/echosign.pyfrom 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}) PK%DxZX social/backends/runkeeper.py""" 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}) PK%Dasocial/backends/ubuntu.py""" 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'] PK%D-+SSsocial/backends/belgiumeid.py""" 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' PKAmG08777social/backends/open_id.pyimport 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.exceptions import AuthException, AuthFailed, AuthCanceled, \ AuthUnknownError, AuthMissingParameter, \ AuthTokenError from social.backends.base import BaseAuth from social.backends.oauth import BaseOAuth2 # 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 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() decryption_key = self.setting('ID_TOKEN_DECRYPTION_KEY') try: # Decode the JWT and raise an error if the secret is invalid or # the response has expired. id_token = jwt_decode(id_token, decryption_key, audience=client_id, issuer=self.ID_TOKEN_ISSUER, algorithms=['HS256']) except InvalidTokenError as err: raise AuthTokenError(self, err) # Verify the token was issued in the last 10 minutes utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple()) if id_token['iat'] < (utc_timestamp - 600): 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 PKAmGq social/backends/stackoverflow.py""" 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) PKlGW8social/backends/instagram.py""" 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() PKAmGEknnsocial/backends/uber.py""" 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() 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""" client_id, client_secret = self.get_key_and_secret() 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 ) } ) PK%DMMsocial/backends/stocktwits.py""" 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} ) PKAmG)͌  social/backends/amazon.py""" 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 = 'http://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 PK%DaTy1social/backends/steam.py""" 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 PK%DhQfTTsocial/backends/tumblr.py""" 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)) PKAmGsT..social/backends/salesforce.pyfrom urllib import urlencode from social.backends.oauth import BaseOAuth2 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' PK%D social/backends/openstreetmap.py""" 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 } PK%D7social/backends/email.py""" 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'] PKAmGXp social/backends/nk.pyfrom 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) PKAmG@ֳsocial/backends/exacttarget.py""" 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 PK%DA7Jsocial/backends/mailru.py""" 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] PK%D7`social/backends/yammer.py""" 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' PKAmG5Vsocial/backends/reddit.py""" 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': 'Basic {0}'.format(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 PK%Dpx@mmsocial/backends/stripe.py""" 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'} PKAmGR>social/backends/clef.py""" 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}) PKaGJsocial/backends/naver.pyfrom 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 PKAmGJ-uoosocial/backends/google.py""" 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'} ) PK%Dg]hsocial/backends/xing.py""" 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'] } PK4Fľ  social/backends/gae.py""" 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) PK%DxOsocial/backends/username.py""" 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'] PKAmGVsocial/backends/kakao.py""" 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], } PKAmGHrHsocial/backends/twitter.py""" 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': '', # not supplied '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', auth=self.oauth_auth(access_token) ) PKAmGQ Q social/backends/facebook.py""" 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.3/dialog/oauth' ACCESS_TOKEN_URL = 'https://graph.facebook.com/v2.3/oauth/access_token' REVOKE_TOKEN_URL = 'https://graph.facebook.com/v2.3/{uid}/permissions' REVOKE_TOKEN_METHOD = 'DELETE' USER_DATA_URL = 'https://graph.facebook.com/v2.3/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 PKDF:!social/storage/mongoengine_orm.pyimport 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 PK%Dsocial/storage/__init__.pyPKAmG֋social/storage/django_orm.py"""Django ORM models for Social Auth""" import base64 import six 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') return cls.user_model().objects.create_user(*args, **kwargs) @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) return cls.objects.create(user=user, uid=uid, provider=provider) 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 PKAmGxW social/storage/sqlalchemy_orm.py"""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): @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) 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 PKAmG9}  social/storage/base.py"""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: 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') PKAmGT+ %social/strategies/pyramid_strategy.pyfrom 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""" response = getattr(self.request, 'response', None) if response is None: response = Response(body=content) else: response = self.request.response response.body = content return response 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) PKAmG#ݤnn#social/strategies/flask_strategy.pyfrom 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) PKAmG&||social/strategies/utils.pyfrom 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() PKAmG0ۢ&social/strategies/cherrypy_strategy.pyimport 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) PK%Dsocial/strategies/__init__.pyPKAmGyޥ %social/strategies/tornado_strategy.pyimport 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) ) PKAmGxxsocial/strategies/base.pyimport 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 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') PKAmG=4$social/strategies/django_strategy.pyfrom 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() PKAmG](#social/strategies/webpy_strategy.pyimport 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 PKAmGLsocial/tests/pipeline.pyfrom 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')) PKAmG99social/tests/strategy.pyfrom 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')) PKAmGH!!social/tests/test_storage.pyimport 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 PKAmG2cHsocial/tests/test_utils.pyimport 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/') 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_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) 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) def _backend(self, session_kwargs=None): strategy = Mock() strategy.request = None strategy.session_get.return_value = object() strategy.partial_from_session.return_value = \ (0, 'mock-backend', [], session_kwargs or {}) backend = Mock() backend.name = 'mock-backend' backend.strategy = strategy return backend PKAmGd%social/tests/requirements-python3.txthttpretty==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 PKAmG#  social/tests/__init__.pyimport sys import warnings # Ignore deprecation warnings on Python2.6. Maybe it's time to ditch this # oldie? if sys.version_info[0] == 2 and sys.version_info[1] == 6 or \ hasattr(sys, 'pypy_version_info'): warnings.filterwarnings('ignore', category=Warning) PKAmGŦ(hhsocial/tests/models.pyimport 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): self.id = User.next_id() self.username = username self.email = email self.password = None self.slug = None self.social = [] self.extra_data = {} 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): return User(username=username, email=email) @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 PKAmGAiisocial/tests/test_pipeline.pyimport 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) PKAmGVsocial/tests/requirements.txthttpretty==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 PKAmG8{{"social/tests/requirements-pypy.txthttpretty==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 PKAmGf', '', '', '', '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() PKAmG9yy)social/tests/backends/test_livejournal.pyimport 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() PKAmGYaa&social/tests/backends/test_dribbble.pyimport 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() PK%DY>$social/tests/backends/test_twitch.pyimport 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() PKAmG0$social/tests/backends/test_reddit.pyimport 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') PK%D3vv$social/tests/backends/test_disqus.pyimport 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() PKAmG38!social/tests/backends/test_box.pyimport 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') PK%DfT  (social/tests/backends/test_foursquare.pyimport 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() PK%DtO%social/tests/backends/test_behance.pyimport 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() PKAmG__#social/tests/backends/test_kakao.pyimport 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() PKAmGCl#social/tests/backends/test_utils.pyimport 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') PK%D%%%$social/tests/backends/test_amazon.pyimport 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() PKAmGN[`)!)!+social/tests/backends/test_nationbuilder.pyimport 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() PKAmGz&#_#social/tests/backends/test_podio.pyimport 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() PK%D nXzz$social/tests/backends/test_tripit.pyimport 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' \ '' \ '
' \ '
' PK%DQQ#social/tests/backends/test_angel.pyimport 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() PKAmG(t`(($social/tests/backends/test_google.pyimport 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" PKAmG]}ܺsocial/tests/backends/oauth.pyimport 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 PKAmGq\\#social/tests/backends/test_dummy.pyimport 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) PK%DF3ZZ$social/tests/backends/test_strava.pyimport 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() PK%Db&social/tests/backends/test_coinbase.pyimport 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() PKAmGU1Ƒcc&social/tests/backends/test_coursera.pyimport 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() PK%Dbn"")social/tests/backends/test_readability.pyimport 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() PK%DcNN)social/tests/backends/test_dailymotion.pyimport 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() PKDφO'social/tests/backends/test_instagram.pyimport 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() PK%D$social/tests/backends/test_tumblr.pyimport 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() PK%D.??%social/tests/backends/test_skyrock.pyimport 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() PK%D:F#social/tests/backends/test_email.pyfrom 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() PKAmGI룂"social/tests/backends/test_clef.pyimport 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() PKAmGHOv"social/tests/backends/test_saml.pyimport 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.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): # pretend we've started with a URL like /login/saml/?idp=testshib: self.strategy.set_request_data({'idp': 'testshib'}, self.backend) 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)""" self.do_login() 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) PKEUGG%social/tests/backends/test_twitter.pyimport 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() PKAmG8ۉ++*social/tests/backends/test_mapmyfitness.pyimport 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() PK%D!social/tests/backends/__init__.pyPK%D}%social/tests/backends/test_dropbox.pyimport 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() PKAmG>>#social/tests/backends/test_qiita.pyimport 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() PKAmGnJ&social/tests/backends/test_evernote.pyfrom 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): self.do_login() def test_partial_pipeline(self): with self.assertRaises(AuthCanceled): self.do_partial_pipeline() 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() PK%DR0$social/tests/backends/test_yammer.pyimport 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() PK%D' /__(social/tests/backends/test_soundcloud.pyimport 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() PK%D3 +social/tests/backends/test_stackoverflow.pyimport 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() PKAmGU "social/tests/backends/test_uber.pyimport 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() PKAmGaLUU)social/tests/backends/test_khanacademy.pyimport 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() PKAmG:(social/tests/backends/test_wunderlist.pyimport 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() PKAmGÅrQQ'social/tests/backends/test_bitbucket.pyimport 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() PKAmG^NN$social/tests/backends/test_mineid.pyimport 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() PKAmG1gsocial/tests/backends/base.pyimport 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 PKAmGFϭl l %social/tests/backends/test_azuread.py""" 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') PK%D$y~$social/tests/backends/test_yandex.pyimport 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() PKAmGR"social/tests/backends/test_orbi.pyimport 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() PKAmGg[  social/tests/backends/open_id.py# -*- coding: utf-8 -*- from calendar import timegm import sys import json import datetime import requests import jwt 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 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 }) 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, **access_token_kwargs): self.access_token_body = self.prepare_access_token_body( **access_token_kwargs ) with self.assertRaisesRegexp(AuthTokenError, expected_message): 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): expiration_datetime = datetime.datetime.utcnow() - \ datetime.timedelta(hours=1) self.authtoken_raised('Token error: Incorrect id_token: iat', issue_datetime=expiration_datetime) def test_invalid_nonce(self): self.authtoken_raised( 'Token error: Incorrect id_token: nonce', nonce='something-wrong' ) PK%D^g5^^)social/tests/backends/test_thisismyjam.pyimport 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() PKAmG'7100$social/tests/backends/test_broken.pyimport 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) PKAmG f|&social/tests/backends/test_facebook.pyimport json from social.exceptions import AuthUnknownError from social.tests.backends.oauth import OAuth2Test class FacebookOAuth2Test(OAuth2Test): backend_path = 'social.backends.facebook.FacebookOAuth2' user_data_url = 'https://graph.facebook.com/v2.3/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() PKAmG**$social/tests/backends/test_github.pyimport 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() PKaG)av.#social/tests/backends/test_naver.pyimport 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() PK%DLL$social/tests/backends/test_flickr.pyfrom 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() PK%Djj$social/tests/backends/test_stripe.pyimport 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() PKAmG#*social/tests/backends/test_digitalocean.pyimport 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() PK%D|&social/tests/backends/test_username.pyfrom 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() PK%Dm$social/tests/backends/test_fitbit.pyimport json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class FitbitOAuth1Test(OAuth1Test): backend_path = 'social.backends.fitbit.FitbitOAuth' 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() PKAmGlWDWD,social/tests/backends/data/saml_response.txthttp://myapp.com/?RelayState=testshib&SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL215YXBwLmNvbSIgSUQ9Il8yNTk2NTFlOTY3ZGIwOGZjYTQ4MjdkODI3YWY1M2RkMCIgSW5SZXNwb25zZVRvPSJURVNUX0lEIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMDUtMDlUMDM6NTc6NDMuNzkyWiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVudGl0eSI%2BaHR0cHM6Ly9pZHAudGVzdHNoaWIub3JnL2lkcC9zaGliYm9sZXRoPC9zYW1sMjpJc3N1ZXI%2BPHNhbWwycDpTdGF0dXM%2BPHNhbWwycDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWwycDpTdGF0dXM%2BPHNhbWwyOkVuY3J5cHRlZEFzc2VydGlvbiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI%2BPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIElkPSJfMGM0NzYzNzIyOWFkNmEzMTY1OGU0MDc2ZDNlYzBmNmQiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNhZXMxMjgtY2JjIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiLz48ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI%2BPHhlbmM6RW5jcnlwdGVkS2V5IElkPSJfYjZmNmU2YWZjMzYyNGI3NmM1N2JmOWZhODA5YzAzNmMiIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiLz48L3hlbmM6RW5jcnlwdGlvbk1ldGhvZD48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE%2BPGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDc0RDQ0FobWdBd0lCQWdJSkFPN0J3ZGpEWmNVV01BMEdDU3FHU0liM0RRRUJCUVVBTUVVeEN6QUpCZ05WQkFZVEFrTkJNUmt3CkZ3WURWUVFJRXhCQ2NtbDBhWE5vSUVOdmJIVnRZbWxoTVJzd0dRWURWUVFLRXhKd2VYUm9iMjR0YzI5amFXRnNMV0YxZEdnd0hoY04KTVRVd05UQTRNRGMxT0RRMldoY05NalV3TlRBM01EYzFPRFEyV2pCRk1Rc3dDUVlEVlFRR0V3SkRRVEVaTUJjR0ExVUVDQk1RUW5KcApkR2x6YUNCRGIyeDFiV0pwWVRFYk1Ca0dBMVVFQ2hNU2NIbDBhRzl1TFhOdlkybGhiQzFoZFhSb01JR2ZNQTBHQ1NxR1NJYjNEUUVCCkFRVUFBNEdOQURDQmlRS0JnUUNxM2cxQ2wrM3VSNXZDbk40SGJnalRnK20zbkhodGVFTXliKyt5Y1pZcmUyYnhVZnNzaEVSNngzM2wKMjN0SGNrUll3bTdNZEJicnAzTHJWb2lPQ2RQYmxUbWwxSWhFUFRDd0tNaEJLdnZXcVR2Z2ZjU1NuUnpBV2tMbFFZU3VzYXl5Wks0bgo5cWNZa1Y1TUZuaTFyYmp4K01yNWFPRW1iNXUzM2FtTUtMd1NUd0lEQVFBQm80R25NSUdrTUIwR0ExVWREZ1FXQkJSUmlCUjZ6UzY2CmZLVm9rcDB5SkhiZ3YzUlltakIxQmdOVkhTTUViakJzZ0JSUmlCUjZ6UzY2ZktWb2twMHlKSGJndjNSWW1xRkpwRWN3UlRFTE1Ba0cKQTFVRUJoTUNRMEV4R1RBWEJnTlZCQWdURUVKeWFYUnBjMmdnUTI5c2RXMWlhV0V4R3pBWkJnTlZCQW9URW5CNWRHaHZiaTF6YjJOcApZV3d0WVhWMGFJSUpBTzdCd2RqRFpjVVdNQXdHQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ1lFQUp3c01VM1lTCmF5YlZqdUo4VVMwZlVobFBPbE00MFFGQ0dMNHZCM1RFYmIyNE1xOEhyalV3clUwSkZQR2xzOWEyT1l6TjJCM2UzNU5vck11eHMrZ3IKR3RyMnlQNkx2dVgrblY2QTkzd2I0b29HSG9HZkM3VkxseXhTU25zOTM3U1M1UjFwelE0Z1d6Wm1hMktHV0tJQ1dwaDV6UTBBUlZoTAo2Mzk2N21HTG1vST08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6Q2lwaGVyVmFsdWU%2BTElQdkVNVUVGeXhrVHowQ2N4QVA5TjV4Y3NYT2V4aVV4cXBvR2VIeVFMV0R5RVBBUDVnZ1daL3NLZ1ViL2xWSk92bCtuQXhSdVhXUlc5dGxSWWx3R2orRVhIOWhIbmdEY1BWMDNqSUJMQnFJbElBL1RmMGw4cVliOHFKRy9ZM0RTS2RQNkwvUURtYXBtTXpFM29YOEJxMW5Ea3YrUWh4cmQwMGVGK2ZMYVQ0PTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6Q2lwaGVyVmFsdWU%2BRVpUWDhHTkM0My9yWStTUVlBMXRudHlUTTVVNkN2dUNCaktsVEVlekZPRjBZZHhCWUdFQVVjYU8xNVNKOXBMemJ1L1h0WGxzTkVMZTdKdEx4RUpwYUxubWFENnIranNWczdLaTBLNHRTMGNBUERDWHV2R1FoMmFOVjVQOGJ3N1JWUGhLOGQwYlJ1RklGR09FOHMwTTZYOUpxWDN4S0MvL1lSbVVoeDlybnU3ZWlwMGh5ZitPaUZiVGR2SDY2NTB2LzQ3aVdKcDNZeFlUV0QyMHBNbVRJMUpwWUEwYjByWVFQRkR0RU93d0JxYktxanRJc3ZYVFJzeXJhQkxvbnFOeHN5dHpEWHEra0JsMXp3WGUvSE5QcUVQblczdnNxaFhZcDVGM3dkWThkKzNCOTRMZlpOdUd4a0p3VDNzdVR0OGY5VHRBSlI4VytBUmtzT2M4eDBVaVNsVG5BNHFHOTBLMTR5dkVoVHcvd2drZjFXV01RT3dpZDNpakFYbUV4MU5MbVZvYUxYb3p4VExkTjN6YnJ6VEJIRXc3R2J3ZEdrdU5pMlhZOW16YUgwaWtGRm51VUxjMHUwc0pycEdGdzlaK0VlUk44RzNVUVZ5MjhtS2g3ZFBwWU5KbzhyajIxZFFaK2JaeUtTUHZablU3REkyakdJRE5US1g2ZkVyVWFINGlOTzN4cUU2Vk90L2d4T3BMNE5VNUhLV0Q0bG93VzcwdUJjVEVQRmhwaThpYUovdTB6YzUvTEhvdVBjMzByc1RLZFc5cmJLL2NWaHNQUHErZzA5WHZpZ0QweTJvN2tOc1pVL25tRXFiSzBKOTBrazhCR3I5cXRSczY4bUJnSURtUHVwUkhwWjM4eXNnU2VZN3V0VlVaSG5tQ0dzTzZ2NDJ6OTVOK05Pb3RCTEVZbFd1ZEdzYnowQWc4VkRDSlY5ak95QW95MDZyL1AyUHBsOFhjdmJza2d2T1BMMWdDNnVYbVJJS1lmOEw4UDJCNXVjN0haK0dtUHNOWXRLS2VKRDFFUHovdCt2NlBIbXNVb3dsSDhSd3FMRHdtMUF4dlNLQTR3UXBlQ0dQd3A5YXRYS0lWMS84NUZzRWMzajVzNjd6VlRybThrVEpydXV2MDZEdFVRZDNMOFdwTkV4cWhQait6RUp6U3RxSG04ckhNMVhNQUVxdVozc0xycTVqLzFSNlpqS0dOdFJCbjhwOE5ERGtrWm0vWTV5TXlJNXJJS3U5bnA3bXdaaEVpeWVHeHdxblV3VVMvUzVDRjNnMHVidnd4eVVnalVvd1ZvTkNqYktBbkdtT2VCSW5abkh0eGdIVUhVOUVlTFdyd2pRc3JtUmpJV0R2RkZQa3l6SzJDL20yaitubmNxc2E1OGRLVXZxcGR1VTRJYnNPQng3UGpXdXRBNmY5bXd6YWxyRU1NK0lGR3VPdk9HMC93eUdzQjZLREV6bldjUC83NkQ4angzaHZFSlAzN3REbFgreGM4Qno5TXdKdkd6VG4xbTdCb2xoR0lzSXlCTys1ZXpXa3RDWVVIUURGVE9wbXA0MDlOWHp6ZUNTUGY1U2NDWG5YYjRPd01ULy9VM1JFUnRRbGMrNmU2WG1JRjhoRkJVc0taUUJsS2ppSDkwZHlzYWlsNmN2V3UyQW55Q3QxbWxXcHFLc0MzU2RTRVZDTG1qRjlUQUFUMEtFSGdZQjg3RjZtZUpTTysvOXkyZkRuYVVvUUlUVzdubnVuSCtkT3dWSGZMU0wyL2N5YTltNlQzR29TSVNMbGJPMVRzalhKclVkZW55OTcvM2tkNmhFQlphdGY1U3NETFQ3SjNsQUVJNDROeXJ0NkIxQWdod2JNdkpqd1JNTXRNdUJLc3ltUytKVzc4UFNEWXQ4MG9waDJQTTc1N0tBNCtUMTAvYnZaQkE5Vk1OdVpqNVV3NXRWMnFIS3dwS0t6ZVVETUFiQlBRaGpYcXlQZzFKa09rd2RQMUpnOHRITjJTelBZQTlmT1htV0pBZGJDS2tMb0F4ZTV6cDZBUzYzS3FXMmFmSUt6SHJ3RTJmS1VtamppeURvMnNuMkJHbWtBaTRzbnpiVzc2SUQvSVgwd044aDBaQ2VRc29vKzdtb1RCMEJxSnBkS1MycXlsUktoc3BSTC9henVQdmxaK1pwckJxdXpJdEZkNFVLMkpzQkp6VXcwZkpxcTV1bk9PZENzVWM3SUU3QTNmZ1NmZ3NBd1R3WFZJMEVoME5ySWZpMkFKV1Z2VFpEMys2eFZ3dS96WWhuVjc0VXkvMFE4Mi8yQWtpSGpFRjNJVGNLWHdTNTB6bWtLakxjZDJqa2h5TUFYMWRoQ0wwZElFMUJoN0RNamVvNC9YbjBqSlpPL3Rrbi9xZmYzc3RNb1BYVG9KTnBIU1RjR2ZheGtaMzJYNCt3Q0xPc0VBRWxlMVZSY0kwUkZyOFhHTSsxWU9BTjBodFdGcFMxaG9kSi9OczJqL1FnUVNEemNpQ1FZeUFDd3lFRWZDZjZybnR0VmJyTlJQZWlmSHhBM3B2UnZ5ZGRhNDE5cXl0ZXI0akJ3cmw3ZUpuVnJ2VEprR2VhU2FRbDdXWk5SQXBscXRnNnZPYmpiMHZDRWlFaFhKbmNzQUhxcXp5QTRGeWFUVGQ2R0FySU9adUNxRWVoWk51T01lOVlrMVpya0VkR3pIalJESWk3Q1BKQk12NEZ4ZHI3bnJvN0I1WEhKb0ZMNE1DSUtOWWU2aWZiTUtYOU5uN1FWdnphUmY2UXlaSW1BWENQZndvU1BkN2x6NXl3UDJLSUIyaGhFMWt5eVZ5YVc5T0praWpUY3dvUnZrSXhIU0RqMXFqeGxueXh0QzhVZ1pNWmlwcGgzQXJpcjRiekIzUDhIbGIzejZ0OW51KzZMemNiN2ZObVo0UHluaU50Vk9OQ0lHbEh4dTBSY3hQK3cwUXNsM1BtTzJLaHBpc2RIanhvSUJ1YVY1NXdoTlFFNmdNNFBrT0xINDc4Rzg4bUxkd2s2RFpkWVl4L2d6RWE3b3ZIL0pReFp2TzRLdFVTNmZjZHJxV2thTFg1cEhkNkdneFBGZ2NFc2Nad1ZqM2hCS0xFQmE5L0dodERINEhzRnNRbmpPZnNDQkNzN0tjRitmTi9oSUdUeHFqTVlKVHJRYmNtdWF5dk9xR3RQMDFPcXltR24rVm5FSVkzKytQcm95SFN3K0Q0b0JIVG1maFNXRmJLZCtuTlVFS3BhRVIxNkdCU256WktQRVRVSmdRWEw5QWJRQ3RXVjFHb0UzRWNnMDZYaVd2aHFHakpGNldtdEU4dHY4Q25rZmxMNm91TDRvNldpbmx2WnNEdkZrS0R6TDkwUTNsWC9NanBtRTFpWU9uYzdISXdEVGwraFRRcHdsYXJiTDVUNGNkZTg1akNwYU0xU3p1TStiQU5zMHlXVDA0ZXJUVFc2cnhlbXFDTHAra202TVVMTlZOcE1CazBiQjJpRU82UlRtc3VpRlhDUU1xdU5xZjdkWXUwTFFCZzQ0MkJzU1pBV1ZrWEVZblduOURLdTRSby8veEFsb2h5VHozWlZmSkhuWVBSdDloSUErRHVUL3c4T2ZzTURIWnlCelUvL0JEa1NiNkxjMHdraVA3QlhIdjBoNVdud2dNWUxlZDBPalR5UWI2aGxpVnQ5b0FjaDRFVy9EZUlBdkpaQ1BYVm1pUFFYTGVsOVJIRko2bXFiYVo0TCtaZG1ONmQwcFZNZ1FveXhmQTR3dEwwYVpiNnFZYkhibjJMd2VBQVZwL3M2TzVlMVExdnZpZDRTWHo0a2l3RW1LSStIeXZEQ1pnekpQQVN5Z1gvWDJFWEZ0NGV3SjVmUFQyVXZmWnhQWlpqMFZGSFpyUFQwWVd2VE16bjUva3hoT09oM2drVGdDSmNwNWVsZnp4cEFPNFl1a0NoNHJXdVNndDRqVUJyaWNYbFdWdWo5U3JSZVhUalNHTktLK202NWovUDllNHRHT0RkMk9BbjNKTVQ3Q3FuaDhreTZpZjVjbmpVMmU3UDhTZnBONGwxWEFiZEZEcGk5bVJYamEyTzR1RWFHNGNvNW4xcWNDT3ZNMWYyblFBY1ZGNUFoSXhueS96TWhmU2l2RXdOQ0Zyd2tBWDRyQVE0WldUNldFakFyUG5jb1Y4Z1VRclhxQVA4NDJmK1lNWWI5RHFncmFicEg1a3ZuMnQzcWRldGJHODJ0QWlTamhPcUxNYW9iU2F4cXdWa1lUOHRTMW9rUUt2MWZoZ2t6elpEOE5IQnVQQzdNVHdXS0VCS2tDRUUzRWRFMXhNQURLd1B1M3NSaGpSaExXZyszZ2srejJtdlU4cTBhTlc0Y3hObUdoekx4eEY0Q3NFNStMQ1cwOWFpUVJOM1VvWmg1aktBZzBiMlh3WHBLS3pycUVTY1BYdnI0L1dWUTMyMm5qRWRvQVdXR0t2WnBKMlRlREo0eDdiT21LVElFc2RHWU1UZzFVaEU2eFFQcnhqS3dWeGFJNVJyaVE4a0xpaGgwa0t0WHQvYTVsSDhzUjVwR0ZISGZ3dlNVb3liQTB1eUVDNnNRVitPbTVReUZmRmpqZHFCOGNpOGxQS1hLTHFCTHJ6bjNmUkh3TmQwbzFiRTg0aGllTkx5UlhZVmhrRCtFNEpGaVd3ZWt3U3VWM3BjQk9ybnRVU3RoWmx6M3hIUURUVGNJNWliOFJyQ2swZEZ6YTgvQmw3VUdtWlUwSXZ2UmdvVXF2TXNHT2dMY3pGWmRpZnJ5aGNiUTY4a2ZzZ3lCMHppdC9MN1BSV3V4RkdYdDFoTVZSVUZ3WXBJS04zVkI3cXVKZlgwamZsU1JaRndMaXdlK3VhYndmTVZ6c2doajUvOXZNNzcwK0JaMGtJcE45NzBTMG5BbHl6R0h0aW1nTUl1RXFhbUt5QTNTQlI1aHZIYmRyNENnTHFUbXIzbFFnWmpnSkNvN1FXYUJWTXdCR0RpdzVOVVhUUnBycWc4U3h2eDlnNWZwbXMrL0o2QjFEelNTM3ZRZzgxdHFRU1ZDWVJpc0Y3M2VqZlFuZk4zcUszd3RJRDkxQnRISmFvMEFaUUdKVFpKOXVsZ0kzV3hzdWR4ejB0VHVpNlJlSWpmSWsxekZRdFpwRExGMnB3NGpTQVdQTlJqNDBYdVIrRzFUVlI3OVFiME9FYkw4RDFoTU5zWmo3MTZNbUhSOTlKaUxNdm1FWHV5a1V4VGhGYjRMTzZVbW1kU3UwTlBpMXQ2NmNkYURpQWhMaVBFTGdUNkZsenA2T2FGSGNSNjRncEtyemtTNDJONEhJeFpNa2R6M0FsYkRhK2pOWHZPR1l3UWl5K0xNNENZWGtrTWtHR3ZTWis5R2xWQ0l5RXBJaXIzbEQ3bmdzZGk4emxGWDYvekNaczlQSUtwZFZlSGJGZi9GS20wV3AreHI0Ykd0R0RrVHR2Nk1Manh2YU8zanFHaUFWeERKVWFkTVBlS2VHSm5uempTdnpKbGdOVHV3c3grRnF5L2dPMkwxMGowWmhDWi92dE9NelVjNjl3cGhKZm9FNzU3V3lOeFJOcThJc0Y1Tkg5Y0x0b3UvbUNxOTc3YnZPSkRrSURCN3lKWEJ6YUhVQkJuSXJra1Qyemg3bGJmUm5SREJUSFZraVZMazVESUxqeC9XL1BSZEZpUUM2SzRmZGx4Y29JbzlMcnM4ZFVWZkt2TTNNYnJ6c1hGT3ZtVVh0K3NsZldvd3UyTC9ndG9mRFhvTUJZZnlEcWIvWlRaRWZ0MC83blliRm1relBEUlZacU5SR0F3YWZVNTU1UjB2SWtNbGR2VjdKUzhNT1BNYWlXQVBpelNLRG4yRzNvcys1MzRFQytaOGZnWmFPVWpZL0xLME9vME9RMmhvNUV6MGNMYWpwUjFINk9FNEhvUm1ydjQzZkFjdGpYc0hYdi81RXg3emdrWk1NZXZhTFNEdjZtcjFGcDk4QXR4L296VTFGVDBoMDUxcVcwR0g2VWpRRXk5aExSZDBBMnFkUTRMZXpReDNvbDFTblhsamt2MG4zTXFlaFozOC94bzZhdHFDdkJtQkc3amlUdXd6YnlVUngzRm1TM0NCNllOYnFON3hPYVRZRnlkOEZDL01nY0xGQmMwS3F4MXllQ2VUd1hucldQb0dvdllVQlYxYjA1cWtIa1d5V0RUaCsveXJFNzF0RjNxbUQvd3F6cUJyNE04NERtWWVuQkdFOWxtb3FIZEMyWnRpK09KVFZKcmlHZWxQQ3RjZnZRaUlQcHdDZ3BFNmg1ekZhRndLajRuZGtBUkRpTC95L1EwWTZxNU5rM1g5RURlTmdjY1pIcFdmOUpKQ3M2a29wdXRtYjdDczIrbVJYdER1S09DaGY5UVUyN3Bmb1NJaklYK3NGdHY1c0hhSms2aHBZMlpzUUhzaTBYbFowc3FMTnQ5ayszdTVnYnBSU1JCczlHaC9BaVY0dkNyYTRkOTh5U0dCdzRSR1FhSStpQ29RaG9YK3lxc3VrYkx6bXJUU3FXMVRXaXJReUlHZ1Q5VnFERE1mUzAxeGdQSlNFSTlIWlp6TGlFVXVGMm1CMi81Y2dqaEFUaWQrdGV1UVB4aldhN2NSc2t5YUhuTENjQURVUU9ESUFPVjJDWXROcnAwY29ZL091S3ZzaXlJT0lacVJ5dE1PMGVNZ1ZJWTBzWmdxeVEycXlubUx0NDBmWmd3SFVyV245Zm9TYTNtMkVRTy9uOS8yU2NuelJWdVZpVnNjM0tCSElQL3AzNlJlSWowTGlNcCtPQ0p3SHlLVW1UeDRBU1V0dXVhWktlRHl1QjlxcXJuUEFNWUVCeElsTGFvdXMzV1pHakIrcW9ub3QvNmk1UE40bUZjbHFDcUxhMGJHbks4ZnJxYy9yd2tuVGV0YUE0c2tXTEw1L21qNEd5MitFQkh3a0x3UXd2K0FKdmZTOXYvNDl1LzY0N1ZFYW15UzdZQ2ZEUHNBQUREQ1FFcWJNQ1h2Ui8xVmEwWi9YUWhoNlkrZUt0MEVpRDdpNmRZODJtQkFoNEJMRmRVV3VGZHVrdUVwaGZ2WXB3N2loVjNxTjB1NFM1NTRXU0dUa0ZsdlpYNG1hbkF4a1g2ekQxS0NWaEFMdEJnSDgzdkhxam9uc0lwOFMydHgwZ0tiYzEreHVaRVppVWlNVVlVdTByQVFsRFcrZHJoN3lVRHZqekFHSnBmTk01eThaMW45em93VzZ5YW5VZWFBNjhSZDd5TUxobFd0NVh6bGhBTVZDZmZYZ0pFelR1YzJEbENVOXNMLzVTVkRaV2N4R1E5aFM1cnJtK2VyQ1Jxd2FJQk1DNUtza0RCZHdOWmh2Q0FCdEpqS2Vla1FUSjd5MFp4SGNhbGVCaU1rbkYwZVRDZzFvUEhPUVZLQ3V3NE94cHRZUS9xS1V0TEFIWFZ2OTlLMGRWcWZDMmpVQWlHQmVYa0t3aGRYTGtJYlZxU0EyZmxraXBBeEhYNnByUEExQjF3eTVab3hPUFg4RVExOW92eXpBbFg1dHU0OXEwWC9PSExFN1o5T1cxenltRXR6ZFpyNXJZbWtFcVdtcHVSNU5jeHFwTWlZam93dUNXZWhubzIyeG5JM09IQ0xDZkFKaHRrcklhL1hPc0tZRFpCRzFJMGJsN2taR2R5cEtUQlhYdXl6WE5WUlU5L005ejhaVytwdG1oZ2NOUzBJS2VaaSs5bFl4cWRlS3lnbldTTTV3czdSYUpmNlRRZTNSaWJZUjFvNkhwRzB2VHpiTEtQZTZnRjJGODdiWlBJei9mcTNLWnZiM3UrSnhZcCtJVjBtQi9VN29YelhRRk1RK3VmWllpNzUxbkx6WlVxRE1ybU53TFJPVUFNUk8rVnJtblkwSVB1cFBVMXc0b0hBb1dnVGRnTk5pNk1uTFQ4V0pmUlhjT0pKMk1lbUc2K2ZNeHNZUU52UVJwa1RGY05vaFV6Y3ZjcHJ3NUV3WEVZQTJzbzczL2MvY3RIRGcreU05YlF4REppUlltRnFydkhYb29hS1JyekxnUjZLVWdoM3ltaWxaQ0lSSm9KbTE3aEtHM1pxTTE0Lzl5OUc5OE9BZjNkVTlqMDk3aUNlaEc3a2VxYXRJQ2hFWmJqbmQ4Y00rS3djN2FtVWp2ekQzQmNvMHl3MDJxT054OWF3OGhSblZiWDZhdkRJbGhySHZ6SU44MzFvUjljRHBwMG1DUEJXZFVDQlNqVGJ1RkZqRC90WElSbGxlT2JraFFKSUdSNlE2U1MxcXkzT29WT1VheFl6THY0U2s3dndrQUMwUitGREVIeVFZbFVhbVVkTWcyUmdwRUdhSVd1V3IxaGNnRm10QmREV2g3ZFBuWTF0U3VKOC95MXp4NkRvN2ZJYmNFenBBK2E0ODNtRG5vemdld3VmaFdqVCsvUS85WlEreFQ5UWJBT1pQSXhHV3VhSXVrVk8zSWxvZDhJM1NGZFJCTHY5ZXBDNzFLeXpSdVlpMktkOHJ5NVNINit1WnMxUHlZUlpRakdDK3Q4VzRtSE82Z1lFRWVXSkJ1UWhnSHdmV2xhZXlWb3hac0NBQVZKRUllT3hPZDZtNW45OHRCUDdHTmgxT1M0eDRCS2FVN1A0UVQzNVVIZW5meE84WWFQUThmbXlobUJhSVJVZklBTVN2ZTJZRFp5SWNNTTkrN0tNSVVabzJ0eXRvYzdCOGVvZzBNaUkrVkpFdFg0c29FRjFSWkhQZVV3NWlCTjI4OTh2MmVTcGNnVUJhWHFzOUN5VlZtTVJQMEtLUDJ1REt4MUdJcUhjS0ZCOXVQVWRkQS9vT3dNa0tVUWsraFZVVDVPbEVMdjd1a0FBUEE0eE4rZkczVmYxeUVKV0FiVGx5dWtGcThjNXBTRkY1cXVHbUgwVmVpQzVvVEFka1VES3Z6WGhWWUs5c3BRYjNVZ1Z0Qld6N1ZScnlOUVVST3BIZU5xeDlhZHA4YWREWCtRSHJUKytYblN4VVI3SVdGanlNTkZJRWlMWmkxdks1UVVrZlRDUU9qdjh2SHdiUi9MRHF3Z3M5bXdsT3pPY0RLdVBVK0dTb2lnVFdRejRWN0N2SHRaVDI3WUdKVG44RFFFM3IzdjB4aWxvODJ2U3VXSDg0WEU3VEJsTUpFb2R5eDNDRngwVUVkc3VhRHBPSEV3UjZYNlUyU0xseERYSXVZeEhlNXh2NjI4bXU0bDRMSnBYUjhkYmljTEZKQW55Q0FVeDJLb2dDamt1cmU4bXNUZktDbG8wamFlN1hNR05PSk15b0ZYbVlHZUh2eGhNUGMzTEtYLy9VY1p0c3p3dFJrQmNFdURXQysvQWNWZVBOSHVOWWI5MEpIcnRucGg1ZDlhL1lpTkpzY1N3QTFwUVZrdW1TQWtPQWdLdWRzcnl3c0N3Zkg1anNydVpHUTJDd1hKRXQzUU4wU2NLUlVnT1NCQ3FYa1BqZDVSVzJuOFZpamt4anovbWptakhCNmk0eHM5NEU2Nzk5STAyaldYNVd3UDZhTFRaTGt5TjhxNDUxT0RmeUZVZEY5WWsyZXQ5VUpsV1NzRFJMSWVCd0ZyQkEyZTdyRWsybWFLVUNCRW5PUWM2bUhVMXQvZ3gzK1VXVVFXbkpMZVUxbWUvbkFEdy96UGUwd3d0Vm9BaERZdDBoR1hQblJydjFoUHRGS01CeWtqckg3a0J5U0R3WDlQMi9XZkNkQlE5K1J4cHRsR2hvRmdpMUs0NVlOeEpEd05wTmd5MDV2WXUzVUtrMkpRYVNGUzcwK0Y1NzluRE5RenZpK0pPRlRsdDFmWDJGNXk5NEV2NHZobWRQSmRVOFVVRjU2Ymx0emxKREVFdmsySlFrOTM0aHpwTXJGZ1d3ZHUxUkxxSEhCN2h2T2hnaHNqV0ZGY01zNjZaRUtWcVhKUytxWWNVMHk0akwySVQrNlF2N2pvQ3BWbUdzUWtGY1FyblhxOUJiOTdaUS96UCtwaldmWTU0UmNRVlMydUU1YURObVVyVkdLK3E0d0xRcUhuRVViT2puSHFFeGlacUtxOVdRaUtUK2c3QS96bVlIQ2k0YzFTejRNVWhHb0t6U2l4aXoxYUNJUEJXdy9vczR2cUVqbXgzOGx6YnV0OWNWbElzeGNkTUpUTERRK3ZOZ0YyY1ZRaVcxRTQ0d3lWcnI3TUFaOE9KRVpFSzlEZWt5MzJQUkFuSkRUVXVqdGFscmJ0T2VOczhyS09uTjcvNFRqUEwvZmRlbEI4bjA4WXdSNXdmbU42VGpGWUhRSDFjbUZmK1AvNUxVMTI4Q1pEYjNQUStxMlFJazV3aE40eGwvcy9lb29pallmeWtDcm5aSEhHWkluTGhoU2pWbk5ISWdTL203VWV0NlhBTDdvZUl5UFRLeHVnbDJzRWtUQzNnZ0tjTnFZR0E5U3ZlYVlaQ00vWHNQRUtQbWs3QmlRNmprWFBKaE1yREd4Vkc0SW9aSDgrYjBrUWJYR2l0Mkw0L3hZdHh1bTVzcFNPSjdsTDltVFpRNnBxM2JOaTEwZU1mZ0ZWaDc3NU5JRlc0SEp3U1FtaTU0bk11blZTQjhxdjZKc0w3SGlsZ2N0ZHFSNThTTjVad1lCa2dOR1hzYjA1QXJWemVXbHh1Y21BSHNPT3dyczFnMzh6bTRZN2ZPZmducmFhV1kxanZZOFlEODZQZThkZzR4cE5paTg3UnNDZk5WK2NKVmMraktFdnpuZVY1Zzd0RmlxZCtsZHp4STlKemdSS2t0WUV6RUpRSVU5M2UvclJaN1lrVkZtNVV1cjVhMWYzcG83T0VtYkJUc2MrQ1FaOGNnYmIvbUphRXJoa3NyL3JURjBNcjNxeDl5SlJWSEJ6YWNWd0dScEFRaURPdnJkWU4xQXBVOTRyR1lrVFVzdWs1YjE1Wll2QVZxRlRzVlVMaS9HY29mbEljMm01Z2RFTFZOblRmdXY1Zlk5S1NlWHFoUU80S0pOYVZmbHAwQ0VKYWFFZFNLUXJJNXRaT2w1RkE4VXZlNmxTWVd5TVk0REl4a1RiT1JoWHVBdzR6b1RTMjgrN3d2TXhydVBkZnlKbUJCTkhQdCtEYmdKNHovcHJZWUhpTmFMTXNZamtQZE44ajNKZDczQXJFZk92Um52MzYxSVVVMFg1RDc1dlRSdlpkbzMzWERzanRlOU4weUo3K2lIQnF1a1FJY2pIVW9ic2RQN0hOajBVYWNSMHIvTmRlVTlGNFBNc1VLY2t6Tk4rZGhyMVI2d1J2R1VZb1pDRWJaWlJMWEt4QnA3SElUNEVQUktHakIvdW1xTFhhMXl6RWx2QW1WQUJhMDFZN3dGdk4wM2Ywb25FbUhTM2w1d1paRmV6cjVibnN5T01XVGxhMU5kaW1ZNXNVeE15VFliZmc4dzB2cXNEc28zWFAxYndLdzZ3M3VIRGQ1UHBSWnVDSnR0eWk0ZzJGeWI0Ymg1UU42ZkdORTI2ekRGN1Y4QmJwZXJLNkFKQ0xTWm5kaDZMMTlPUTBram4xUGpEMGk4c1BZcGFXOWxVeVJkZElPKzRWQS9LemxPUzJ4M2s5VUtUdElsTTBUSVdtZXFIS0dYUVpocGpvVGI2VlNKN203cjZaaVlQMnVsQVVvZmVWL0o2eCtzckxEQXkyQ2ZFNnFrREZ1OU9NWDBBSXVnN3loQUtOMDRyT3hVNk5tcGtjOUZ4bXUvVS9vR3hHdmIzeFVFTDYwdE1sSE9EaWtqY1I5RDJrKzRwbEc1WnV0d0FIY2kwRU02WHRrVEhQOU5QMlRTR1VFN1E5SGYvU0VEc2V0a25hZXhvWmhDczJLWDFMeU5JS0U0N2pkMkR3MTUreDRRVXV0VUFTbzU5Q1lHMVFBeW9BVVhrV3dtbXkzTGdTUWp5T3ZLV25qaE8veWpPd0FyWGd0NFBrSVVnZDQ1N05ReFpMbU41K0J4NVJoQ0FHdkUxYmxOZjlMek9keGJiaG5VZ2Z1RDM5MXVSRkhjS2RYREY3ZmVqb3gveThtaWZJcTRWVzQyajBHQnFOQUtkK0prMnJCMW9hOTRiT2hxcVVzanhqWnlRaGRXTzhNblR6T2tOaGVpZXU2blYxcW5yZ3JHU2huWTNJMlczb29GNFNnczRjZ3drZ2h2dHpFa0xUbU5OUm83RTdudVRuMkxJcmlGSnlvTmZQdUp0aWN0S0JtNzRGZytkWVBTMlIzTzNmOWxBZWxiVWZjbzZGNU9EL3hkS1VuRTh0V3FOMExVcDlWQUptWVZYZFVDaGJ4MjM4MWtDaStLNDJoRzUydFNQYU1hb1dTb0xQY2Zrb24rc1pYdjdEdEtwZi9HTzdhcUMza1pzRGpva29haHJGZGJWSlNTZWhrNGp5K3RzRHplQnJKSjBrMVZrUnJHN1NoVHZjTmd1cjVucVRUTEE5dlJMQmJNTTlhNlI1NEZ0Z1pQOWFKMU1aMEdCcUVpMnF6Ui8yd2tYQlhwcFhZdi9TcU1RV1dhbTVsSHBMVktxaDN4ZHRjNFdmck9mYldsbU1PNXA5Z0JUSFp1YUcxVGFkZXFRVVpKQmZBS01ENFdSR0NsMDFaeDRTVzE0YzZrdnFKdXExL080N215L3RsVHlLWndpYlBkQTNRMVVGd0I3R2Z4anEwaDN2ckxFbUNrS3Vsc0VBUkN6UnZNVjJSVnBVbFpUV240Y1Boc0hjcTNROElHSUYyKy9nOENFSU4vMU8xcVMvMkpXcXlDNmtIb0w4Y2R2R0VHbmkxSTNDTk1JcXhxaHhJL1V0R3REc2VwYmwrSHI0elh4MzZna3BCbXBoT2xkTFVYTHAzVEtibVVZRWJSWHcvZmRmeFQ3WDdZUFhHQ0hHVG1uTzk4WkxDOTA2Zmkvekd2b04rNlpzbCs3MkpWMGxJWEo0V3dZdWxFUmZHbkFDWGNoa0Yzei9ITWR3elcwTUFFaXptQmwvREo2ZUoyU01PSG1Uc25YbElGRDRlcFRrYnFBQ0dpZ2I1UExFdHdQRVRjYkNRckM5YUtTU1FnSTdEZXd1aWlxM2J0Y0RUWkIzeEI5WWxlbmhpU0FXNjIwcmwzc2ZjY3d3eGFSOHBDV2Rzd0x3dmFxcDhjM01PV3RCc2xPcmVTSkNEcWgvdzBYbm1WMFJVWFpNM2JvUmkwVXhsaHVUeDFlM1NTd09pbTlOczNYV3NoTmI4Lzc3VkhnUWhRVFlSUU1NRllYaWRmMElCKzBtSUpocWNoQTlUeUY3dGRjSDhrUUJUSHNEWS96bFpqK3EwNlFMd0JkbTkxc3IyK3VzZmxlaXB3WUMrcmdiNHROVnA3VU5rYkVqTnR6ZWZsTi9VRTlkbHZtT2x6V1dtZkh2NGVkUGkzMmJmeUNRS1d6SGJVVEV3NU0yVFpsZnpNaTFWUjVsaDBxQ1lqaDNITUlmL2MwcHBKd2I1b1lFTnBBenlxbnlmdmlTV3lBYzc2L1l1VWwvb2FVaysrYzBZc2d1TGo5ZGFQdVVvemhoZ3VjSytQRGlNckI0ODU1Mk83VWg0aHRwNmZ3S2dJa1JCTVFIUTd6MmV5WXovV1AwQm9ZZVhjOGc3aUprclhFNzA1bFo1bXhGU0poT3E1WlNleVJSb21pUm41K3VRemM5ZFdWQjBYb2JURXdOc0VRM2FIZ25JY29BczY2UGplUT09PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWREYXRhPjwvc2FtbDI6RW5jcnlwdGVkQXNzZXJ0aW9uPjwvc2FtbDJwOlJlc3BvbnNlPg==PKAmGߖO O "social/tests/actions/test_login.pyfrom 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') PKAmGIVt t &social/tests/actions/test_associate.pyimport 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() PKAmGF#)` ` 'social/tests/actions/test_disconnect.pyimport 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) PKAmGd`)W!W!social/tests/actions/actions.pyimport 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) PK%D social/tests/actions/__init__.pyPKAmG?_social/pipeline/debug.pyfrom 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) PKAmGw  social/pipeline/utils.pyimport 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()) ) PKAmG"ϴ social/pipeline/user.pyfrom 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] 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) or 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) PK%D2R::social/pipeline/disconnect.pyfrom 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) PKAmG9R R social/pipeline/__init__.pyDEFAULT_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 were 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' ) PKAmGAsocial/pipeline/mail.pyfrom 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') ) PK9$FB0;77social/pipeline/partial.pyfrom 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 PKAmGz1 social/pipeline/social_auth.pyfrom 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': False} 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]} 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) PKz9H,''3python_social_auth-0.2.14.dist-info/DESCRIPTION.rstPython 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://pypip.in/d/python-social-auth/badge.png :target: https://crate.io/packages/python-social-auth?version=latest .. 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 * 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_ OAuth1 * Flickr_ OAuth1 * Foursquare_ OAuth2 * `Google App Engine`_ Auth * Github_ OAuth2 * Google_ OAuth1, OAuth2 and OpenId * Instagram_ OAuth2 * Jawbone_ OAuth2 https://jawbone.com/up/developer/authentication * Kakao_ OAuth2 https://developer.kakao.com * `Khan Academy`_ OAuth1 * Launchpad_ OpenId * 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 * Odnoklassniki_ OAuth2 and Application Auth * OpenId_ * OpenStreetMap_ OAuth1 http://wiki.openstreetmap.org/wiki/OAuth * OpenSuse_ OpenId http://en.opensuse.org/openSUSE:Connect * PixelPin_ OAuth2 * Pocket_ OAuth2 * Podio_ OAuth2 * Rdio_ OAuth1 and OAuth2 * Readability_ OAuth1 * Reddit_ OAuth2 https://github.com/reddit/reddit/wiki/OAuth2 * Shopify_ 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 * 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 .. _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 .. _LaunchPad: https://help.launchpad.net/YourAccount/OpenID .. _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/ .. _Odnoklassniki: http://www.odnoklassniki.ru .. _Pocket: http://getpocket.com .. _Podio: https://podio.com .. _Shopify: http://shopify.com .. _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/ PKz9HLO  1python_social_auth-0.2.14.dist-info/metadata.json{"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.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3"], "extensions": {"python.details": {"contacts": [{"email": "matiasaguirre@gmail.com", "name": "Matias Aguirre", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/omab/python-social-auth"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["django", "flask", "pyramid", "webpy", "openid", "oauth", "social", "auth"], "license": "BSD", "metadata_version": "2.0", "name": "python-social-auth", "run_requires": [{"requires": ["PyJWT (>=1.0.0)", "oauthlib (>=0.3.8)", "python-openid (>=2.2)", "requests (>=2.5.1)", "requests-oauthlib (>=0.3.1)", "six (>=1.2.0)"]}], "summary": "Python social authentication made simple.", "test_requires": [{"requires": ["PyJWT (>=1.0.0,<2.0.0)", "coverage (>=3.6)", "httpretty (==0.6.5)", "mock (==1.0.1)", "nose (>=1.2.1)", "python-saml (==2.1.3)", "rednose (>=0.4.1)", "requests (>=1.1.0)", "unittest2 (==0.5.1)"]}], "version": "0.2.14"}PKz9HqzA1python_social_auth-0.2.14.dist-info/top_level.txtsocial PKz9H''\\)python_social_auth-0.2.14.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PKz9HmD**,python_social_auth-0.2.14.dist-info/METADATAMetadata-Version: 2.0 Name: python-social-auth Version: 0.2.14 Summary: Python social authentication made simple. Home-page: https://github.com/omab/python-social-auth Author: Matias Aguirre Author-email: matiasaguirre@gmail.com License: BSD Keywords: django,flask,pyramid,webpy,openid,oauth,social auth Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Topic :: Internet Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: Developers Classifier: Environment :: Web Environment Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Requires-Dist: PyJWT (>=1.0.0) Requires-Dist: oauthlib (>=0.3.8) Requires-Dist: python-openid (>=2.2) Requires-Dist: requests (>=2.5.1) Requires-Dist: requests-oauthlib (>=0.3.1) Requires-Dist: six (>=1.2.0) 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, 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://pypip.in/d/python-social-auth/badge.png :target: https://crate.io/packages/python-social-auth?version=latest .. 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 * 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_ OAuth1 * Flickr_ OAuth1 * Foursquare_ OAuth2 * `Google App Engine`_ Auth * Github_ OAuth2 * Google_ OAuth1, OAuth2 and OpenId * Instagram_ OAuth2 * Jawbone_ OAuth2 https://jawbone.com/up/developer/authentication * Kakao_ OAuth2 https://developer.kakao.com * `Khan Academy`_ OAuth1 * Launchpad_ OpenId * 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 * Odnoklassniki_ OAuth2 and Application Auth * OpenId_ * OpenStreetMap_ OAuth1 http://wiki.openstreetmap.org/wiki/OAuth * OpenSuse_ OpenId http://en.opensuse.org/openSUSE:Connect * PixelPin_ OAuth2 * Pocket_ OAuth2 * Podio_ OAuth2 * Rdio_ OAuth1 and OAuth2 * Readability_ OAuth1 * Reddit_ OAuth2 https://github.com/reddit/reddit/wiki/OAuth2 * Shopify_ 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 * 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 .. _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 .. _LaunchPad: https://help.launchpad.net/YourAccount/OpenID .. _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/ .. _Odnoklassniki: http://www.odnoklassniki.ru .. _Pocket: http://getpocket.com .. _Podio: https://podio.com .. _Shopify: http://shopify.com .. _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/ PKz9HΖ%cc*python_social_auth-0.2.14.dist-info/RECORDpython_social_auth-0.2.14.dist-info/DESCRIPTION.rst,sha256=4-s7bse5cWmDy_KMAVcu8S5QOqppWMgz9Y1DOBGx7fs,10009 python_social_auth-0.2.14.dist-info/METADATA,sha256=RyuNA-Jc-aYKRuCnNJ_7n2t9DklqNruaYmdWlo_-5Uk,10942 python_social_auth-0.2.14.dist-info/RECORD,, python_social_auth-0.2.14.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 python_social_auth-0.2.14.dist-info/metadata.json,sha256=xk371Z0PQpy82Ln5Jt1rcdMSdwuzkp8ETcfOmwvjR-o,1290 python_social_auth-0.2.14.dist-info/top_level.txt,sha256=UCHX9v4j_6Xi0jD_wu30492cSYOfeHcAnW1im8_JKGQ,7 social/__init__.py,sha256=HrU87N2OZY8ILQDrCzjJ_P2Ue0H5c3RxgaRRxgNjdVI,212 social/actions.py,sha256=my3hXmS0PYov2DKfuTcuvRjXG9-lAyHWInfFZWR3LbQ,4788 social/exceptions.py,sha256=7z21NWW-RD4GHpJhvI6JGV-fllXzXCeqJ_XxumDTqyQ,3143 social/p3.py,sha256=lK9sbQqkKAOJ_c4KQtjS1Kw42SIrgwj5QsAGeAwZuEg,599 social/store.py,sha256=-6EU09rygTA_14ea3KjfLNdSxjBrRx4a-hrB2S0FTAI,2744 social/utils.py,sha256=7OIvMoRtt28KbLWe6zqt0GYBLCDtG4vgPFrzwwH5uoM,7238 social/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/apps/cherrypy_app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/apps/cherrypy_app/models.py,sha256=5LgBqG2eKC0DbQPpuR3X_9yaERA7jgL79cX0r16VeQI,1762 social/apps/cherrypy_app/utils.py,sha256=kD6PwMrOuSp35os9V6lfXtFzJtxfsj1oLc3LWSgbmTs,1721 social/apps/cherrypy_app/views.py,sha256=NAH_BsaYm9-L3ygrnpP8aXE-9nv_v8eUyvIWQudRNtE,1029 social/apps/django_app/__init__.py,sha256=FnrbAhKCDEYYoTLd4gjnEqe9n2X6xoTNvO_6NDZYZSk,889 social/apps/django_app/context_processors.py,sha256=E-Svj1myA_zwhInGozU0mKxfJgT5qtRgul9R8XaCZMU,1540 social/apps/django_app/middleware.py,sha256=_Z7MLy9KP9c2pw_2NY391v2dwRfD6XV9xw62pVB34ZA,2285 social/apps/django_app/tests.py,sha256=wOcL3VhyphnGRw2IiuAX28Qp1ActVx0tiXJSDAhWZC0,2416 social/apps/django_app/urls.py,sha256=4O9pLIIlLnkW7Y_LrFdDnl6h5Jsq0IiZqClCUqBYNT8,856 social/apps/django_app/utils.py,sha256=OfprNCl_uJVdyM_cxNdrF8rPWIlmPag_giVG00JGlZw,2402 social/apps/django_app/views.py,sha256=_wZOOkQgvXlWuVm47hmwd5qtYra-rfAADaEpZHd4Ddo,2181 social/apps/django_app/default/__init__.py,sha256=dQXAHEViwEQevpqOmXwYWtASLQz8ItVQHp_I0XYDHvM,290 social/apps/django_app/default/admin.py,sha256=gvADTC_jWRifh4jAnvzAnvHcrhdoszhbWJ8oZ36XdqY,1719 social/apps/django_app/default/config.py,sha256=JZzq_TiQxAHiUCVbCq4uFJjVM3iN6hI2xhts76LBiWA,544 social/apps/django_app/default/fields.py,sha256=zRfPN-7wjqic7VYYf5S8tjcYr0k1qbJJ8ZPmlNnYSl4,2513 social/apps/django_app/default/managers.py,sha256=KzGn86KPekqIPe040Ga16Qu9oYkjfUkL3GGKuo-Rxh4,385 social/apps/django_app/default/models.py,sha256=mB43X3aUjV1mOwu7brlAOmBzigptWyWbq4zi7WNdkVY,3989 social/apps/django_app/default/tests.py,sha256=6qRfKzIv52H8Wdt4ucYKbQmDpxqwJ8GDMCageRK-i2A,43 social/apps/django_app/default/migrations/0001_initial.py,sha256=Cqa28t5zKAQAH2I-R1i6cy4_1vJVzNB3j-vY8U0tSWo,3829 social/apps/django_app/default/migrations/0002_add_related_name.py,sha256=_H-QXP5KEzwlemOmAC7g7tfk7UM_L0yJjjfE5N6YZ9s,472 social/apps/django_app/default/migrations/0003_alter_email_max_length.py,sha256=PjIaZo1KOIdq3bgRrF9fDkbUV7z5Ff5eYHUayipZg70,399 social/apps/django_app/default/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/apps/django_app/default/south_migrations/0001_initial.py,sha256=5hLZ5pBfFUnytAwN-y8Fl-5zRtty4a3Rj2v-2q3MHqQ,11002 social/apps/django_app/default/south_migrations/__init__.py,sha256=O4LYZ9y2zP2HqKI2BKcwENKbAXK_kx-ZCuo-bghjbx8,1402 social/apps/django_app/me/__init__.py,sha256=RbKFoi0Naph7gfBGonRtEEwEHw2hUkB7LcU8Dh_EhgM,272 social/apps/django_app/me/config.py,sha256=ohtkkL5nDk3DLJSenxtdnrY_CYIkpn_MwDIIOHAuryc,539 social/apps/django_app/me/models.py,sha256=9jSOtw0IAq2rK8LcrI9B70RegG5W0YfBI6gweCqLIfo,2124 social/apps/django_app/me/tests.py,sha256=6qRfKzIv52H8Wdt4ucYKbQmDpxqwJ8GDMCageRK-i2A,43 social/apps/flask_app/__init__.py,sha256=ASpI4lIpSnmHhob0L32EuuEOVMTbLlTWvtq3uFq6Gq4,163 social/apps/flask_app/routes.py,sha256=xCYCmJZtgJm_mO09fLJhorf6ht94i4ZgnnF13fTwahM,1614 social/apps/flask_app/template_filters.py,sha256=q0iWZhnk-Llvg3ML_xws9-7rvZxubCva0t56Spe6hzM,846 social/apps/flask_app/utils.py,sha256=iLHrrjz5S8z9RuDD_n5W8L6wM-vkOgvu32RleNzHct8,1627 social/apps/flask_app/default/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/apps/flask_app/default/models.py,sha256=jLzPjlwwnLMFWqJIwpwhB9IhomcNK6BIeUYjXze6onE,2369 social/apps/flask_app/me/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/apps/flask_app/me/models.py,sha256=suLoQDaopdMEihmORzElMpIl1ADH6uIfHg8MHPDWvlM,1384 social/apps/pyramid_app/__init__.py,sha256=kPf1TX4XcDlnA_VBRqk5ZIKDc6wM_Zg2O-3VRVlLqI0,493 social/apps/pyramid_app/models.py,sha256=IUvPHTsQdpFbdppOR1ebGuZSlsxy6l5kUKuaEgyK5Sc,2119 social/apps/pyramid_app/utils.py,sha256=SN94u6CiaKP8E6uSwDzPJiR6BapIjpbP4kRr0U-05M0,2458 social/apps/pyramid_app/views.py,sha256=QikEDOJt0tPe2X8DonaRmfTgUgBRPwY5G8CDMxTqCeA,1091 social/apps/tornado_app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/apps/tornado_app/handlers.py,sha256=6wUJo1LTiVV0r0XtKYpxlToV_DzHhUTQHKBPeC8rjaM,1201 social/apps/tornado_app/models.py,sha256=noH9QqQjB4xTFZgi8FqCg9w7QeC7yJ7jkDV8rLt8kf0,2063 social/apps/tornado_app/routes.py,sha256=_lO2EsV-3aph3WxZNK4ZiB4BXNhJ4N9-CaTGia0oq4E,489 social/apps/tornado_app/utils.py,sha256=UhJTYkn4bE3guAMHWfGt29SYToy8CzmWeSUpIz6YM8A,1546 social/apps/webpy_app/__init__.py,sha256=kKfW5OLCfcyW0VjDVmjCnFwR_YeFX96n86RtWyhNW6M,163 social/apps/webpy_app/app.py,sha256=4J7BD3LiJkGfnAgUBOnR58LIY7RWf08kJrW1mUpLjo0,2153 social/apps/webpy_app/models.py,sha256=lPN86NBdzrx64LbpNqpNLGHUTXwxl-JgYRHlXLKdgH4,1781 social/apps/webpy_app/utils.py,sha256=rjErxbOxveUCjtaFN_p6-6MDf0GbfQ9DQ6Te-85pX-Q,2142 social/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/backends/amazon.py,sha256=SHRlnmN2XqE-QRpT3QRkIsSHz7j1MKUIYmVtv8s8Hvo,1568 social/backends/angel.py,sha256=YuBwuHaUoNGHqRGV6IRBhDvNAaJvPKHqllEFNIG8Dr0,1065 social/backends/aol.py,sha256=0CwOBbSX8-a06CIPh2nZxhc7EQslyEEzTSNmfe2CdVE,222 social/backends/appsfuel.py,sha256=yajMJlI04Fck1IcRbuZtmmzF48AsbB9PTl3swgmbhTI,1502 social/backends/azuread.py,sha256=iaDkcDDHDa_-CeGGhAXPUlodKix1bP8IFmByEjg6iOo,4519 social/backends/base.py,sha256=sYwXiqqrEsndtdNSPNpM-fq1busY-cQ_gZ0g50taDEs,9755 social/backends/battlenet.py,sha256=7H43SCgJoqyh0TSAdauWH4tDo6V7g4JlKiI0s4ZG8FE,1828 social/backends/beats.py,sha256=7EhlvFbFlMh-F2-H_nrx2cVwrm5WyqRwiJl0d5CKInQ,2265 social/backends/behance.py,sha256=kA3I2Xvs2D20B5wiiXkv_s9kItoi9AsTjbMYobi4_LI,1581 social/backends/belgiumeid.py,sha256=1yW2xMpHCfDpMntkJ93Q95n5_5znGPSNPLSC6E8izaA,339 social/backends/bitbucket.py,sha256=ZG8C3zq5jkSsS_Dfh_yCSaw-MggoJN19h1wMQDpjSI8,3679 social/backends/box.py,sha256=rjMC7WnEKk3RHN2lLM0meWLo4XnYuJ6GgqXumgfDE_Q,2193 social/backends/changetip.py,sha256=B1wFuLYfsu9Foa3pcFPNucW2jaxcnrXvVXQJgmcr-jU,886 social/backends/clef.py,sha256=DO6qsrRMno7Vrh4QJP-IA30eEld9a5649k76FYxLbcA,1768 social/backends/coinbase.py,sha256=6EsW__koe46Vpoe-jjmL7k3Xb9Iik2szUPRHohvcysA,1262 social/backends/coursera.py,sha256=3v5D4YEi9SWQuPZXTdk4CGXC87aOY6iPXdoGVRdDdMU,1320 social/backends/dailymotion.py,sha256=bmRr7f0y45yHE2YfcGIRHoHzjblJnFLEC74t0Ux-lH0,887 social/backends/digitalocean.py,sha256=THlV3zAvAqQB1wtXtrjvzc5M3le6NjArFSIHVZAZGNs,1440 social/backends/disqus.py,sha256=GKExF1u-DtQRH9VZ8HTBxZA46n5fdoc4nCyb4t8cfbM,1811 social/backends/docker.py,sha256=HytpCHyeksToiYXOX_kVSJWjaIcGJWm9E4-F-xBF6NI,1615 social/backends/douban.py,sha256=R07IWgiVRP0g1by4CrJZ0S13O_OHozYsvMpGcPB1t1I,2100 social/backends/dribbble.py,sha256=u6fhdHoJB34OboNF42LE7lnBgts2xqq6hZ0qeCFStbA,2218 social/backends/dropbox.py,sha256=_pVj_YxmqUeqLqlU2P_TIC-oSpqAvvVX9T6fHG1NcO8,2385 social/backends/echosign.py,sha256=l29IY25JVQBH-z2p-rRfIhXcl1wFV7udokIFhRpgyfs,859 social/backends/email.py,sha256=caiwqDJvpm6gq0kn9oPQ9Rqk0qsSgC9TiSAZTbB6ucM,278 social/backends/eveonline.py,sha256=cvPPpKYk0myMi_BWjhur-zRv8v5Qq_ifB2GSYsPxkII,1413 social/backends/evernote.py,sha256=DxAD4ao57V-MRVrhup4EjGwzD_dtoMjv3MhKt2sJvh8,2705 social/backends/exacttarget.py,sha256=5hB6_CdyCV2k7wB3z50QnAvd4Ifx8Wg0y7FCFw0e2Ak,4029 social/backends/facebook.py,sha256=dSlhXrd-FZ_pd1tfaogNDCdgrinWaznN3qeAqiN9qgE,8273 social/backends/fedora.py,sha256=TrtsVrh73mfmltMjzLzOeT8klQGtMcgz-MdPLEgOE1s,271 social/backends/fitbit.py,sha256=H8zOOv8Y0hBOgPDZo07ro-pfDut9ZhVQFEyz4qyy0NU,993 social/backends/flickr.py,sha256=LMyC4CSyB5i8Dr519blNrvuGR9ZFEJ8K3RFV7jW6BSA,1496 social/backends/foursquare.py,sha256=ZB8M3y59b42k7bXhZ1V_3EEPi0hIY1bu5NPxwLY4-Yk,1376 social/backends/gae.py,sha256=OauPdryZnVqA9vfEzyMCCbQrX8pSydaKlRwGMyM-JaY,1292 social/backends/github.py,sha256=by12ExkhwElrwDJ0HjGbcAVdXuq9oWuPgREs4Mn51lM,3946 social/backends/github_enterprise.py,sha256=YCBRtHwItbtusLVWrOzEdbqXwPtecL81z8pTV45_ubA,1330 social/backends/goclio.py,sha256=XW0DxZnLiups4f6XSF0PoWhuYo2KV5geUweC7VimXrw,1247 social/backends/goclioeu.py,sha256=QBEU7g4He7LSHOg3l4Y_y2bxw2Kr3cubOmtfY8aFRYQ,482 social/backends/google.py,sha256=bOTFg5jNG85gP4uyD0i2ypKqPpLbwxJu4NsYWv0yg5A,8047 social/backends/instagram.py,sha256=pr7UZrGcNMIH8U3VZZha9aFfOmyCC5pcLG2xYVjwzdM,2069 social/backends/jawbone.py,sha256=JIhHQjOxhmOadZAdcwuTr53rDTU3xbgxWVwWIuyf-i4,2794 social/backends/justgiving.py,sha256=kkWlsmD_Mn2wPGSabHCX5-3CMj0zcxX7YcjsHren9UI,2153 social/backends/kakao.py,sha256=Nwen4IBKZVsRZ_v5gKD5H5N28mwRJ_DkUlUvNnduoqw,1285 social/backends/khanacademy.py,sha256=B4-TOZIcNjV8FagCarkTeW9xhQUDP6qT7hXJGKeB2rc,5039 social/backends/lastfm.py,sha256=WZA__G5gS_ZCm_L2fHu9YhCrOZdyDpnBY1N3EnNUwF4,1888 social/backends/launchpad.py,sha256=ovLKb59SPGlIw1nozX9dz34NkJivPES8suckfMNtVnM,211 social/backends/legacy.py,sha256=e2ANNPIEYkvQevOFTeaVY3LezQcuzCwH-vtZeOkkIUU,1519 social/backends/linkedin.py,sha256=5GhaJmwYpPJ4VqbGzqJHtlFSGQRfmsX-HEuZyzpf-hQ,3776 social/backends/live.py,sha256=wJnMD4SqtJ_tgnj2X3QMCfr4H-wRZMNqCfMQvBgeYFA,1565 social/backends/livejournal.py,sha256=ecCyHwLOCElUx-UnNfx0PnEP9yRTCd1pgLoFRBKWIik,991 social/backends/loginradius.py,sha256=ajEXByEDO0eJuyaHdDz_PUDdMUbLlQs8KAvp0hgyWGc,2617 social/backends/mailru.py,sha256=TFrb-FquumvOeOk5BR7dnwRYP3rZI78uWOT6vMpdRa8,1693 social/backends/mapmyfitness.py,sha256=l2uEHno5bsxatEISMFF3S9oMoIE_iAbkBIkaNjccjLQ,1525 social/backends/meetup.py,sha256=6OHTUvdtWA9XHq3M_Z5PVyGD3js33pLZg7STKgC793k,1194 social/backends/mendeley.py,sha256=si6WLYzx2xoBvpe1frmLlTGkCA7bqFQP1E8ekft6jG4,2245 social/backends/mineid.py,sha256=q3060RbUL3tdHdOTpoDGAWD_bx-_haYgICFKB63mpBU,1257 social/backends/mixcloud.py,sha256=cWSlhpL-MtFZfCPhg0J-b8M60xAKHLu9_RtBDRTqoGE,961 social/backends/moves.py,sha256=fHtLLtjZwSyj07BpMJ_EyvtXc9pO3r4frbDjhQlYxiA,1011 social/backends/nationbuilder.py,sha256=FdzL3cQnsOayNo5cVTYHyW3iBFIc-_4Bdjv_fDLCq1w,1618 social/backends/naver.py,sha256=hyjs8jN7LZ43yB2uewOHyhGvho7zURoKZcBpSIX4y-Q,2015 social/backends/nk.py,sha256=n9N_3HeXuGBdvqWohChnERH6NIGSsOrELJnZVol1RlM,2723 social/backends/oauth.py,sha256=comGoR2NvNMnv10WiaSdlN71dKHAteJbEvlOJRxaAjc,16628 social/backends/odnoklassniki.py,sha256=x82vPXO22xP8-UYVlSFnG2wvnxigIX2YuKYWqEulSSs,7099 social/backends/open_id.py,sha256=ALF0BeFTVMxcQHrUw9cpR7m6I0p1sWgZsHMoibwYyFo,14306 social/backends/openstreetmap.py,sha256=htdFbqwo9P_Mszl8unr6IiRQ2s_rLA_qChIw9eGrZ6c,1920 social/backends/orbi.py,sha256=TJ_YnHozr-zqYKsEujtQ7DSq_HiuVGY3Z24t_xKU8Ho,1283 social/backends/persona.py,sha256=jdzgEUL26C9u0cRllO0UuRLwWEKSFUAuRX1oQtgSs3A,1845 social/backends/pixelpin.py,sha256=avfCUFlJKMvf2g4gpB08E-UXvYyMlFl264ZtOyHWFec,1193 social/backends/pocket.py,sha256=oWHGPGymG7hk6QAKiqD7esbx2XsmR0RJZE0e7_T-nIk,1730 social/backends/podio.py,sha256=66soHIbZTG16gzLJSqbRP2zS-naAWtCoQhGvH7cEiTA,1246 social/backends/professionali.py,sha256=RmIWxgeDKTuMtfAWBLV4S5I2U0dpJIDtKHiivWOVT3M,1907 social/backends/pushbullet.py,sha256=3LyRyFQkUVSHz1VN13Yt-VmZ8bPnMai643yQpFBYIp0,845 social/backends/qiita.py,sha256=tuGcJMIon5eNb-0Im5ZjqrQOEdRcr7YXo0yJUbUw_Q8,2204 social/backends/qq.py,sha256=i29Jrqhvp-moF99q4UUipCu4KdG4fdf3maG8RJefjrQ,2198 social/backends/rdio.py,sha256=nYMAetHTUMwLqyJLl0QMpZdqDbSpIAyLh4t2VLrsAYA,2468 social/backends/readability.py,sha256=ntAU_gMa6ZNA3PoP5bLCo_Jum2VbWv5XO186EHQnDh4,1351 social/backends/reddit.py,sha256=IcOEQE_-s4BNSYLQMvM7la-MZk8Q6IOqVKdWNh62Ulo,1770 social/backends/runkeeper.py,sha256=EBci_-Md0_CCwrpmi2UR20hMV5UuZGCbkNUgT8c13zc,1807 social/backends/salesforce.py,sha256=q1XtLsecfMtocIVuE91j7kF-wPyfGYmpqm-udJiXaBU,1838 social/backends/saml.py,sha256=gGgp-EwpZhtyOdPoKbjTnoyXZ_bjwbrrzbp12xRZluI,12436 social/backends/shopify.py,sha256=BLO_Oo1SljQktA6wa6v9dGmFGmdxouLtENgPlJFv49Q,3380 social/backends/skyrock.py,sha256=dnkL6Qz9zE7xLKXXX_Im-ATu_OikoxawjPN-Uly5Yyo,1195 social/backends/slack.py,sha256=QvGVwhOjY6949lVG8Gg-J3KDSolah5MknoJvdQnEj1E,2414 social/backends/soundcloud.py,sha256=8gip0gouck1yWNAuWXljyA-O03to6QXwI4xtYgoy_M4,2156 social/backends/spotify.py,sha256=uRYmwDiL3IEHl-z8f5dbb6vBaI182jaP23bie4J7b-0,1518 social/backends/stackoverflow.py,sha256=NxuPM1j3kBxSrUbVtnTLjTswj21fxFgSf6ky1quxlvg,1456 social/backends/steam.py,sha256=SHzo6BsSZEirPlzLTcW2XZ9SXextsIjmzbuwre0uY8c,1553 social/backends/stocktwits.py,sha256=m4eON92NaGj3B1zjCGybpHCliu9gUuIq7Gn_DCxi4Lo,1357 social/backends/strava.py,sha256=rzPred9hk1ryZDHJRPNI0R2A0OUrAPH8Xg6_-h4cePQ,1850 social/backends/stripe.py,sha256=PmM79Pxjeo3m2ytfyS7CGS5PrHRUqCqwjgOZhO40Heg,1901 social/backends/suse.py,sha256=2nbB5yJBpoJ3yQqfq2pDs2n0Bt2NshkcbdVUVkXanqQ,459 social/backends/taobao.py,sha256=qai7_JgEQXecVh_1lF2pRWtOqF0E4U0wpTSlZsdZHbI,892 social/backends/thisismyjam.py,sha256=DHHys63ntWVUScIS3Y_ijOgs1Slk3wz-yzNJzXA99Ng,1219 social/backends/trello.py,sha256=nUapQgAC9FOgCD_n1ST7TUNf_xZOTZed_oetttec1mk,1513 social/backends/tripit.py,sha256=f6NldTCWsXYaMUXtk7DhDLIx3gf32KjDO5csc2jSC28,1776 social/backends/tumblr.py,sha256=0pxFuhURC_fYZJ8x9C86bnBolGCE4ESV1aAHi_6V7eA,1108 social/backends/twilio.py,sha256=Dzq4Nbw25_62JU4BAPWBi4tgxU3HBKiXaOsVkr11DmA,1384 social/backends/twitch.py,sha256=MGEQdIw8-7kDysS-oiNIlWkrusteaSs9uVCrt24T8qI,910 social/backends/twitter.py,sha256=Z6eRuGrc-FE6Ke-8pqhVmO7tQl8eDcmXN8LUN4EBcFk,1452 social/backends/uber.py,sha256=x771DazPVXf9QumovQ1jBaOQKbqgqxnP4Xf1f5F8dCw,1390 social/backends/ubuntu.py,sha256=tJ8dO6WWWewB3ZW0MZtyWgjcvuIyLR1snp4aJMk8Bkw,385 social/backends/username.py,sha256=jS6nCupp5qaNTt0LlQjt7NVd6-EJGvJhVklke1xM-3k,259 social/backends/utils.py,sha256=x22pygVyMfRa53TvsuVb7VFbTRqG_tCpDvzQbQXNROs,3142 social/backends/vend.py,sha256=vH3qc7uqZObWwjWlyK1uBBKw1ER086trocGKuJc8OJA,1242 social/backends/vimeo.py,sha256=0oy_SAHanR5B7VnNpaPSSFUVGi7a_6SBBtBKBr99Cbk,2820 social/backends/vk.py,sha256=QbdR4Nvhd_-Gkr3VGYc_bK-eDqMVOFb9AGFTJsOSt8w,7579 social/backends/weibo.py,sha256=sRYlc0AwgTqE3RsFEeMBRw3v95zrBho6-psj93bbLeg,2195 social/backends/weixin.py,sha256=hLq0s4y_NhtleUFNTdNOfJt0YmyPnwgEjBLs0vTv8mw,3533 social/backends/withings.py,sha256=sQiig-QMFwtXGHVMLVAlftln0KO8qQoh92ZaCjwhaps,533 social/backends/wunderlist.py,sha256=NCG3eFlZ1FttA0iluJqP2t8RCkVw-kvkw7w078BrL94,1097 social/backends/xing.py,sha256=oKP3nIFaifJxhmx01vVsRjHF6AO0iIvSoHECbZMC_fg,1519 social/backends/yahoo.py,sha256=7MRnZ4vgSxHEXtALnUv1iMI9KmG74oRZGfxWEdIeGP4,6000 social/backends/yammer.py,sha256=8rPfDFhFrH0YOvUPrkhRUITNkktE5SBKHunSpjEX5jo,1536 social/backends/yandex.py,sha256=9J4U8o2AztDbd3TyX-nazOqoInGeDUHcmzCBtRCDEUs,3022 social/backends/zotero.py,sha256=k2Kfj9GYh0axOWr-t72ap5xx2u0_RtXIe0BWpJwPZTI,941 social/pipeline/__init__.py,sha256=dTCj5Whr465Vqv9XGcs7R312VKGDOicxJot42C5oRnc,2386 social/pipeline/debug.py,sha256=xPJkSuvckkzbEh87RA6exqJc-YcQWxFdR5s29_fzivM,252 social/pipeline/disconnect.py,sha256=uaL6TfHLhI7f_IYUI8NLjhOkHNzzG8wTs6ENFo8rdls,1082 social/pipeline/mail.py,sha256=MzFs2Xo87LVpmWN7RfSkQ4iGcPHDo8Td9R_fC2xYQRU,1163 social/pipeline/partial.py,sha256=yWbm21Rd-r0Z37pFT5aNtpXlNm3_dA_-6clquEPelLU,823 social/pipeline/social_auth.py,sha256=JcIks9SlmJ6Mvp8ty3_OowxMD_p0cZ8-RZghQM6vcKs,3325 social/pipeline/user.py,sha256=UHJg6XzW0HCjkd2zSnvDheQhTWHhETuyWkWC-rBzkrQ,3494 social/pipeline/utils.py,sha256=3Vlv5IiijRBG8kcGqYvGqF7kq-W_AtMy8MqtibQPBtc,2057 social/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/storage/base.py,sha256=MZMK31kmNqGIn014Xce1arFQEnMZktD_sGPd__hRM8k,7948 social/storage/django_orm.py,sha256=RTsISlg7UPH4kXHnHCwMQQ8QxYhATXP-nl2TulT8dwE,4747 social/storage/mongoengine_orm.py,sha256=mlbsQvE_9IA-sYRKF4LzsLzHl0BvUkACTC1oQkYJzLg,5914 social/storage/sqlalchemy_orm.py,sha256=El4WUniKIIddyO7CM3qRnTmqU6fTgp_mWk1sP0uE71U,7117 social/strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/strategies/base.py,sha256=Kc9XdpuS3W4ubHSPf7XmCZuY1tGqpOfPb1KjtpysNzI,7544 social/strategies/cherrypy_strategy.py,sha256=9009Ga0hZ6HMA-SKSsz230bDAeXfJm8AGkuiRN4hXt8,1924 social/strategies/django_strategy.py,sha256=kqjsy0JkeOG4jv4T-1E1g5gbAw6w9A_GlYKGE9WKyMw,5052 social/strategies/flask_strategy.py,sha256=LggTRqn4HM6h9GMYg5TQ6_5KpaAaBqDxbUmVkFcKb4o,1646 social/strategies/pyramid_strategy.py,sha256=KrCzeW-qodmG9qVDawrw83IUxHNOcEyVcFTvvt9nXXE,2731 social/strategies/tornado_strategy.py,sha256=69RriD3dGIevA6EzgP2pPesh4p-x2Tyh5Hae2maskY8,2469 social/strategies/utils.py,sha256=O2Fd0FNYIX7A4Xsm4JhPoGdGp_jXST_usUXmz29BIQo,892 social/strategies/webpy_strategy.py,sha256=UtJkEq1EG4TqNk2hWFIBBL9F61GYZasBkG0-g0T87j8,1932 social/tests/__init__.py,sha256=1R4HZS6wLNx_PH2ovvJ64YklA8mnNQQVtBF2DSTaDxM,268 social/tests/models.py,sha256=MP6pQlBu8YF9VNh1jlqWtUPOvMyq-08aaRZtF-0yl2o,5480 social/tests/pipeline.py,sha256=EyJbMv7Gauzl5ibBsgiCZIRHw1b2lLh_2rTXYmNNaQw,1194 social/tests/requirements-pypy.txt,sha256=Umip_SxSNKIzDcSZ6-Xbrwk80WqcRbo-ojCVuQp9fh8,123 social/tests/requirements-python3.txt,sha256=FnJNdehjvSprohYk4N2RybM2_n9dU2IsgiWRYiG6dMM,127 social/tests/requirements.txt,sha256=pYi3BeMy895Yqk7cRm53IyGXCaCJ-RDEjEhzkLxDO3Q,142 social/tests/strategy.py,sha256=-8k2Cy_rBW5E5L5DZpu6pQ7ryvKETpVWfJ2gEec9pKk,3641 social/tests/test_exceptions.py,sha256=oU2UPSD7Nahlki-hvMM7cfHpxNcdw3qjDMtNPUEjEko,2904 social/tests/test_pipeline.py,sha256=h8wc-dzLoK18DlEdBcNoTxBb-_QojwSXn8TYCBcAIOQ,7529 social/tests/test_storage.py,sha256=8Ihbnj7cmdPYL_A_5pLD0GsoFA7c9OBQBFMc2wknetI,6433 social/tests/test_utils.py,sha256=qUSRUJ5o_VYUInCs1B6HoiO2WvO9ekEH57K0iyrK44g,5018 social/tests/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/tests/actions/actions.py,sha256=flZHIgsvsJMfLFIc_kgE2EnXM3W6M-Ciy1E3cVs5_kE,8535 social/tests/actions/test_associate.py,sha256=7GSGVq1Vf3KNiDf14d_ywJp_cHUEee5N7KxECw1-H5E,2932 social/tests/actions/test_disconnect.py,sha256=gGh1TxD73OZsFi5LTakkUqarMuODbKlcoxZU9C1QzU4,2656 social/tests/actions/test_login.py,sha256=T3zZq9bUMwzWhSSvR8LSKEwO3DhWNfqB6L0cORLWqq0,2639 social/tests/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 social/tests/backends/base.py,sha256=UcpZ-oZDzuT20LhiNnUNQxWmUJlIz6KPYk0UqrOFu54,6160 social/tests/backends/legacy.py,sha256=FDwp03jImKLATivvhH1sILiPBkKnr1jHBKs3mpxaLYA,1506 social/tests/backends/oauth.py,sha256=3p_EM1TyLfwXOiw4GP9T7t6V1WaUNFIdtRaz0jYDXWc,4538 social/tests/backends/open_id.py,sha256=Ou_n1K7Z7ZvS04CN75Dd6BA6TwjpUsToEwzKIJ4aYA8,8219 social/tests/backends/test_amazon.py,sha256=Lf17cnuvtpt-yt7BCC4M6ILu9-5aCCIudDJ-SS_1kZQ,1317 social/tests/backends/test_angel.py,sha256=3QtjVN9DDz2Xl68ZF99SDI1CJx1pLZ_rIn3F5MZ0y4M,1105 social/tests/backends/test_azuread.py,sha256=E9sIB7p-ssOA9eRrtDId8Z_AM2rvdK5DP7CNtWd1SU4,3180 social/tests/backends/test_behance.py,sha256=qkd_UrOcuArWuMexk5xDcKfd2LAB_QrMw0OyMslVgrQ,1700 social/tests/backends/test_bitbucket.py,sha256=DshscwY44KdAMp8fY4KMKbkaSlZEkeS64_4X7i2H20k,5713 social/tests/backends/test_box.py,sha256=XCt_s29_aQopR5jyO4G-RJ-YGpWl_qdm7Ld9lFp4IO8,2294 social/tests/backends/test_broken.py,sha256=6-8nDVzeooKvkzh6HVDoimK5LO8SDiM5kGW8fUQEIGM,1072 social/tests/backends/test_clef.py,sha256=Qz43s5wlGqT0GTYi3CYo1yI3XkbrSBTEE3ACPVvpsoI,642 social/tests/backends/test_coinbase.py,sha256=W5m0SIQCkIRF1u0S46Oa14jAkjtUMCgfVjp1lnwIXE4,1459 social/tests/backends/test_coursera.py,sha256=i1oxKbowTyGrdeynOG0nosIpq87w3ln3zOt8ihAbkeM,1123 social/tests/backends/test_dailymotion.py,sha256=n_nznTytCXLJfF7DJ3cakX4hpMiMeHYt2Dscm569GzQ,590 social/tests/backends/test_digitalocean.py,sha256=WxyVABr8kQgp-Fv9bI1flPanvIoi-42na_f8--WlvVU,993 social/tests/backends/test_disqus.py,sha256=Apt0nAlpKgPlHjdg3cZxMz4qkniFRFfOWp8VCS-mm_8,2166 social/tests/backends/test_dribbble.py,sha256=RoduiFt6fhq04a3SqYe9AQQafaljpUrRvvJDzTuhYpE,609 social/tests/backends/test_dropbox.py,sha256=XD6WCsKNRg0We4qMHKexTy3n6ZRzXodijm_mQQhFpqk,1042 social/tests/backends/test_dummy.py,sha256=yx1Slb0dIbAvjxJsWaFzrCNBHswKte-NnevjgR8-d5g,3932 social/tests/backends/test_email.py,sha256=iPEBuFjOzFn5xWyBG0OlWv3aDz3gRkB0Vr5rD2u7ECQ,484 social/tests/backends/test_evernote.py,sha256=wT456omV_svWpymkVbvS0G3lSkpl3s1rBwIRJp90MUg,1525 social/tests/backends/test_facebook.py,sha256=xhyTFoPev7eBm-VRPtZ4QcAfAsgJ4S_hLj9bYA5gNG0,1198 social/tests/backends/test_fitbit.py,sha256=PkbED9_uU9w__MuS-NH5nWwkvkCwqe4nbewm6gC06oA,1556 social/tests/backends/test_flickr.py,sha256=jjmFo9-3lVkVSm9Fu1pciekK-MtLwfA5e-xRzx9cyiE,685 social/tests/backends/test_foursquare.py,sha256=HTGBKLZK1qActVTu-4nUoE2IW44iaY0adWJBC4JzPqc,4108 social/tests/backends/test_github.py,sha256=ioLQtmcMSCZe5esqaY31LsyQiBXuJo92PBOtI3ZMYvg,6442 social/tests/backends/test_github_enterprise.py,sha256=JXBcUzz0vp4Ijcu5_nnKmVI6bTVOIuWQVYZgJSbfSdU,10066 social/tests/backends/test_google.py,sha256=Vqgi2Cr_kLfpV7DglCBlbIC_47Xee62IPcShTi4pXXo,10397 social/tests/backends/test_instagram.py,sha256=s4o--0wggfm9VtI995PMBabItcmgepn_Zs9o3hbi-_c,1787 social/tests/backends/test_kakao.py,sha256=IwwAbvDZEkShdyk4kzISK1_corVUdc-NSLJ-Sqs7CFk,863 social/tests/backends/test_khanacademy.py,sha256=5FgFmJbgdnKCb-1lwY6fK32EtkiAq4q1EA885of5EN0,853 social/tests/backends/test_linkedin.py,sha256=0KFbWHByVi4fF7GN2IA3OCh6lyaJAboEMaEGUnDhEJE,1050 social/tests/backends/test_live.py,sha256=sjtFCLR-HgsuXhOKiib9uQbIFAEK50f5RjpqXf-LdZ0,953 social/tests/backends/test_livejournal.py,sha256=Tmef7eLHiaupTLBnk596tA4KQU-Llwyk_voUgLNxMBU,3705 social/tests/backends/test_mapmyfitness.py,sha256=J3aMnOUpZIJww2iC9ycn18jBo407j3Qf9vDDMpRB0hQ,4907 social/tests/backends/test_mineid.py,sha256=3n7UokmFRQNiJHRCgq3J6-B3IyebgFktO6KMq77t4L0,590 social/tests/backends/test_mixcloud.py,sha256=0zWlLc_PcD3WXMCUF3-AvLtIfnt3PbZiRcwh7EiLEw4,2434 social/tests/backends/test_nationbuilder.py,sha256=AFSbyzhnWLS4-kmsHF8zS2Mmgd4caHeZ74plN1_ug14,8489 social/tests/backends/test_naver.py,sha256=b4k82v8pDD0v-3HogQerPpVouQhB1X4AKQ9GwNmteTQ,1286 social/tests/backends/test_orbi.py,sha256=9lMYviugnW4iK0nTUSVcraKmAF6js3THD7PxjUWKIxo,786 social/tests/backends/test_podio.py,sha256=gBtSmjfF6tGsXG3S7KHIZUzazoylzohQezLZmpgsqjw,1689 social/tests/backends/test_qiita.py,sha256=auyai7XvZhubh2bjQZtQxuYmg8II1MOXEoA-wPNwttA,574 social/tests/backends/test_readability.py,sha256=G_TXQHprKWmx0bPQIByx-FR_SpwuS-DmA_8wTigws_s,1314 social/tests/backends/test_reddit.py,sha256=0iM0MDn1Qy6vxcXoeapPisMU70x5U_BajlZqHhhSerk,1754 social/tests/backends/test_saml.py,sha256=68b5GcA83c2KxmT79OiPbrtafVis7DHPtKOaa8x0a_0,4569 social/tests/backends/test_skyrock.py,sha256=Y0z9meHw9l7CgbVsvzHTcsrWh70MK7t3J8NysvVBt0k,1343 social/tests/backends/test_soundcloud.py,sha256=-_Bz3-ssYD6_4T_KYiiq-G8GlAILu87SjW_skS8b30A,1631 social/tests/backends/test_stackoverflow.py,sha256=8GVVDudQwJwZcCP-4hONELd8P_PUZzFNAbytZ8pOA5o,1677 social/tests/backends/test_steam.py,sha256=E3Q-ENSg1krAG6-qrS_15nSzlLmrwnj3Zd5lsZ6sU7k,5412 social/tests/backends/test_stocktwits.py,sha256=OPW6n_GiKgebIJS4kqBvWj4gS7vkw6Ed1JZI3t7Hcjg,1658 social/tests/backends/test_strava.py,sha256=kkXDVxbynxNokNF8bDMELCuG-OaCXafXuv0NsDHAyGA,1882 social/tests/backends/test_stripe.py,sha256=qEr14ScLBWPd_hXPPzhYNAD8xfMQsL6AmUt2rpynrpg,618 social/tests/backends/test_taobao.py,sha256=7WfNZCnlxX-J-_vqmDWmrgI4nWP5tegVzmpUzzeEqMg,764 social/tests/backends/test_thisismyjam.py,sha256=QpRNO7VaEUid-Ltn-AJtboyQpGDPQ5YbOJTl-uLFqmo,862 social/tests/backends/test_tripit.py,sha256=wTar1EynAA03wmoIiOLX1YpWZ8PseLogRFxtGP6u5zs,4218 social/tests/backends/test_tumblr.py,sha256=i1s0gbOMVHfpkDLq0UeYQMuOQpHnF_95mYnec9kKZt0,1743 social/tests/backends/test_twitch.py,sha256=ydkvjLSFTm-rdYUHenJeImU3kckyXawwywH0fdLEmrU,1050 social/tests/backends/test_twitter.py,sha256=1LWF1aol086p1afiznRdC0-VDjp1VEBTCfLEcOi1lRc,4679 social/tests/backends/test_uber.py,sha256=Yp32V04nTZ33msJuc-EoKg4dVn77aq4atfdDIeWSz4M,1003 social/tests/backends/test_username.py,sha256=WNF2X8tcV7N8Y5Fd0WYvp6rAh8Vk6oAb7PFXJs5P05I,497 social/tests/backends/test_utils.py,sha256=RTAYM3p6tsaXpbNCu54Haj7gng4rc45ZmXCc9j5gXzM,1776 social/tests/backends/test_vk.py,sha256=Edc9yPWgiGbBKZ-k3_Yxk7VrApn8qM65TxQFStLcgkk,847 social/tests/backends/test_wunderlist.py,sha256=sOwYd1F8HyZnj5t_iZG2oA9EaJCiiWPSlC0lwHoaHOA,757 social/tests/backends/test_xing.py,sha256=jPX_qlt0QZMOE-zyjVbtk1-F3V0_vXslPHF_bo6-eGc,6199 social/tests/backends/test_yahoo.py,sha256=nif4KJuXbWw9eZUT5sPmlw4jV6_pKbnQXi5x2_pvYq0,2535 social/tests/backends/test_yammer.py,sha256=eYbU2PRbdj3ijwwLKFS7nOWIvNiUH85vWPpvps5mWl4,4006 social/tests/backends/test_yandex.py,sha256=-24w2HCLO_OMTPlqqPMOsDf-mFgMELr-BQTlbFpYmJc,717 social/tests/backends/test_zotero.py,sha256=SZsm96tMMPkMQUh4xNMGwxrw_atPabEUGFKp_7CLMTs,699 social/tests/backends/data/saml_response.txt,sha256=K2pUL1chyWM1ICWhjsks1GRrC8YaVnPcD29FAmfkmw8,17495 PKAmG2ZFFsocial/utils.pyPKAmG5wqssocial/actions.pyPKVz9HUV/social/__init__.pyPKAmG*WW Z0social/p3.pyPK%D_] 2social/store.pyPKAmG7 G G =social/exceptions.pyPK$D9Jsocial/apps/__init__.pyPKAmG(nc!nJsocial/apps/tornado_app/routes.pyPKAmG   Lsocial/apps/tornado_app/utils.pyPK$D#Rsocial/apps/tornado_app/__init__.pyPKAmGԛ!Ssocial/apps/tornado_app/models.pyPKAmGz?#m[social/apps/tornado_app/handlers.pyPK$DӴ,_`social/apps/django_app/context_processors.pyPKAmGopb b fsocial/apps/django_app/utils.pyPKAmG:>$Lpsocial/apps/django_app/middleware.pyPK.HXX{ysocial/apps/django_app/urls.pyPKAmGjyy"}social/apps/django_app/__init__.pyPKAmGƥpȀsocial/apps/django_app/views.pyPK$Dep p social/apps/django_app/tests.pyPKAmG Y&#7social/apps/django_app/me/config.pyPKAmGrJ%social/apps/django_app/me/__init__.pyPKAmG уLL#social/apps/django_app/me/models.pyPK$Dr++"ssocial/apps/django_app/me/tests.pyPKAmGt  (ޟsocial/apps/django_app/default/config.pyPK$D< (Dsocial/apps/django_app/default/fields.pyPKAmGl""*[social/apps/django_app/default/__init__.pyPKAmGOyH(ŭsocial/apps/django_app/default/models.pyPKAmGzz-m*social/apps/django_app/default/managers.pyPKAmG6aϷ'isocial/apps/django_app/default/admin.pyPK$Dr++'esocial/apps/django_app/default/tests.pyPKAmGdS1zz;social/apps/django_app/default/south_migrations/__init__.pyPKAmGF**?social/apps/django_app/default/south_migrations/0001_initial.pyPKAmG`Bsocial/apps/django_app/default/migrations/0002_add_related_name.pyPKAmG>5ۏH7social/apps/django_app/default/migrations/0003_alter_email_max_length.pyPKAmG5,social/apps/django_app/default/migrations/__init__.pyPKAmG#9social/apps/django_app/default/migrations/0001_initial.pyPKAmGQƕ! social/apps/cherrypy_app/utils.pyPK$D$social/apps/cherrypy_app/__init__.pyPKAmGu]"social/apps/cherrypy_app/models.pyPKAmGtzF!'social/apps/cherrypy_app/views.pyPKAmGwp9NNksocial/apps/flask_app/routes.pyPKAmG9ّ[[$social/apps/flask_app/utils.pyPKv DGNN)+social/apps/flask_app/template_filters.pyPK$DO+Q!"/social/apps/flask_app/__init__.pyPKAmG$0social/apps/flask_app/me/__init__.pyPKAmGPhh"F0social/apps/flask_app/me/models.pyPKAmG)5social/apps/flask_app/default/__init__.pyPKAmG`A A '56social/apps/flask_app/default/models.pyPKAmGzii?social/apps/webpy_app/app.pyPKAmGGUu^^^Hsocial/apps/webpy_app/utils.pyPK%D&!Psocial/apps/webpy_app/__init__.pyPKAmGLy]Qsocial/apps/webpy_app/models.pyPKAmG:" Ysocial/apps/pyramid_app/utils.pyPK}sFz#bsocial/apps/pyramid_app/__init__.pyPKAmG2GG!esocial/apps/pyramid_app/models.pyPKAmGfCC msocial/apps/pyramid_app/views.pyPKAmGN((rsocial/backends/coursera.pyPKAmGۯzwsocial/backends/khanacademy.pyPKRGxuW esocial/backends/odnoklassniki.pyPK%DS^social/backends/legacy.pyPKAmGp;social/backends/disqus.pyPKAmGԗδsocial/backends/azuread.pyPKAmG[ƶvsocial/backends/spotify.pyPKAmGt*social/backends/fitbit.pyPKAmG@i#social/backends/meetup.pyPKAmGsocial/backends/goclioeu.pyPK%Dֻ/social/backends/appsfuel.pyPK%D6bsocial/backends/coinbase.pyPK%DP||%social/backends/taobao.pyPKAmGsocial/backends/mapmyfitness.pyPKAmGa social/backends/eveonline.pyPKAmG)n n social/backends/slack.pyPKAmGIKsOOmsocial/backends/docker.pyPK%Dsocial/backends/suse.pyPKAmG social/backends/mineid.pyPK%D social/backends/thisismyjam.pyPKAmG9social/backends/vend.pyPKG+!social/backends/qq.pyPK.wGm 00social/backends/saml.pyPKAmGZ8vvMsocial/backends/changetip.pyPK%DZ/ cQsocial/backends/rdio.pyPKAmG<[social/backends/beats.pyPK%DKdsocial/backends/mixcloud.pyPKAmGS/uss Ehsocial/backends/professionali.pyPKAmG55osocial/backends/persona.pyPKAmGKRR cwsocial/backends/nationbuilder.pyPK%DϗGG}social/backends/readability.pyPKz4F@qhhvsocial/backends/twilio.pyPKAmGl social/backends/launchpad.pyPKAmG}=F F "social/backends/utils.pyPKAmGHAsocial/backends/zotero.pyPK.wG_U2@@social/backends/oauth.pyPK%D9I44social/backends/douban.pyPK%DMsocial/backends/skyrock.pyPK%DͯVsocial/backends/box.pyPK%D1Psocial/backends/aol.pyPKAmG5jjsocial/backends/github.pyPKAmGCrsocial/backends/withings.pyPKAmGҶsocial/backends/goclio.pyPKAmG)qj4 4  social/backends/shopify.pyPKAmG8NBsocial/backends/live.pyPKAmG8=, social/backends/jawbone.pyPKAmG+::(social/backends/strava.pyPKRG诛'0social/backends/vk.pyPKAmG4Msocial/backends/dribbble.pyPKWEuQ Q Vsocial/backends/dropbox.pyPK%DBa`social/backends/podio.pyPKAmGZ__uesocial/backends/bitbucket.pyPK%Dtsocial/backends/__init__.pyPKAmGSMMGtsocial/backends/pushbullet.pyPK%Dh0awsocial/backends/twitch.pyPKAmG/*II{social/backends/wunderlist.pyPKRG|xT social/backends/evernote.pyPKAmGgLppsocial/backends/yahoo.pyPKAmGgOiisocial/backends/justgiving.pyPK%DJll,social/backends/soundcloud.pyPK%Dz>wwӳsocial/backends/dailymotion.pyPKAmGx~Ksocial/backends/pocket.pyPKAmGRsocial/backends/mendeley.pyPK%D {}social/backends/fedora.pyPKAmG76social/backends/moves.pyPKAmGCgsocial/backends/trello.pyPKAmGS social/backends/flickr.pyPKAmGsocial/backends/orbi.pyPK%D``Ssocial/backends/foursquare.pyPKAmGnx--social/backends/behance.pyPKAmG22$Ssocial/backends/github_enterprise.pyPK%DbOsocial/backends/pixelpin.pyPKAmGsocial/backends/qiita.pyPK%D   {social/backends/vimeo.pyPKAmG :$$social/backends/battlenet.pyPKAmGI!]))social/backends/angel.pyPK%DͯZe rsocial/backends/yandex.pyPKAmG2 w social/backends/weixin.pyPKAmG^R{.social/backends/weibo.pyPK%Dr3D7social/backends/tripit.pyPKAmG(k>social/backends/linkedin.pyPKAmGldMsocial/backends/digitalocean.pyPK%D>J9 9 ASsocial/backends/loginradius.pyPK%D 1Y]social/backends/livejournal.pyPKAmG D``asocial/backends/lastfm.pyPKRGP&&hisocial/backends/base.pyPKAmG*[[social/backends/echosign.pyPK%DxZX Lsocial/backends/runkeeper.pyPK%Dasocial/backends/ubuntu.pyPK%D-+SSMsocial/backends/belgiumeid.pyPKAmG08777۝social/backends/open_id.pyPKAmGq social/backends/stackoverflow.pyPKlGW8social/backends/instagram.pyPKAmGEknn2social/backends/uber.pyPK%DMMsocial/backends/stocktwits.pyPKAmG)͌  ]social/backends/amazon.pyPK%DaTy1social/backends/steam.pyPK%DhQfTTsocial/backends/tumblr.pyPKAmGsT..social/backends/salesforce.pyPK%D social/backends/openstreetmap.pyPK%D7social/backends/email.pyPKAmGXp social/backends/nk.pyPKAmG@ֳsocial/backends/exacttarget.pyPK%DA7J+social/backends/mailru.pyPK%D7`2social/backends/yammer.pyPKAmG5V8social/backends/reddit.pyPK%Dpx@mm?social/backends/stripe.pyPKAmGR>Gsocial/backends/clef.pyPKaGJNsocial/backends/naver.pyPKAmGJ-uooVsocial/backends/google.pyPK%Dg]hpvsocial/backends/xing.pyPK4Fľ  |social/backends/gae.pyPK%DxOԁsocial/backends/username.pyPKAmGVsocial/backends/kakao.pyPKAmGHrHKsocial/backends/twitter.pyPKAmGQ Q /social/backends/facebook.pyPKDF:!social/storage/mongoengine_orm.pyPK%Dsocial/storage/__init__.pyPKAmG֋Jsocial/storage/django_orm.pyPKAmGxW social/storage/sqlalchemy_orm.pyPKAmG9}  social/storage/base.pyPKAmGT+ %Zsocial/strategies/pyramid_strategy.pyPKAmG#ݤnn#Hsocial/strategies/flask_strategy.pyPKAmG&||%social/strategies/utils.pyPKAmG0ۢ&)social/strategies/cherrypy_strategy.pyPK%Ds1social/strategies/__init__.pyPKAmGyޥ %1social/strategies/tornado_strategy.pyPKAmGxx;social/strategies/base.pyPKAmG=4$EYsocial/strategies/django_strategy.pyPKAmG](#Cmsocial/strategies/webpy_strategy.pyPKAmGLusocial/tests/pipeline.pyPKAmG99ysocial/tests/strategy.pyPKAmGH!!_social/tests/test_storage.pyPKAmG2cHsocial/tests/test_utils.pyPKAmGd%social/tests/requirements-python3.txtPKAmG#  Nsocial/tests/__init__.pyPKAmGŦ(hhsocial/tests/models.pyPKAmGAii,social/tests/test_pipeline.pyPKAmGVsocial/tests/requirements.txtPKAmG8{{"social/tests/requirements-pypy.txtPKAmGf$social/tests/backends/test_twitch.pyPKAmG0$social/tests/backends/test_reddit.pyPK%D3vv$-social/tests/backends/test_disqus.pyPKAmG38!social/tests/backends/test_box.pyPK%DfT  (social/tests/backends/test_foursquare.pyPK%DtO%lsocial/tests/backends/test_behance.pyPKAmG__#Ssocial/tests/backends/test_kakao.pyPKAmGCl#social/tests/backends/test_utils.pyPK%D%%%$$social/tests/backends/test_amazon.pyPKAmGN[`)!)!+social/tests/backends/test_nationbuilder.pyPKAmGz&#_#social/tests/backends/test_podio.pyPK%D nXzz$social/tests/backends/test_tripit.pyPK%DQQ#social/tests/backends/test_angel.pyPKAmG(t`(($%social/tests/backends/test_google.pyPKAmG]}ܺ=social/tests/backends/oauth.pyPKAmGq\\#Nsocial/tests/backends/test_dummy.pyPK%DF3ZZ$^social/tests/backends/test_strava.pyPK%Db&3fsocial/tests/backends/test_coinbase.pyPKAmGU1Ƒcc&*lsocial/tests/backends/test_coursera.pyPK%Dbn"")psocial/tests/backends/test_readability.pyPK%DcNN):vsocial/tests/backends/test_dailymotion.pyPKDφO'xsocial/tests/backends/test_instagram.pyPK%D$social/tests/backends/test_tumblr.pyPK%D.??% social/tests/backends/test_skyrock.pyPK%D:F#social/tests/backends/test_email.pyPKAmGI룂"ǎsocial/tests/backends/test_clef.pyPKAmGHOv"social/tests/backends/test_saml.pyPKEUGG%social/tests/backends/test_twitter.pyPKAmG8ۉ++*,social/tests/backends/test_mapmyfitness.pyPK%D!social/tests/backends/__init__.pyPK%D}%social/tests/backends/test_dropbox.pyPKAmG>>#3social/tests/backends/test_qiita.pyPKAmGnJ&social/tests/backends/test_evernote.pyPK%DR0$social/tests/backends/test_yammer.pyPK%D' /__(social/tests/backends/test_soundcloud.pyPK%D3 +xsocial/tests/backends/test_stackoverflow.pyPKAmGU "Nsocial/tests/backends/test_uber.pyPKAmGaLUU)ysocial/tests/backends/test_khanacademy.pyPKAmG:(social/tests/backends/test_wunderlist.pyPKAmGÅrQQ'Psocial/tests/backends/test_bitbucket.pyPKAmG^NN$ social/tests/backends/test_mineid.pyPKAmG1gv social/tests/backends/base.pyPKAmGFϭl l %0 social/tests/backends/test_azuread.pyPK%D$y~$p= social/tests/backends/test_yandex.pyPKAmGR"@ social/tests/backends/test_orbi.pyPKAmGg[  C social/tests/backends/open_id.pyPK%D^g5^^)*d social/tests/backends/test_thisismyjam.pyPKAmG'7100$g social/tests/backends/test_broken.pyPKAmG f|&Al social/tests/backends/test_facebook.pyPKAmG**$3q social/tests/backends/test_github.pyPKaG)av.# social/tests/backends/test_naver.pyPK%DLL$ social/tests/backends/test_flickr.pyPK%Djj$Ւ social/tests/backends/test_stripe.pyPKAmG#* social/tests/backends/test_digitalocean.pyPK%D|& social/tests/backends/test_username.pyPK%Dm$ߛ social/tests/backends/test_fitbit.pyPKAmGlWDWD,5 social/tests/backends/data/saml_response.txtPKAmGߖO O " social/tests/actions/test_login.pyPKAmGIVt t &e social/tests/actions/test_associate.pyPKAmGF#)` ` ' social/tests/actions/test_disconnect.pyPKAmGd`)W!W! social/tests/actions/actions.pyPK%D V) social/tests/actions/__init__.pyPKAmG?_) social/pipeline/debug.pyPKAmGw  * social/pipeline/utils.pyPKAmG"ϴ 3 social/pipeline/user.pyPK%D2R::@ social/pipeline/disconnect.pyPKAmG9R R UE social/pipeline/__init__.pyPKAmGAN social/pipeline/mail.pyPK9$FB0;77S social/pipeline/partial.pyPKAmGz1 W social/pipeline/social_auth.pyPKz9H,''3Hd python_social_auth-0.2.14.dist-info/DESCRIPTION.rstPKz9HLO  1 python_social_auth-0.2.14.dist-info/metadata.jsonPKz9HqzA1 python_social_auth-0.2.14.dist-info/top_level.txtPKz9H''\\)a python_social_auth-0.2.14.dist-info/WHEELPKz9HmD**, python_social_auth-0.2.14.dist-info/METADATAPKz9HΖ%cc* python_social_auth-0.2.14.dist-info/RECORDPKV!