Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ to your ``settings.py``:
STORMPATH_SECRET = 'yourApiKeySecret'
STORMPATH_APPLICATION = 'https://api.stormpath.com/v1/applications/YOUR_APP_UID_HERE'

If you have custom fields in you user model, you can update it while sign up or sign in
by defining STORMPATH_RETRIEVE_SOCIAL_CUSTOM_DATA variable like this:

.. code-block:: python

STORMPATH_RETRIEVE_SOCIAL_CUSTOM_DATA = {
'facebook': "users.utils.retrieve_custom_data_facebook"
}

Where `users.utils.retrieve_custom_data_facebook` is a function that return dict.
Key is the field name with django prefix, and value is the field. This function
Will be responsible for connecting to the provider and process the data.

Once this is done, you're ready to get started! The next thing you need to do
is to sync your database and apply any migrations:

Expand Down
2 changes: 1 addition & 1 deletion django_stormpath/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _create_or_get_user(self, account):
UserModel = get_user_model()
try:
user = UserModel.objects.get(
Q(username=account.username) | Q(email=account.email))
**{UserModel.USERNAME_FIELD: getattr(account, UserModel.USERNAME_FIELD)})
user._mirror_data_from_stormpath_account(account)
self._mirror_groups_from_stormpath()
users_sp_groups = [g.name for g in account.groups]
Expand Down
7 changes: 3 additions & 4 deletions django_stormpath/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
"""

from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import ReadOnlyPasswordHashField

from stormpath.error import Error

from .models import APPLICATION
from .models import StormpathUser, APPLICATION


class StormpathUserCreationForm(forms.ModelForm):
Expand All @@ -22,7 +21,7 @@ class StormpathUserCreationForm(forms.ModelForm):
widget=forms.PasswordInput)

class Meta:
model = get_user_model()
model = StormpathUser
fields = ("username", "email",
"given_name", "surname", "password1", "password2")

Expand Down Expand Up @@ -89,7 +88,7 @@ class StormpathUserChangeForm(forms.ModelForm):
"""Update Stormpath user form."""

class Meta:
model = get_user_model()
model = StormpathUser
exclude = ('password',)

password = ReadOnlyPasswordHashField(help_text=("Passwords are not stored in the local database "
Expand Down
110 changes: 72 additions & 38 deletions django_stormpath/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@

from django.conf import settings
from django.db import models, IntegrityError, transaction
from django.contrib.auth.models import (BaseUserManager,
AbstractBaseUser, PermissionsMixin)
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser, PermissionsMixin)
from django.forms import model_to_dict
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.signals import pre_save, pre_delete
from django.contrib.auth.models import Group
from django.dispatch import receiver
from django import VERSION as django_version
from django.utils.dateparse import (
parse_date, parse_datetime, parse_time
)

from stormpath.client import Client
from stormpath.error import Error as StormpathError
Expand Down Expand Up @@ -143,37 +146,39 @@ def delete(self, *args, **kwargs):
delete.queryset_only = True


class StormpathBaseUser(AbstractBaseUser, PermissionsMixin):

class Meta:
abstract = True

class StormpathMixin(models.Model):
href = models.CharField(max_length=255, null=True, blank=True)
username = models.CharField(max_length=255, unique=True)
given_name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
middle_name = models.CharField(max_length=255, null=True, blank=True)
email = models.EmailField(verbose_name='email address',
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
db_index=True)

STORMPATH_BASE_FIELDS = ['href', 'username', 'given_name', 'surname', 'middle_name', 'email', 'password']
EXCLUDE_FIELDS = ['href', 'last_login', 'groups', 'id', 'stormpathpermissionsmixin_ptr', 'user_permissions']

PASSWORD_FIELD = 'password'
is_active = models.BooleanField(default=get_default_is_active)
is_verified = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['given_name', 'surname']
STORMPATH_BASE_FIELDS = [
'href', 'username', 'given_name', 'surname', 'middle_name', 'email',
'password']
EXCLUDE_FIELDS = [
'href', 'last_login', 'groups', 'id', 'stormpathpermissionsmixin_ptr',
'user_permissions']
DATE_FIELDS = []
TIME_FIELDS = []
DATETIME_FIELDS = []
FILE_FIELDS = []
PASSWORD_FIELD = 'password'

is_active = models.BooleanField(default=get_default_is_active)
is_verified = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
DJANGO_PREFIX = 'spDjango_'

objects = StormpathUserManager()

DJANGO_PREFIX = 'spDjango_'
class Meta:
abstract = True

@property
def first_name(self):
Expand Down Expand Up @@ -212,11 +217,19 @@ def _mirror_data_from_db_user(self, account, data):
if 'is_active' in data:
del data['is_active']

for key in data:
for key, value in data.iteritems():
if key in self.STORMPATH_BASE_FIELDS:
account[key] = data[key]
account[key] = value
else:
account.custom_data[self.DJANGO_PREFIX + key] = data[key]
# Matches datetime, date and time
if hasattr(value, 'isoformat'):
value = value.isoformat()
if key in self.FILE_FIELDS:
if value:
value = value.url
else:
value = None
account.custom_data[self.DJANGO_PREFIX + key] = value

return account

Expand All @@ -228,7 +241,17 @@ def _mirror_data_from_stormpath_account(self, account):
if field != 'password':
self.__setattr__(field, account[field])
for key in account.custom_data.keys():
self.__setattr__(key.split(self.DJANGO_PREFIX)[0], account.custom_data[key])
field_name = [part for part in key.split(self.DJANGO_PREFIX) if part][0]
value = account.custom_data[key]
# check if value is not None for nullable fields
if value:
if field_name in self.DATE_FIELDS:
value = parse_date(value)
elif field_name in self.DATETIME_FIELDS:
value = parse_datetime(value)
elif field_name in self.TIME_FIELDS:
value = parse_time(value)
self.__setattr__(field_name, value)

if account.status == account.STATUS_ENABLED:
self.is_active = True
Expand Down Expand Up @@ -283,19 +306,10 @@ def _update_stormpath_user(self, data, raw_password):
finally:
self._remove_raw_password()

def get_full_name(self):
return "%s %s" % (self.given_name, self.surname)

def get_short_name(self):
return self.email

def __unicode__(self):
return self.get_full_name()

def _update_for_db_and_stormpath(self, *args, **kwargs):
try:
with transaction.atomic():
super(StormpathBaseUser, self).save(*args, **kwargs)
super(StormpathMixin, self).save(*args, **kwargs)
self._update_stormpath_user(model_to_dict(self), self._get_raw_password())
except StormpathError:
raise
Expand All @@ -308,7 +322,7 @@ def _update_for_db_and_stormpath(self, *args, **kwargs):
def _create_for_db_and_stormpath(self, *args, **kwargs):
try:
with transaction.atomic():
super(StormpathBaseUser, self).save(*args, **kwargs)
super(StormpathMixin, self).save(*args, **kwargs)
account = self._create_stormpath_user(model_to_dict(self), self._get_raw_password())
self.href = account.href
self.username = account.username
Expand All @@ -324,7 +338,7 @@ def _create_for_db_and_stormpath(self, *args, **kwargs):
raise

def _save_db_only(self, *args, **kwargs):
super(StormpathBaseUser, self).save(*args, **kwargs)
super(StormpathMixin, self).save(*args, **kwargs)

def _remove_raw_password(self):
"""We need to send a raw password to Stormpath. After an Account is saved on Stormpath
Expand Down Expand Up @@ -368,16 +382,36 @@ def save(self, *args, **kwargs):
def delete(self, *args, **kwargs):
with transaction.atomic():
href = self.href
super(StormpathBaseUser, self).delete(*args, **kwargs)
super(StormpathMixin, self).delete(*args, **kwargs)
try:
account = APPLICATION.accounts.get(href)
account.delete()
except StormpathError:
raise


