It doesn't necessarily need to live in the primary codebase, it is simply a proof of the original idea. With the appearance of a fair number of helper methods to decouple social auth from django models, it really is looking like a backend is in order. More details:
+ Add MongoEngine models
* Add several methods to both models modules to decouple social auth core and default pipelines from Django ORM code
* In social pipelines, make sure uid is a str value
* Only load django admin settings if current models are django models
+ Add a conf module to manage loading specific Django settings and default values to use
"""Admin settings"""
-from django.contrib import admin
-from social_auth.models import UserSocialAuth, Nonce, Association
+from social_auth import conf
-class UserSocialAuthOption(admin.ModelAdmin):
- """Social Auth user options"""
- list_display = ('id', 'user', 'provider', 'uid')
- search_fields = ('user__first_name', 'user__last_name', 'user__email')
- list_filter = ('provider',)
- raw_id_fields = ('user',)
- list_select_related = True
+if conf.get_models_module().NAME == 'django_models':
-class NonceOption(admin.ModelAdmin):
- """Nonce options"""
- list_display = ('id', 'server_url', 'timestamp', 'salt')
- search_fields = ('server_url',)
+ from django.contrib import admin
+ from social_auth.models import UserSocialAuth, Nonce, Association
-class AssociationOption(admin.ModelAdmin):
- """Association options"""
- list_display = ('id', 'server_url', 'assoc_type')
- list_filter = ('assoc_type',)
- search_fields = ('server_url',)
+ class UserSocialAuthOption(admin.ModelAdmin):
+ """Social Auth user options"""
+ list_display = ('id', 'user', 'provider', 'uid')
+ search_fields = ('user__first_name', 'user__last_name', 'user__email')
+ list_filter = ('provider',)
+ raw_id_fields = ('user',)
+ list_select_related = True
-admin.site.register(UserSocialAuth, UserSocialAuthOption)
-admin.site.register(Nonce, NonceOption)
-admin.site.register(Association, AssociationOption)
+
+ 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)
from django.utils import simplejson
from django.utils.importlib import import_module
+from social_auth.models import get_social_auth_for_user
from social_auth.utils import setting, log, model_to_ctype, ctype_to_model, \
clean_partial_pipeline
from social_auth.store import DjangoOpenIDStore
from social_auth.backends.utils import build_consumer_oauth_request
-if setting('SOCIAL_AUTH_USER_MODEL'):
- User = models.get_model(*setting('SOCIAL_AUTH_USER_MODEL').rsplit('.', 1))
-else:
- from django.contrib.auth.models import User
+def get_user_model():
+ from social_auth.models import User
+ return User
# OpenID configuration
return {}
def get_user(self, user_id):
+
"""
Return user with given ID from the User model used by this backend
"""
+ user_cls = get_user_model()
try:
- return User.objects.get(pk=user_id)
- except User.DoesNotExist:
+ return user_cls.objects.get(id=user_id)
+ except user_cls.DoesNotExist:
return None
Override if extra operations are needed.
"""
if association_id:
- user.social_auth.get(id=association_id).delete()
+ get_social_auth_for_user(user).get(id=association_id).delete()
else:
- user.social_auth.filter(provider=self.AUTH_BACKEND.name).delete()
+ get_social_auth_for_user(user).filter(provider=self.AUTH_BACKEND.name).delete()
def build_absolute_uri(self, path=None):
"""Build absolute URI for given path. Replace http:// schema with
from django.conf import settings
from social_auth.models import User
+from social_auth.models import USERNAME, get_username_max_length
from social_auth.backends import get_backend, PIPELINE
-USERNAME = 'username'
-USERNAME_MAX_LENGTH = User._meta.get_field(USERNAME).max_length
+USERNAME_MAX_LENGTH = get_username_max_length()
def warn_setting(name, func_name):
-from django.core.exceptions import MultipleObjectsReturned
-
from social_auth.utils import setting
from social_auth.models import User
from social_auth.backends.pipeline import warn_setting
# objects are returned
try:
return {'user': User.objects.get(email=email)}
- except MultipleObjectsReturned:
+ except User.MultipleObjectsReturned:
raise AuthException(kwargs['backend'], 'Not unique email address.')
except User.DoesNotExist:
pass
Raise AuthException if UserSocialAuth entry belongs to another user.
"""
try:
- social_user = UserSocialAuth.objects.select_related('user')\
+ social_user = UserSocialAuth.select_related()\
.get(provider=backend.name,
uid=uid)
except UserSocialAuth.DoesNotExist:
return None
try:
+ if type(uid) is not str:
+ uid = str(uid)
social = UserSocialAuth.objects.create(user=user, uid=uid,
provider=backend.name)
except IntegrityError:
from uuid import uuid4
from social_auth.utils import setting
-from social_auth.models import User
+from social_auth.models import create_user as create_user_in_db
+from social_auth.models import simple_user_exists
from social_auth.backends.pipeline import USERNAME, USERNAME_MAX_LENGTH, \
warn_setting
from social_auth.signals import socialauth_not_registered, \
pre_update
-def simple_user_exists(*args, **kwargs):
- """Return True/False if a User instance exists with the given arguments.
- Arguments are directly passed to filter() manager method."""
- return User.objects.filter(*args, **kwargs).exists()
-
-
def get_username(details, user=None, user_exists=simple_user_exists,
*args, **kwargs):
"""Return an username for new user. Return current user username
details=details)
return None
- email = details.get('email')
+ email = details.get('email') or None
return {
- 'user': User.objects.create_user(username=username, email=email),
+ 'user': create_user_in_db(username=username, email=email),
'is_new': True
}
--- /dev/null
+"""Centralized definition of settings"""
+
+
+from django.utils import importlib
+from social_auth.utils import setting
+
+
+SOCIAL_AUTH_MODELS = setting('SOCIAL_AUTH_MODELS', 'social_auth.django_models')
+
+
+def get_models_module():
+ """Load and return the module specified by the SOCIAL_AUTH_MODELS config
+ setting.
+
+ """
+ return importlib.import_module(SOCIAL_AUTH_MODELS)
from social_auth.backends import get_backends
from social_auth.utils import group_backend_by_type
from social_auth.models import User
+from social_auth.models import get_social_auth_for_user
# Note: social_auth_backends, social_auth_by_type_backends and
if isinstance(user, User) and user.is_authenticated():
accounts.update((assoc.provider.replace('-', '_'), assoc)
- for assoc in user.social_auth.all())
+ for assoc in get_social_auth_for_user(user))
return {'social_auth': accounts}
# user comes from request.user usually, on /admin/ it will be an instance
# of auth.User and this code will fail if a custom User model was defined
if isinstance(user, User) and user.is_authenticated():
- associated = user.social_auth.all()
+ associated = get_social_auth_for_user(user)
not_associated = list(set(available) -
set(assoc.provider for assoc in associated))
values['associated'] = associated
-"""Social auth models"""
+"""Django ORM models for Social Auth"""
from datetime import timedelta
from django.db import models
from social_auth.utils import setting
+NAME = 'django_models'
+
+
# If User class is overridden, it *must* provide the following fields
# and methods work with django-social-auth:
#
from django.contrib.auth.models import User
+# TODO make this a complementary config setting to SOCIAL_AUTH_USER_MODEL
+USERNAME = 'username'
+
+
+def get_username_max_length():
+ """Get the max length constraint from the User model username field.
+ """
+ return User._meta.get_field(USERNAME).max_length
+
+
+def simple_user_exists(*args, **kwargs):
+ """Return True/False if a User instance exists with the given arguments.
+ Arguments are directly passed to filter() manager method."""
+ return User.objects.filter(*args, **kwargs).exists()
+
+
+def create_user(*args, **kwargs):
+ return User.objects.create_user(*args, **kwargs)
+
+
+def get_social_auth_for_user(user):
+ return user.social_auth.all()
+
+
class UserSocialAuth(models.Model):
"""Social Auth association model"""
user = models.ForeignKey(User, related_name='social_auth')
pass
return None
+ @classmethod
+ def select_related(cls):
+ return cls.objects.select_related('user')
+
class Nonce(models.Model):
"""One use numbers"""
# TODO define protocol for implementing modules...
-from django.conf import settings
-from django.utils import importlib
+from social_auth import conf
-models_module_name = getattr(settings, 'SOCIAL_AUTH_MODELS',
- 'social_auth.django_models')
-models_module = importlib.import_module(models_module_name)
+models_module = conf.get_models_module()
this_module = globals()
for key in dir(models_module):
--- /dev/null
+"""MongoEngine models for Social Auth
+
+Requires MongoEngine 0.6.10
+
+"""
+# TODO extract common code into base objects/mixins
+
+
+from datetime import timedelta
+from mongoengine import DictField
+from mongoengine import Document
+from mongoengine import IntField
+from mongoengine import ReferenceField
+from mongoengine import StringField
+from social_auth.utils import setting
+
+
+NAME = 'mongoengine_models'
+
+
+# If User class is overridden, it *must* provide the following fields
+# and methods work with django-social-auth:
+#
+# username = CharField()
+# last_login = DateTimeField()
+# is_active = BooleanField()
+# def is_authenticated():
+# ...
+
+if setting('SOCIAL_AUTH_USER_MODEL'):
+ User = models.get_model(*setting('SOCIAL_AUTH_USER_MODEL').rsplit('.', 1))
+else:
+ from mongoengine.django.auth import User
+
+
+# TODO make this a complementary config setting to SOCIAL_AUTH_USER_MODEL
+USERNAME = 'username'
+
+
+def get_username_max_length():
+ """Get the max length constraint from the User model username field.
+ """
+ return getattr(User, USERNAME).max_length
+
+
+def simple_user_exists(*args, **kwargs):
+ """Return True/False if a User instance exists with the given arguments.
+ Arguments are directly passed to filter() manager method."""
+ return User.objects.filter(*args, **kwargs).count()
+
+
+def create_user(*args, **kwargs):
+ return User.objects.create(*args, **kwargs)
+
+
+def get_social_auth_for_user(user):
+ return UserSocialAuth.objects(user=user)
+
+
+class UserSocialAuth(Document):
+ """Social Auth association model"""
+ user = ReferenceField(User)
+ provider = StringField(max_length=32)
+ uid = StringField(max_length=255, unique_with='provider')
+ extra_data = DictField()
+
+ def __unicode__(self):
+ """Return associated user unicode representation"""
+ return u'%s - %s' % (unicode(self.user), self.provider)
+
+ @classmethod
+ def select_related(cls):
+ return cls.objects #.select_related() No 'user', only provie a depth parameter
+
+ @property
+ def tokens(self):
+ """Return access_token stored in extra_data or None"""
+ # Make import here to avoid recursive imports :-/
+ from social_auth.backends import get_backends
+ backend = get_backends().get(self.provider)
+ if backend:
+ return backend.AUTH_BACKEND.tokens(self)
+ else:
+ return {}
+
+ def expiration_delta(self):
+ """Return saved session expiration seconds if any. Is returned in
+ the form of a timedelta data type. None is returned if there's no
+ value stored or it's malformed.
+ """
+ if self.extra_data:
+ name = setting('SOCIAL_AUTH_EXPIRATION', 'expires')
+ try:
+ return timedelta(seconds=int(self.extra_data.get(name)))
+ except (ValueError, TypeError):
+ pass
+ return None
+
+
+class Nonce(Document):
+ """One use numbers"""
+ server_url = StringField(max_length=255)
+ timestamp = IntField()
+ salt = StringField(max_length=40)
+
+ def __unicode__(self):
+ """Unicode representation"""
+ return self.server_url
+
+
+class Association(Document):
+ """OpenId account association"""
+ server_url = StringField(max_length=255)
+ handle = StringField(max_length=255)
+ secret = StringField(max_length=255) # Stored base64 encoded
+ issued = IntField()
+ lifetime = IntField()
+ assoc_type = StringField(max_length=64)
+
+ def __unicode__(self):
+ """Unicode representation"""
+ return '%s %s' % (self.handle, self.issued)