Integrated my history into data
This commit is contained in:
@@ -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'
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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,
|
||||
)
|
||||
),
|
||||
]
|
||||
+28
@@ -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,
|
||||
),
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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)
|
||||
Reference in New Issue
Block a user