class StormpathBaseUser(StormpathMixin, AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=255, unique=True)
middle_name = models.CharField(max_length=255, null=True, blank=True)

is_admin = models.BooleanField(default=False)

class Meta:
abstract = True

def get_full_name(self):
return "%s %s" % (self.given_name, self.surname)

def get_short_name(self):
return self.email

def __unicode__(self):
return self.get_full_name()


class StormpathUser(StormpathBaseUser):
pass
class Meta(StormpathBaseUser.Meta):
swappable = 'AUTH_USER_MODEL'


@receiver(pre_save, sender=Group)
Expand Down
20 changes: 16 additions & 4 deletions django_stormpath/social.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.shortcuts import resolve_url
from django.core.urlresolvers import reverse
from django.conf import settings
from django.utils.module_loading import import_string


from stormpath.error import Error as StormpathError
Expand Down Expand Up @@ -78,12 +79,12 @@ def get_access_token(provider, authorization_response, redirect_uri):
def handle_social_callback(request, provider):
provider_redirect_url = 'stormpath_' + provider.lower() + '_login_callback'
abs_redirect_uri = request.build_absolute_uri(
reverse(provider_redirect_url, kwargs={'provider': provider}))
reverse(provider_redirect_url, kwargs={'provider': provider}))

access_token = get_access_token(
provider,
request.build_absolute_uri(),
abs_redirect_uri)
provider,
request.build_absolute_uri(),
abs_redirect_uri)

if not access_token:
raise RuntimeError('Error communicating with Autentication Provider: %s' % provider)
Expand All @@ -108,6 +109,17 @@ def handle_social_callback(request, provider):

account = APPLICATION.get_provider_account(**params)

if hasattr(settings, 'STORMPATH_RETRIEVE_SOCIAL_CUSTOM_DATA'):
if settings.STORMPATH_RETRIEVE_SOCIAL_CUSTOM_DATA.get(provider):
update_custom_data_func = import_string(
settings.STORMPATH_RETRIEVE_SOCIAL_CUSTOM_DATA.get(provider)
)

new_custom_data = update_custom_data_func(account, access_token)
for key, value in new_custom_data.items():
# account doesn't have .update method
account.custom_data[key] = value

user = _get_django_user(account)
user.backend = SOCIAL_AUTH_BACKEND
django_login(request, user)
Expand Down