Integrated my history into data

This commit is contained in:
Karthik Pullela
2018-01-21 10:46:18 -08:00
parent bce29a723f
commit 97d35e633c
917 changed files with 50890 additions and 11 deletions
@@ -0,0 +1,23 @@
__version__ = '2.1.0'
from social_core.backends.base import BaseAuth
# django.contrib.auth.load_backend() will import and instanciate the
# authentication backend ignoring the possibility that it might
# require more arguments. Here we set a monkey patch to
# BaseAuth.__init__ to ignore the mandatory strategy argument and load
# it.
def baseauth_init_workaround(original_init):
def fake_init(self, strategy=None, *args, **kwargs):
from .utils import load_strategy
original_init(self, strategy or load_strategy(), *args, **kwargs)
return fake_init
if not getattr(BaseAuth, '__init_patched', False):
BaseAuth.__init__ = baseauth_init_workaround(BaseAuth.__init__)
BaseAuth.__init_patched = True
default_app_config = 'social_django.config.PythonSocialAuthConfig'
@@ -0,0 +1,62 @@
"""Admin settings"""
from itertools import chain
from django.conf import settings
from django.contrib import admin
from social_core.utils import setting_name
from .models import UserSocialAuth, Nonce, Association
class UserSocialAuthOption(admin.ModelAdmin):
"""Social Auth user options"""
list_display = ('user', 'id', 'provider', 'uid')
list_filter = ('provider',)
raw_id_fields = ('user',)
list_select_related = True
def get_search_fields(self, request=None):
search_fields = getattr(
settings, setting_name('ADMIN_USER_SEARCH_FIELDS'), None
)
if search_fields is None:
_User = UserSocialAuth.user_model()
username = getattr(_User, 'USERNAME_FIELD', None) or \
hasattr(_User, 'username') and 'username' or \
None
fieldnames = ('first_name', 'last_name', 'email', username)
all_names = self._get_all_field_names(_User._meta)
search_fields = [name for name in fieldnames
if name and name in all_names]
return ['user__' + name for name in search_fields] + \
getattr(settings, setting_name('ADMIN_SEARCH_FIELDS'), [])
@staticmethod
def _get_all_field_names(model):
names = chain.from_iterable(
(field.name, field.attname)
if hasattr(field, 'attname') else (field.name,)
for field in model.get_fields()
# For complete backwards compatibility, you may want to exclude
# GenericForeignKey from the results.
if not (field.many_to_one and field.related_model is None)
)
return list(set(names))
class NonceOption(admin.ModelAdmin):
"""Nonce options"""
list_display = ('id', 'server_url', 'timestamp', 'salt')
search_fields = ('server_url',)
class AssociationOption(admin.ModelAdmin):
"""Association options"""
list_display = ('id', 'server_url', 'assoc_type')
list_filter = ('assoc_type',)
search_fields = ('server_url',)
admin.site.register(UserSocialAuth, UserSocialAuthOption)
admin.site.register(Nonce, NonceOption)
admin.site.register(Association, AssociationOption)
@@ -0,0 +1,34 @@
# coding=utf-8
import six
import django
from django.db import models
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
MiddlewareMixin = object
def get_rel_model(field):
if django.VERSION >= (2, 0):
return field.remote_field.model
user_model = field.rel.to
if isinstance(user_model, six.string_types):
app_label, model_name = user_model.split('.')
user_model = models.get_model(app_label, model_name)
return user_model
def get_request_port(request):
if django.VERSION >= (1, 9):
return request.get_port()
host_parts = request.get_host().partition(':')
return host_parts[2] or request.META['SERVER_PORT']
@@ -0,0 +1,10 @@
from django.apps import AppConfig
class PythonSocialAuthConfig(AppConfig):
# Full Python path to the application eg. 'django.contrib.admin'.
name = 'social_django'
# Last component of the Python path to the application eg. 'admin'.
label = 'social_django'
# Human-readable name for the application eg. "Admin".
verbose_name = 'Python Social Auth'
@@ -0,0 +1,52 @@
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.utils.functional import SimpleLazyObject
from django.utils.http import urlquote
try:
from django.utils.functional import empty as _empty
empty = _empty
except ImportError: # django < 1.4
empty = None
from social_core.backends.utils import user_backends_data
from .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_core.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)
if value:
value = urlquote(value)
querystring = REDIRECT_FIELD_NAME + '=' + value
else:
querystring = ''
return {
'REDIRECT_FIELD_NAME': REDIRECT_FIELD_NAME,
'REDIRECT_FIELD_VALUE': value,
'REDIRECT_QUERYSTRING': querystring
}
@@ -0,0 +1,94 @@
import json
import six
import functools
import django
from django.core.exceptions import ValidationError
from django.conf import settings
from django.db import models
from social_core.utils import setting_name
try:
from django.utils.encoding import smart_unicode as smart_text
smart_text # placate pyflakes
except ImportError:
from django.utils.encoding import smart_text
# SubfieldBase causes RemovedInDjango110Warning in 1.8 and 1.9, and
# will not work in 1.10 or later
if django.VERSION[:2] >= (1, 8):
field_metaclass = type
else:
from django.db.models import SubfieldBase
field_metaclass = SubfieldBase
field_class = functools.partial(six.with_metaclass, field_metaclass)
if getattr(settings, setting_name('POSTGRES_JSONFIELD'), False):
from django.contrib.postgres.fields import JSONField as JSONFieldBase
else:
JSONFieldBase = field_class(models.TextField)
class JSONField(JSONFieldBase):
"""Simple JSON field that stores python structures as JSON strings
on database.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('default', dict)
super(JSONField, self).__init__(*args, **kwargs)
def from_db_value(self, value, expression, connection, context):
return self.to_python(value)
def to_python(self, value):
"""
Convert the input JSON value into python structures, raises
django.core.exceptions.ValidationError if the data can't be converted.
"""
if self.blank and not value:
return {}
value = value or '{}'
if isinstance(value, six.binary_type):
value = six.text_type(value, 'utf-8')
if isinstance(value, six.string_types):
try:
# with django 1.6 i have '"{}"' as default value here
if value[0] == value[-1] == '"':
value = value[1:-1]
return json.loads(value)
except Exception as err:
raise ValidationError(str(err))
else:
return value
def validate(self, value, model_instance):
"""Check value is a valid JSON string, raise ValidationError on
error."""
if isinstance(value, six.string_types):
super(JSONField, self).validate(value, model_instance)
try:
json.loads(value)
except Exception as err:
raise ValidationError(str(err))
def get_prep_value(self, value):
"""Convert value to JSON string before save"""
try:
return json.dumps(value)
except Exception as err:
raise ValidationError(str(err))
def value_to_string(self, obj):
"""Return value from object converted to string properly"""
return smart_text(self.value_from_object(obj))
def value_from_object(self, obj):
"""Return value dumped to string."""
orig_val = super(JSONField, self).value_from_object(obj)
return self.get_prep_value(orig_val)
@@ -0,0 +1,15 @@
from django.db import models
class UserSocialAuthManager(models.Manager):
"""Manager for the UserSocialAuth django model."""
class Meta:
app_label = "social_django"
def get_social_auth(self, provider, uid):
try:
return self.select_related('user').get(provider=provider,
uid=uid)
except self.model.DoesNotExist:
return None
@@ -0,0 +1,60 @@
# -*- 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_core.exceptions import SocialAuthBaseException
from social_core.utils import social_logger
from .compat import MiddlewareMixin
class SocialAuthExceptionMiddleware(MiddlewareMixin):
"""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:
if url:
url += ('?' in url and '&' or '?') + \
'message={0}&backend={1}'.format(urlquote(message),
backend_name)
if url:
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') or 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')
@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
from social_core.utils import setting_name
from ..fields import JSONField
from ..storage import DjangoAssociationMixin, DjangoCodeMixin, \
DjangoNonceMixin, DjangoUserMixin
USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \
getattr(settings, 'AUTH_USER_MODEL', None) or \
'auth.User'
UID_LENGTH = getattr(settings, setting_name('UID_LENGTH'), 255)
NONCE_SERVER_URL_LENGTH = getattr(
settings, setting_name('NONCE_SERVER_URL_LENGTH'), 255
)
ASSOCIATION_SERVER_URL_LENGTH = getattr(
settings, setting_name('ASSOCIATION_SERVER_URL_LENGTH'), 255
)
ASSOCIATION_HANDLE_LENGTH = getattr(
settings, setting_name('ASSOCIATION_HANDLE_LENGTH'), 255
)
class Migration(migrations.Migration):
replaces = [
('default', '0001_initial'),
('social_auth', '0001_initial')
]
dependencies = [
migrations.swappable_dependency(USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Association',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('server_url',
models.CharField(max_length=ASSOCIATION_SERVER_URL_LENGTH)),
('handle',
models.CharField(max_length=ASSOCIATION_HANDLE_LENGTH)),
('secret', models.CharField(max_length=255)),
('issued', models.IntegerField()),
('lifetime', models.IntegerField()),
('assoc_type', models.CharField(max_length=64)),
],
options={
'db_table': 'social_auth_association',
},
bases=(
models.Model, 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, DjangoCodeMixin),
),
migrations.CreateModel(
name='Nonce',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True
)),
('server_url',
models.CharField(max_length=NONCE_SERVER_URL_LENGTH)),
('timestamp', models.IntegerField()),
('salt', models.CharField(max_length=65)),
],
options={
'db_table': 'social_auth_nonce',
},
bases=(models.Model, DjangoNonceMixin),
),
migrations.CreateModel(
name='UserSocialAuth',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('provider', models.CharField(max_length=32)),
('uid', models.CharField(max_length=UID_LENGTH)),
('extra_data', JSONField(default='{}')),
('user', models.ForeignKey(
related_name='social_auth', to=USER_MODEL, on_delete=models.CASCADE)),
],
options={
'db_table': 'social_auth_usersocialauth',
},
bases=(models.Model, DjangoUserMixin),
),
migrations.AlterUniqueTogether(
name='usersocialauth',
unique_together={('provider', 'uid')},
),
migrations.AlterUniqueTogether(
name='code',
unique_together={('email', 'code')},
),
migrations.AlterUniqueTogether(
name='nonce',
unique_together={('server_url', 'timestamp', 'salt')},
),
]
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
from social_core.utils import setting_name
USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \
getattr(settings, 'AUTH_USER_MODEL', None) or \
'auth.User'
class Migration(migrations.Migration):
replaces = [
('default', '0002_add_related_name'),
('social_auth', '0002_add_related_name')
]
dependencies = [
('social_django', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='usersocialauth',
name='user',
field=models.ForeignKey(
related_name='social_auth', to=USER_MODEL, on_delete=models.CASCADE,
)
),
]
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.db import models, migrations
from social_core.utils import setting_name
EMAIL_LENGTH = getattr(settings, setting_name('EMAIL_LENGTH'), 254)
class Migration(migrations.Migration):
replaces = [
('default', '0003_alter_email_max_length'),
('social_auth', '0003_alter_email_max_length')
]
dependencies = [
('social_django', '0002_add_related_name'),
]
operations = [
migrations.AlterField(
model_name='code',
name='email',
field=models.EmailField(max_length=EMAIL_LENGTH),
),
]
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from ..fields import JSONField
class Migration(migrations.Migration):
replaces = [
('default', '0004_auto_20160423_0400'),
('social_auth', '0004_auto_20160423_0400')
]
dependencies = [
('social_django', '0003_alter_email_max_length'),
]
operations = [
migrations.AlterField(
model_name='usersocialauth',
name='extra_data',
field=JSONField(default=dict),
)
]
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-07-28 02:33
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
replaces = [
('social_auth', '0005_auto_20160727_2333')
]
dependencies = [
('social_django', '0004_auto_20160423_0400'),
]
operations = [
migrations.AlterUniqueTogether(
name='association',
unique_together=set([('server_url', 'handle')]),
),
]
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-02 11:54
from __future__ import unicode_literals
from django.db import migrations, models
import social_django.fields
import social_django.storage
class Migration(migrations.Migration):
dependencies = [
('social_django', '0005_auto_20160727_2333'),
]
operations = [
migrations.CreateModel(
name='Partial',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(db_index=True, max_length=32)),
('next_step', models.PositiveSmallIntegerField(default=0)),
('backend', models.CharField(max_length=32)),
('data', social_django.fields.JSONField(default=dict)),
],
options={
'db_table': 'social_auth_partial',
},
bases=(models.Model, social_django.storage.DjangoPartialMixin),
),
]
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-06-08 06:54
from __future__ import unicode_literals
from django.db import migrations, models
from django.utils import timezone
class Migration(migrations.Migration):
dependencies = [
('social_django', '0006_partial'),
]
operations = [
migrations.AddField(
model_name='code',
name='timestamp',
field=models.DateTimeField(auto_now_add=True,
db_index=True,
default=timezone.now),
preserve_default=False
),
]
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-06-08 06:57
from __future__ import unicode_literals
from django.db import migrations, models
from django.utils import timezone
class Migration(migrations.Migration):
dependencies = [
('social_django', '0007_code_timestamp'),
]
operations = [
migrations.AddField(
model_name='partial',
name='timestamp',
field=models.DateTimeField(auto_now_add=True,
db_index=True,
default=timezone.now),
preserve_default=False,
),
]
@@ -0,0 +1,139 @@
"""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_core.utils import setting_name
from .compat import get_rel_model
from .storage import DjangoUserMixin, DjangoAssociationMixin, \
DjangoNonceMixin, DjangoCodeMixin, \
DjangoPartialMixin, BaseDjangoStorage
from .fields import JSONField
from .managers import UserSocialAuthManager
USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \
getattr(settings, 'AUTH_USER_MODEL', None) or \
'auth.User'
UID_LENGTH = getattr(settings, setting_name('UID_LENGTH'), 255)
EMAIL_LENGTH = getattr(settings, setting_name('EMAIL_LENGTH'), 254)
NONCE_SERVER_URL_LENGTH = getattr(
settings, setting_name('NONCE_SERVER_URL_LENGTH'), 255)
ASSOCIATION_SERVER_URL_LENGTH = getattr(
settings, setting_name('ASSOCIATION_SERVER_URL_LENGTH'), 255)
ASSOCIATION_HANDLE_LENGTH = getattr(
settings, setting_name('ASSOCIATION_HANDLE_LENGTH'), 255)
class AbstractUserSocialAuth(models.Model, DjangoUserMixin):
"""Abstract Social Auth association model"""
user = models.ForeignKey(USER_MODEL, related_name='social_auth',
on_delete=models.CASCADE)
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:
app_label = "social_django"
abstract = True
@classmethod
def get_social_auth(cls, provider, uid):
try:
return cls.objects.select_related('user').get(provider=provider,
uid=uid)
except cls.DoesNotExist:
return None
@classmethod
def username_max_length(cls):
username_field = cls.username_field()
field = cls.user_model()._meta.get_field(username_field)
return field.max_length
@classmethod
def user_model(cls):
user_model = get_rel_model(field=cls._meta.get_field('user'))
return user_model
class UserSocialAuth(AbstractUserSocialAuth):
"""Social Auth association model"""
class Meta:
"""Meta data"""
app_label = "social_django"
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:
app_label = "social_django"
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:
app_label = "social_django"
db_table = 'social_auth_association'
unique_together = (
('server_url', 'handle',)
)
class Code(models.Model, DjangoCodeMixin):
email = models.EmailField(max_length=EMAIL_LENGTH)
code = models.CharField(max_length=32, db_index=True)
verified = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
class Meta:
app_label = "social_django"
db_table = 'social_auth_code'
unique_together = ('email', 'code')
class Partial(models.Model, DjangoPartialMixin):
token = models.CharField(max_length=32, db_index=True)
next_step = models.PositiveSmallIntegerField(default=0)
backend = models.CharField(max_length=32)
data = JSONField()
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
class Meta:
app_label = "social_django"
db_table = 'social_auth_partial'
class DjangoStorage(BaseDjangoStorage):
user = UserSocialAuth
nonce = Nonce
association = Association
code = Code
partial = Partial
@classmethod
def is_integrity_error(cls, exception):
return exception.__class__ is IntegrityError
@@ -0,0 +1,198 @@
"""Django ORM models for Social Auth"""
import base64
import six
import sys
from django.db import transaction
from django.db.utils import IntegrityError
from social_core.storage import UserMixin, AssociationMixin, NonceMixin, \
CodeMixin, PartialMixin, BaseStorage
class DjangoUserMixin(UserMixin):
"""Social Auth association model"""
@classmethod
def changed(cls, user):
user.save()
def set_extra_data(self, extra_data=None):
if super(DjangoUserMixin, self).set_extra_data(extra_data):
self.save()
@classmethod
def allowed_to_disconnect(cls, user, backend_name, association_id=None):
if association_id is not None:
qs = cls.objects.exclude(id=association_id)
else:
qs = cls.objects.exclude(provider=backend_name)
qs = qs.filter(user=user)
if hasattr(user, 'has_usable_password'):
valid_password = user.has_usable_password()
else:
valid_password = True
return valid_password or qs.count() > 0
@classmethod
def disconnect(cls, entry):
entry.delete()
@classmethod
def username_field(cls):
return getattr(cls.user_model(), 'USERNAME_FIELD', 'username')
@classmethod
def user_exists(cls, *args, **kwargs):
"""
Return True/False if a User instance exists with the given arguments.
Arguments are directly passed to filter() manager method.
"""
if 'username' in kwargs:
kwargs[cls.username_field()] = kwargs.pop('username')
return cls.user_model().objects.filter(*args, **kwargs).count() > 0
@classmethod
def get_username(cls, user):
return getattr(user, cls.username_field(), None)
@classmethod
def create_user(cls, *args, **kwargs):
username_field = cls.username_field()
if 'username' in kwargs and username_field not in kwargs:
kwargs[username_field] = kwargs.pop('username')
try:
if hasattr(transaction, 'atomic'):
# In Django versions that have an "atomic" transaction decorator / context
# manager, there's a transaction wrapped around this call.
# If the create fails below due to an IntegrityError, ensure that the transaction
# stays undamaged by wrapping the create in an atomic.
with transaction.atomic():
user = cls.user_model().objects.create_user(*args, **kwargs)
else:
user = cls.user_model().objects.create_user(*args, **kwargs)
except IntegrityError:
# User might have been created on a different thread, try and find them.
# If we don't, re-raise the IntegrityError.
exc_info = sys.exc_info()
# If email comes in as None it won't get found in the get
if kwargs.get('email', True) is None:
kwargs['email'] = ''
try:
user = cls.user_model().objects.get(*args, **kwargs)
except cls.user_model().DoesNotExist:
six.reraise(*exc_info)
return user
@classmethod
def get_user(cls, pk=None, **kwargs):
if pk:
kwargs = {'pk': pk}
try:
return cls.user_model().objects.get(**kwargs)
except cls.user_model().DoesNotExist:
return None
@classmethod
def get_users_by_email(cls, email):
user_model = cls.user_model()
email_field = getattr(user_model, 'EMAIL_FIELD', 'email')
return user_model.objects.filter(**{email_field + '__iexact': email})
@classmethod
def get_social_auth(cls, provider, uid):
if not isinstance(uid, six.string_types):
uid = str(uid)
try:
return cls.objects.get(provider=provider, uid=uid)
except cls.DoesNotExist:
return None
@classmethod
def get_social_auth_for_user(cls, user, provider=None, id=None):
qs = cls.objects.filter(user=user)
if provider:
qs = qs.filter(provider=provider)
if id:
qs = qs.filter(id=id)
return qs
@classmethod
def create_social_auth(cls, user, uid, provider):
if not isinstance(uid, six.string_types):
uid = str(uid)
if hasattr(transaction, 'atomic'):
# In Django versions that have an "atomic" transaction decorator / context
# manager, there's a transaction wrapped around this call.
# If the create fails below due to an IntegrityError, ensure that the transaction
# stays undamaged by wrapping the create in an atomic.
with transaction.atomic():
social_auth = cls.objects.create(user=user, uid=uid, provider=provider)
else:
social_auth = cls.objects.create(user=user, uid=uid, provider=provider)
return social_auth
class DjangoNonceMixin(NonceMixin):
@classmethod
def use(cls, server_url, timestamp, salt):
return cls.objects.get_or_create(server_url=server_url,
timestamp=timestamp,
salt=salt)[1]
class DjangoAssociationMixin(AssociationMixin):
@classmethod
def store(cls, server_url, association):
# Don't use get_or_create because issued cannot be null
try:
assoc = cls.objects.get(server_url=server_url,
handle=association.handle)
except cls.DoesNotExist:
assoc = cls(server_url=server_url,
handle=association.handle)
assoc.secret = base64.encodestring(association.secret)
assoc.issued = association.issued
assoc.lifetime = association.lifetime
assoc.assoc_type = association.assoc_type
assoc.save()
@classmethod
def get(cls, *args, **kwargs):
return cls.objects.filter(*args, **kwargs)
@classmethod
def remove(cls, ids_to_delete):
cls.objects.filter(pk__in=ids_to_delete).delete()
class DjangoCodeMixin(CodeMixin):
@classmethod
def get_code(cls, code):
try:
return cls.objects.get(code=code)
except cls.DoesNotExist:
return None
class DjangoPartialMixin(PartialMixin):
@classmethod
def load(cls, token):
try:
return cls.objects.get(token=token)
except cls.DoesNotExist:
return None
@classmethod
def destroy(cls, token):
partial = cls.load(token)
if partial:
partial.delete()
class BaseDjangoStorage(BaseStorage):
user = DjangoUserMixin
nonce = DjangoNonceMixin
association = DjangoAssociationMixin
code = DjangoCodeMixin
@@ -0,0 +1,159 @@
# coding=utf-8
from django.conf import settings
from django.http import HttpResponse, HttpRequest
from django.db.models import Model
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import authenticate
from django.shortcuts import redirect, resolve_url
from django.template import TemplateDoesNotExist, loader, engines
from django.utils.crypto import get_random_string
from django.utils.encoding import force_text
from django.utils.functional import Promise
from django.utils.translation import get_language
from social_core.strategy import BaseStrategy, BaseTemplateStrategy
from .compat import get_request_port
def render_template_string(request, html, context=None):
"""Take a template in the form of a string and render it for the
given context"""
template = engines['django'].from_string(html)
return template.render(context=context, request=request)
class DjangoTemplateStrategy(BaseTemplateStrategy):
def render_template(self, tpl, context):
template = loader.get_template(tpl)
return template.render(context=context, request=self.strategy.request)
def render_string(self, html, context):
return render_template_string(self.strategy.request, html, 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'):
if isinstance(value, Promise):
value = force_text(value)
value = resolve_url(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 get_request_port(request=self.request)
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)
return template.render(context=context, request=self.request)
except TemplateDoesNotExist:
return render_template_string(self.request, html, context)
def authenticate(self, backend, *args, **kwargs):
kwargs['strategy'] = self
kwargs['storage'] = self.storage
kwargs['backend'] = backend
return authenticate(*args, **kwargs)
def clean_authenticate_args(self, *args, **kwargs):
"""Cleanup request argument if present, which is passed to
authenticate as for Django 1.11"""
if len(args) > 0 and isinstance(args[0], HttpRequest):
kwargs['request'], args = args[0], args[1:]
return 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):
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()
@@ -0,0 +1,24 @@
"""URLs module"""
from django.conf import settings
from django.conf.urls import url
from social_core.utils import setting_name
from . import views
extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or ''
app_name = 'social'
urlpatterns = [
# authentication / association
url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth,
name='begin'),
url(r'^complete/(?P<backend>[^/]+){0}$'.format(extra), views.complete,
name='complete'),
# disconnection
url(r'^disconnect/(?P<backend>[^/]+){0}$'.format(extra), views.disconnect,
name='disconnect'),
url(r'^disconnect/(?P<backend>[^/]+)/(?P<association_id>\d+){0}$'
.format(extra), views.disconnect, name='disconnect_individual'),
]
@@ -0,0 +1,51 @@
# coding=utf-8
from functools import wraps
from django.conf import settings
from django.http import Http404
from social_core.utils import setting_name, module_member, get_strategy
from social_core.exceptions import MissingBackend
from social_core.backends.utils import get_backend
from .compat import reverse
BACKENDS = settings.AUTHENTICATION_BACKENDS
STRATEGY = getattr(settings, setting_name('STRATEGY'),
'social_django.strategy.DjangoStrategy')
STORAGE = getattr(settings, setting_name('STORAGE'),
'social_django.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
@@ -0,0 +1,129 @@
from 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_core.utils import setting_name
from social_core.actions import do_auth, do_complete, do_disconnect
from .utils import psa
NAMESPACE = getattr(settings, setting_name('URL_NAMESPACE'), None) or 'social'
# Calling `session.set_expiry(None)` results in a session lifetime equal to
# platform default session lifetime.
DEFAULT_SESSION_TIMEOUT = None
@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, request=request,
*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 get_session_timeout(social_user, enable_session_expiration=False,
max_session_length=None):
if enable_session_expiration:
# Retrieve an expiration date from the social user who just finished
# logging in; this value was set by the social auth backend, and was
# typically received from the server.
expiration = social_user.expiration_datetime()
# We've enabled session expiration. Check to see if we got
# a specific expiration time from the provider for this user;
# if not, use the platform default expiration.
if expiration:
received_expiration_time = expiration.total_seconds()
else:
received_expiration_time = DEFAULT_SESSION_TIMEOUT
# Check to see if the backend set a value as a maximum length
# that a session may be; if they did, then we should use the minimum
# of that and the received session expiration time, if any, to
# set the session length.
if received_expiration_time is None and max_session_length is None:
# We neither received an expiration length, nor have a maximum
# session length. Use the platform default.
session_expiry = DEFAULT_SESSION_TIMEOUT
elif received_expiration_time is None and max_session_length is not None:
# We only have a maximum session length; use that.
session_expiry = max_session_length
elif received_expiration_time is not None and max_session_length is None:
# We only have an expiration time received by the backend
# from the provider, with no set maximum. Use that.
session_expiry = received_expiration_time
else:
# We received an expiration time from the backend, and we also
# have a set maximum session length. Use the smaller of the two.
session_expiry = min(received_expiration_time, max_session_length)
else:
# If there's an explicitly-set maximum session length, use that
# even if we don't want to retrieve session expiry times from
# the backend. If there isn't, then use the platform default.
if max_session_length is None:
session_expiry = DEFAULT_SESSION_TIMEOUT
else:
session_expiry = max_session_length
return session_expiry
def _do_login(backend, user, social_user):
user.backend = '{0}.{1}'.format(backend.__module__,
backend.__class__.__name__)
# Get these details early to avoid any issues involved in the
# session switch that happens when we call login().
enable_session_expiration = backend.setting('SESSION_EXPIRATION', False)
max_session_length_setting = backend.setting('MAX_SESSION_LENGTH', None)
# Log the user in, creating a new session.
login(backend.strategy.request, user)
# Make sure that the max_session_length value is either an integer or
# None. Because we get this as a setting from the backend, it can be set
# to whatever the backend creator wants; we want to be resilient against
# unexpected types being presented to us.
try:
max_session_length = int(max_session_length_setting)
except (TypeError, ValueError):
# We got a response that doesn't look like a number; use the default.
max_session_length = None
# Get the session expiration length based on the maximum session length
# setting, combined with any session length received from the backend.
session_expiry = get_session_timeout(
social_user,
enable_session_expiration=enable_session_expiration,
max_session_length=max_session_length,
)
try:
# Set the session length to our previously determined expiry length.
backend.strategy.request.session.set_expiry(session_expiry)
except OverflowError:
# The timestamp we used wasn't in the range of values supported by
# Django for session length; use the platform default. We tried.
backend.strategy.request.session.set_expiry(DEFAULT_SESSION_TIMEOUT)