mirror of
https://github.com/Tivix/django-rest-auth.git
synced 2024-11-25 10:33:45 +03:00
commit
0ac575e603
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -52,3 +52,4 @@ coverage.xml
|
|||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
.DS_Store
|
||||
|
|
10
.travis.yml
10
.travis.yml
|
@ -3,11 +3,17 @@ python:
|
|||
- "2.6"
|
||||
- "2.7"
|
||||
env:
|
||||
- DJANGO=1.5.8
|
||||
- DJANGO=1.6.5
|
||||
- DJANGO=1.5.10
|
||||
- DJANGO=1.6.7
|
||||
- DJANGO=1.7
|
||||
matrix:
|
||||
exclude:
|
||||
- python: "2.6"
|
||||
env: DJANGO=1.7
|
||||
install:
|
||||
- pip install -q Django==$DJANGO --use-mirrors
|
||||
- pip install coveralls
|
||||
- pip install -r test_requirements.pip
|
||||
script:
|
||||
- coverage run --source=rest_auth setup.py test
|
||||
after_success:
|
||||
|
|
41
rest_auth/app_settings.py
Normal file
41
rest_auth/app_settings.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
from django.conf import settings
|
||||
|
||||
from rest_auth.serializers import (
|
||||
TokenSerializer as DefaultTokenSerializer,
|
||||
UserDetailsSerializer as DefaultUserDetailsSerializer,
|
||||
LoginSerializer as DefaultLoginSerializer,
|
||||
PasswordResetSerializer as DefaultPasswordResetSerializer,
|
||||
PasswordResetConfirmSerializer as DefaultPasswordResetConfirmSerializer,
|
||||
PasswordChangeSerializer as DefaultPasswordChangeSerializer)
|
||||
from .utils import import_callable
|
||||
|
||||
|
||||
serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {})
|
||||
|
||||
TokenSerializer = import_callable(
|
||||
serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer))
|
||||
|
||||
UserDetailsSerializer = import_callable(
|
||||
serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer)
|
||||
)
|
||||
|
||||
LoginSerializer = import_callable(
|
||||
serializers.get('LOGIN_SERIALIZER', DefaultLoginSerializer)
|
||||
)
|
||||
|
||||
PasswordResetSerializer = import_callable(
|
||||
serializers.get('PASSWORD_RESET_SERIALIZER',
|
||||
DefaultPasswordResetSerializer)
|
||||
)
|
||||
|
||||
PasswordResetConfirmSerializer = import_callable(
|
||||
serializers.get('PASSWORD_RESET_CONFIRM_SERIALIZER',
|
||||
DefaultPasswordResetConfirmSerializer)
|
||||
)
|
||||
|
||||
PasswordChangeSerializer = import_callable(
|
||||
serializers.get('PASSWORD_CHANGE_SERIALIZER',
|
||||
DefaultPasswordChangeSerializer)
|
||||
)
|
||||
|
||||
|
0
rest_auth/registration/__init__.py
Normal file
0
rest_auth/registration/__init__.py
Normal file
43
rest_auth/registration/serializers.py
Normal file
43
rest_auth/registration/serializers.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from rest_framework import serializers
|
||||
from requests.exceptions import HTTPError
|
||||
from allauth.socialaccount.helpers import complete_social_login
|
||||
|
||||
|
||||
class SocialLoginSerializer(serializers.Serializer):
|
||||
|
||||
access_token = serializers.CharField(required=True)
|
||||
|
||||
def validate_access_token(self, attrs, source):
|
||||
access_token = attrs[source]
|
||||
|
||||
view = self.context.get('view')
|
||||
request = self.context.get('request')
|
||||
|
||||
if not view:
|
||||
raise serializers.ValidationError('View is not defined, pass it ' +
|
||||
'as a context variable')
|
||||
self.adapter_class = getattr(view, 'adapter_class', None)
|
||||
|
||||
if not self.adapter_class:
|
||||
raise serializers.ValidationError('Define adapter_class in view')
|
||||
|
||||
self.adapter = self.adapter_class()
|
||||
app = self.adapter.get_provider().get_app(request)
|
||||
token = self.adapter.parse_token({'access_token': access_token})
|
||||
token.app = app
|
||||
|
||||
try:
|
||||
login = self.adapter.complete_login(request, app, token,
|
||||
response=access_token)
|
||||
token.account = login.account
|
||||
login.token = token
|
||||
complete_social_login(request, login)
|
||||
except HTTPError:
|
||||
raise serializers.ValidationError('Incorrect value')
|
||||
|
||||
if not login.is_existing:
|
||||
login.lookup()
|
||||
login.save(request, connect=True)
|
||||
self.object = {'user': login.account.user}
|
||||
|
||||
return attrs
|
16
rest_auth/registration/urls.py
Normal file
16
rest_auth/registration/urls.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from django.views.generic import TemplateView
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from .views import Register, VerifyEmail
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', Register.as_view(), name='rest_register'),
|
||||
url(r'^verify-email/$', VerifyEmail.as_view(), name='verify_email'),
|
||||
|
||||
url(r'^account-email-verification-sent/$', TemplateView.as_view(),
|
||||
name='account_email_verification_sent'),
|
||||
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
|
||||
name='account_confirm_email'),
|
||||
|
||||
)
|
||||
|
66
rest_auth/registration/views.py
Normal file
66
rest_auth/registration/views.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework import status
|
||||
|
||||
from allauth.account.views import SignupView, ConfirmEmailView
|
||||
from allauth.account.utils import complete_signup
|
||||
from allauth.account import app_settings
|
||||
|
||||
from rest_auth.serializers import UserDetailsSerializer
|
||||
from rest_auth.registration.serializers import SocialLoginSerializer
|
||||
from rest_auth.views import Login
|
||||
|
||||
|
||||
class Register(APIView, SignupView):
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
user_serializer_class = UserDetailsSerializer
|
||||
|
||||
def form_valid(self, form):
|
||||
self.user = form.save(self.request)
|
||||
return complete_signup(self.request, self.user,
|
||||
app_settings.EMAIL_VERIFICATION,
|
||||
self.get_success_url())
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.initial = {}
|
||||
self.request.POST = self.request.DATA.copy()
|
||||
form_class = self.get_form_class()
|
||||
self.form = self.get_form(form_class)
|
||||
if self.form.is_valid():
|
||||
self.form_valid(self.form)
|
||||
return self.get_response()
|
||||
else:
|
||||
return self.get_response_with_errors()
|
||||
|
||||
def get_response(self):
|
||||
serializer = self.user_serializer_class(instance=self.user)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def get_response_with_errors(self):
|
||||
return Response(self.form.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class VerifyEmail(APIView, ConfirmEmailView):
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.kwargs['key'] = self.request.DATA.get('key', '')
|
||||
confirmation = self.get_object()
|
||||
confirmation.confirm(self.request)
|
||||
return Response({'message': 'ok'}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class SocialLogin(Login):
|
||||
"""
|
||||
class used for social authentications
|
||||
example usage for facebook
|
||||
|
||||
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
||||
class FacebookLogin(SocialLogin):
|
||||
adapter_class = FacebookOAuth2Adapter
|
||||
"""
|
||||
|
||||
serializer_class = SocialLoginSerializer
|
|
@ -1,60 +1,21 @@
|
|||
#This file mainly exists to allow python setup.py test to work.
|
||||
import os, sys
|
||||
# This file mainly exists to allow python setup.py test to work.
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'test_settings'
|
||||
test_dir = os.path.dirname(__file__)
|
||||
sys.path.insert(0, test_dir)
|
||||
|
||||
import django
|
||||
from django.test.utils import get_runner
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import RequestSite
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db import models
|
||||
|
||||
from rest_framework.serializers import _resolve_model
|
||||
from registration.models import RegistrationProfile
|
||||
from registration.backends.default.views import RegistrationView as BaseRegistrationView
|
||||
from registration import signals
|
||||
|
||||
"""
|
||||
create user profile model
|
||||
"""
|
||||
class UserProfile(models.Model):
|
||||
user = models.ForeignKey(User, unique=True)
|
||||
newsletter_subscribe = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
app_label = 'rest_auth'
|
||||
|
||||
|
||||
"""
|
||||
overwrite register to avoid sending email
|
||||
"""
|
||||
class RegistrationView(BaseRegistrationView):
|
||||
def register(self, request, **cleaned_data):
|
||||
username, email, password = cleaned_data['username'], cleaned_data['email'], cleaned_data['password1']
|
||||
if Site._meta.installed:
|
||||
site = Site.objects.get_current()
|
||||
else:
|
||||
site = RequestSite(request)
|
||||
new_user = RegistrationProfile.objects.create_inactive_user(username, email,
|
||||
password, site, send_email=False)
|
||||
signals.user_registered.send(sender=self.__class__,
|
||||
user=new_user,
|
||||
request=request)
|
||||
|
||||
# create user profile
|
||||
profile_model_path = getattr(settings, 'REST_PROFILE_MODULE', None)
|
||||
if profile_model_path:
|
||||
user_profile_model = _resolve_model(profile_model_path)
|
||||
user_profile_model.objects.create(user=new_user)
|
||||
|
||||
return new_user
|
||||
|
||||
|
||||
def runtests():
|
||||
TestRunner = get_runner(settings)
|
||||
test_runner = TestRunner(verbosity=1, interactive=True)
|
||||
if hasattr(django, 'setup'):
|
||||
django.setup()
|
||||
failures = test_runner.run_tests(['rest_auth'])
|
||||
sys.exit(bool(failures))
|
||||
|
||||
|
|
|
@ -1,20 +1,34 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
|
||||
try:
|
||||
from django.utils.http import urlsafe_base64_decode as uid_decoder
|
||||
except:
|
||||
# make compatible with django 1.5
|
||||
from django.utils.http import base36_to_int as uid_decoder
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import _resolve_model
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.authtoken.serializers import AuthTokenSerializer
|
||||
|
||||
|
||||
profile_model_path = lambda: getattr(settings, 'REST_PROFILE_MODULE', None)
|
||||
class LoginSerializer(AuthTokenSerializer):
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(max_length=30)
|
||||
password = serializers.CharField(max_length=128)
|
||||
def validate(self, attrs):
|
||||
attrs = super(LoginSerializer, self).validate(attrs)
|
||||
|
||||
if 'rest_auth.registration' in settings.INSTALLED_APPS:
|
||||
from allauth.account import app_settings
|
||||
if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
|
||||
user = attrs['user']
|
||||
email_address = user.emailaddress_set.get(email=user.email)
|
||||
if not email_address.verified:
|
||||
raise serializers.ValidationError('E-mail is not verified.')
|
||||
return attrs
|
||||
|
||||
|
||||
class TokenSerializer(serializers.ModelSerializer):
|
||||
|
||||
"""
|
||||
Serializer for Token model.
|
||||
"""
|
||||
|
@ -34,120 +48,6 @@ class UserDetailsSerializer(serializers.ModelSerializer):
|
|||
fields = ('username', 'email', 'first_name', 'last_name')
|
||||
|
||||
|
||||
class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||
|
||||
"""
|
||||
Serializer for Django User model and most of its fields.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = get_user_model()
|
||||
fields = ('username', 'password', 'email', 'first_name', 'last_name')
|
||||
|
||||
|
||||
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
|
||||
|
||||
"""
|
||||
ModelSerializer that allows fields argument to control fields
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
fields = kwargs.pop('fields', None)
|
||||
|
||||
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
|
||||
|
||||
if fields:
|
||||
allowed = set(fields)
|
||||
existing = set(self.fields.keys())
|
||||
|
||||
for field_name in existing - allowed:
|
||||
self.fields.pop(field_name)
|
||||
|
||||
|
||||
class UserUpdateSerializer(DynamicFieldsModelSerializer):
|
||||
|
||||
"""
|
||||
User model w/o username and password
|
||||
"""
|
||||
class Meta:
|
||||
model = get_user_model()
|
||||
fields = ('id', 'email', 'first_name', 'last_name')
|
||||
|
||||
|
||||
|
||||
def get_user_registration_profile_serializer(*args, **kwargs):
|
||||
if profile_model_path():
|
||||
class UserRegistrationProfileSerializer(serializers.ModelSerializer):
|
||||
|
||||
"""
|
||||
Serializer that includes all profile fields except for user fk / id.
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
model = _resolve_model(profile_model_path())
|
||||
fields = filter(lambda x: x != 'id' and x != 'user',
|
||||
map(lambda x: x.name, model._meta.fields))
|
||||
else:
|
||||
class UserRegistrationProfileSerializer(serializers.Serializer):
|
||||
pass
|
||||
return UserRegistrationProfileSerializer
|
||||
|
||||
|
||||
def get_user_profile_serializer(*args, **kwargs):
|
||||
if profile_model_path():
|
||||
class UserProfileSerializer(serializers.ModelSerializer):
|
||||
|
||||
"""
|
||||
Serializer for UserProfile model.
|
||||
"""
|
||||
|
||||
user = UserDetailsSerializer()
|
||||
|
||||
class Meta:
|
||||
# http://stackoverflow.com/questions/4881607/django-get-model-from-string
|
||||
model = _resolve_model(profile_model_path())
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UserProfileSerializer, self).__init__(*args, **kwargs)
|
||||
else:
|
||||
class UserProfileSerializer(serializers.Serializer):
|
||||
pass
|
||||
return UserProfileSerializer
|
||||
|
||||
|
||||
def get_user_profile_update_serializer(*args, **kwargs):
|
||||
if profile_model_path():
|
||||
class UserProfileUpdateSerializer(serializers.ModelSerializer):
|
||||
|
||||
"""
|
||||
Serializer for updating User and UserProfile model.
|
||||
"""
|
||||
|
||||
user = UserUpdateSerializer()
|
||||
|
||||
class Meta:
|
||||
# http://stackoverflow.com/questions/4881607/django-get-model-from-string
|
||||
model = _resolve_model(profile_model_path())
|
||||
else:
|
||||
class UserProfileUpdateSerializer(serializers.Serializer):
|
||||
pass
|
||||
return UserProfileUpdateSerializer
|
||||
|
||||
|
||||
class SetPasswordSerializer(serializers.Serializer):
|
||||
|
||||
"""
|
||||
Serializer for changing Django User password.
|
||||
"""
|
||||
|
||||
new_password1 = serializers.CharField(max_length=128)
|
||||
new_password2 = serializers.CharField(max_length=128)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
return super(SetPasswordSerializer, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class PasswordResetSerializer(serializers.Serializer):
|
||||
|
||||
"""
|
||||
|
@ -155,3 +55,86 @@ class PasswordResetSerializer(serializers.Serializer):
|
|||
"""
|
||||
|
||||
email = serializers.EmailField()
|
||||
|
||||
password_reset_form_class = PasswordResetForm
|
||||
|
||||
def validate_email(self, attrs, source):
|
||||
# Create PasswordResetForm with the serializer
|
||||
self.reset_form = self.password_reset_form_class(data=attrs)
|
||||
if not self.reset_form.is_valid():
|
||||
raise serializers.ValidationError('Error')
|
||||
return attrs
|
||||
|
||||
def save(self):
|
||||
request = self.context.get('request')
|
||||
# Set some values to trigger the send_email method.
|
||||
opts = {
|
||||
'use_https': request.is_secure(),
|
||||
'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),
|
||||
'request': request,
|
||||
}
|
||||
self.reset_form.save(**opts)
|
||||
|
||||
|
||||
class PasswordResetConfirmSerializer(serializers.Serializer):
|
||||
|
||||
"""
|
||||
Serializer for requesting a password reset e-mail.
|
||||
"""
|
||||
|
||||
new_password1 = serializers.CharField(max_length=128)
|
||||
new_password2 = serializers.CharField(max_length=128)
|
||||
|
||||
uid = serializers.CharField(required=True)
|
||||
token = serializers.CharField(required=True)
|
||||
|
||||
set_password_form_class = SetPasswordForm
|
||||
|
||||
def custom_validation(self, attrs):
|
||||
pass
|
||||
|
||||
def validate(self, attrs):
|
||||
self._errors = {}
|
||||
# Get the UserModel
|
||||
UserModel = get_user_model()
|
||||
# Decode the uidb64 to uid to get User object
|
||||
try:
|
||||
uid = uid_decoder(attrs['uid'])
|
||||
self.user = UserModel._default_manager.get(pk=uid)
|
||||
except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
|
||||
self._errors['uid'] = ['Invalid value']
|
||||
|
||||
self.custom_validation(attrs)
|
||||
|
||||
# Construct SetPasswordForm instance
|
||||
self.set_password_form = self.set_password_form_class(user=self.user,
|
||||
data=attrs)
|
||||
if not self.set_password_form.is_valid():
|
||||
self._errors['token'] = ['Invalid value']
|
||||
|
||||
if not default_token_generator.check_token(self.user, attrs['token']):
|
||||
self._errors['token'] = ['Invalid value']
|
||||
|
||||
def save(self):
|
||||
self.set_password_form.save()
|
||||
|
||||
|
||||
class PasswordChangeSerializer(serializers.Serializer):
|
||||
|
||||
new_password1 = serializers.CharField(max_length=128)
|
||||
new_password2 = serializers.CharField(max_length=128)
|
||||
|
||||
set_password_form_class = SetPasswordForm
|
||||
|
||||
def validate(self, attrs):
|
||||
request = self.context.get('request')
|
||||
self.set_password_form = self.set_password_form_class(user=request.user,
|
||||
data=attrs)
|
||||
|
||||
if not self.set_password_form.is_valid():
|
||||
self._errors = self.set_password_form.errors
|
||||
return None
|
||||
return attrs
|
||||
|
||||
def save(self):
|
||||
self.set_password_form.save()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import django
|
||||
import os, sys
|
||||
import os
|
||||
import sys
|
||||
|
||||
PROJECT_ROOT = os.path.abspath(os.path.split(os.path.split(__file__)[0])[0])
|
||||
ROOT_URLCONF = 'urls'
|
||||
|
@ -27,6 +28,26 @@ if django.VERSION[:2] >= (1, 3):
|
|||
else:
|
||||
DATABASE_ENGINE = 'sqlite3'
|
||||
|
||||
MIDDLEWARE_CLASSES = [
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware'
|
||||
]
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = [
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.core.context_processors.debug',
|
||||
'django.core.context_processors.media',
|
||||
'django.core.context_processors.request',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.core.context_processors.static',
|
||||
|
||||
"allauth.account.context_processors.account",
|
||||
"allauth.socialaccount.context_processors.socialaccount",
|
||||
]
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
|
@ -37,13 +58,22 @@ INSTALLED_APPS = [
|
|||
'django.contrib.sitemaps',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'allauth.socialaccount.providers.facebook',
|
||||
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'registration',
|
||||
|
||||
'rest_auth',
|
||||
'rest_auth.registration'
|
||||
]
|
||||
|
||||
SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd"
|
||||
ACCOUNT_ACTIVATION_DAYS = 1
|
||||
SITE_ID = 1
|
||||
|
||||
MIGRATION_MODULES = {
|
||||
'authtoken': 'authtoken.migrations',
|
||||
}
|
||||
|
|
22
rest_auth/test_urls.py
Normal file
22
rest_auth/test_urls.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from django.conf.urls import patterns, url, include
|
||||
from django.views.generic import TemplateView
|
||||
from django.contrib.auth.tests import urls
|
||||
|
||||
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
||||
|
||||
from .urls import urlpatterns
|
||||
from .registration.views import SocialLogin
|
||||
|
||||
|
||||
class FacebookLogin(SocialLogin):
|
||||
adapter_class = FacebookOAuth2Adapter
|
||||
|
||||
urlpatterns += patterns('',
|
||||
url(r'^rest-registration/', include('registration.urls')),
|
||||
url(r'^test-admin/', include(urls)),
|
||||
url(r'^account-email-verification-sent/$', TemplateView.as_view(),
|
||||
name='account_email_verification_sent'),
|
||||
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
|
||||
name='account_confirm_email'),
|
||||
url(r'^social-login/facebook/$', FacebookLogin.as_view(), name='fb_login')
|
||||
)
|
|
@ -8,9 +8,13 @@ from django.test import TestCase
|
|||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core import mail
|
||||
from django.test.utils import override_settings
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from registration.models import RegistrationProfile
|
||||
from rest_framework.serializers import _resolve_model
|
||||
from allauth.socialaccount.models import SocialApp
|
||||
import responses
|
||||
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
class APIClient(Client):
|
||||
|
@ -22,16 +26,16 @@ class APIClient(Client):
|
|||
return self.generic('OPTIONS', path, data, content_type, **extra)
|
||||
|
||||
|
||||
class CustomJSONEncoder(json.JSONEncoder):
|
||||
"""
|
||||
Convert datetime/date objects into isoformat
|
||||
"""
|
||||
# class CustomJSONEncoder(json.JSONEncoder):
|
||||
# """
|
||||
# Convert datetime/date objects into isoformat
|
||||
# """
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, (datetime, date, time)):
|
||||
return obj.isoformat()
|
||||
else:
|
||||
return super(CustomJSONEncoder, self).default(obj)
|
||||
# def default(self, obj):
|
||||
# if isinstance(obj, (datetime, date, time)):
|
||||
# return obj.isoformat()
|
||||
# else:
|
||||
# return super(CustomJSONEncoder, self).default(obj)
|
||||
|
||||
|
||||
class BaseAPITestCase(object):
|
||||
|
@ -48,7 +52,7 @@ class BaseAPITestCase(object):
|
|||
kwargs['content_type'] = 'application/json'
|
||||
if 'data' in kwargs and request_method != 'get' and kwargs['content_type'] == 'application/json':
|
||||
data = kwargs.get('data', '')
|
||||
kwargs['data'] = json.dumps(data, cls=CustomJSONEncoder)
|
||||
kwargs['data'] = json.dumps(data) # , cls=CustomJSONEncoder
|
||||
if 'status_code' in kwargs:
|
||||
status_code = kwargs.pop('status_code')
|
||||
|
||||
|
@ -56,10 +60,6 @@ class BaseAPITestCase(object):
|
|||
if hasattr(self, 'token'):
|
||||
kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token
|
||||
|
||||
if hasattr(self, 'company_token'):
|
||||
kwargs[
|
||||
'HTTP_AUTHORIZATION'] = 'Company-Token %s' % self.company_token
|
||||
|
||||
self.response = request_func(*args, **kwargs)
|
||||
is_json = bool(
|
||||
filter(lambda x: 'json' in x, self.response._headers['content-type']))
|
||||
|
@ -80,28 +80,28 @@ class BaseAPITestCase(object):
|
|||
def patch(self, *args, **kwargs):
|
||||
return self.send_request('patch', *args, **kwargs)
|
||||
|
||||
def put(self, *args, **kwargs):
|
||||
return self.send_request('put', *args, **kwargs)
|
||||
# def put(self, *args, **kwargs):
|
||||
# return self.send_request('put', *args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
return self.send_request('delete', *args, **kwargs)
|
||||
# def delete(self, *args, **kwargs):
|
||||
# return self.send_request('delete', *args, **kwargs)
|
||||
|
||||
def options(self, *args, **kwargs):
|
||||
return self.send_request('options', *args, **kwargs)
|
||||
# def options(self, *args, **kwargs):
|
||||
# return self.send_request('options', *args, **kwargs)
|
||||
|
||||
def post_file(self, *args, **kwargs):
|
||||
kwargs['content_type'] = MULTIPART_CONTENT
|
||||
return self.send_request('post', *args, **kwargs)
|
||||
# def post_file(self, *args, **kwargs):
|
||||
# kwargs['content_type'] = MULTIPART_CONTENT
|
||||
# return self.send_request('post', *args, **kwargs)
|
||||
|
||||
def get_file(self, *args, **kwargs):
|
||||
content_type = None
|
||||
if 'content_type' in kwargs:
|
||||
content_type = kwargs.pop('content_type')
|
||||
response = self.send_request('get', *args, **kwargs)
|
||||
if content_type:
|
||||
self.assertEqual(
|
||||
bool(filter(lambda x: content_type in x, response._headers['content-type'])), True)
|
||||
return response
|
||||
# def get_file(self, *args, **kwargs):
|
||||
# content_type = None
|
||||
# if 'content_type' in kwargs:
|
||||
# content_type = kwargs.pop('content_type')
|
||||
# response = self.send_request('get', *args, **kwargs)
|
||||
# if content_type:
|
||||
# self.assertEqual(
|
||||
# bool(filter(lambda x: content_type in x, response._headers['content-type'])), True)
|
||||
# return response
|
||||
|
||||
def init(self):
|
||||
settings.DEBUG = True
|
||||
|
@ -113,7 +113,6 @@ class BaseAPITestCase(object):
|
|||
# -----------------------
|
||||
|
||||
|
||||
|
||||
class APITestCase1(TestCase, BaseAPITestCase):
|
||||
"""
|
||||
Case #1:
|
||||
|
@ -121,23 +120,23 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
|||
- custom registration: backend defined
|
||||
"""
|
||||
|
||||
urls = 'rest_auth.test_urls'
|
||||
|
||||
USERNAME = 'person'
|
||||
PASS = 'person'
|
||||
EMAIL = "person1@world.com"
|
||||
NEW_PASS = 'new-test-pass'
|
||||
PROFILE_MODEL = 'rest_auth.UserProfile'
|
||||
REGISTRATION_VIEW = 'rest_auth.runtests.RegistrationView'
|
||||
|
||||
# data without user profile
|
||||
BASIC_REGISTRATION_DATA = {
|
||||
REGISTRATION_DATA = {
|
||||
"username": USERNAME,
|
||||
"password": PASS,
|
||||
"email": EMAIL
|
||||
"password1": PASS,
|
||||
"password2": PASS
|
||||
}
|
||||
|
||||
# data with user profile
|
||||
REGISTRATION_DATA = BASIC_REGISTRATION_DATA.copy()
|
||||
REGISTRATION_DATA['newsletter_subscribe'] = False
|
||||
REGISTRATION_DATA_WITH_EMAIL = REGISTRATION_DATA.copy()
|
||||
REGISTRATION_DATA_WITH_EMAIL['email'] = EMAIL
|
||||
|
||||
BASIC_USER_DATA = {
|
||||
'first_name': "John",
|
||||
|
@ -150,28 +149,44 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
|||
def setUp(self):
|
||||
self.init()
|
||||
self.login_url = reverse('rest_login')
|
||||
self.logout_url = reverse('rest_logout')
|
||||
self.password_change_url = reverse('rest_password_change')
|
||||
self.register_url = reverse('rest_register')
|
||||
self.password_reset_url = reverse('rest_password_reset')
|
||||
self.user_url = reverse('rest_user_details')
|
||||
self.veirfy_email_url = reverse('verify_email')
|
||||
|
||||
setattr(settings, 'REST_PROFILE_MODULE', self.PROFILE_MODEL)
|
||||
self.user_profile_model = None
|
||||
if self.PROFILE_MODEL:
|
||||
self.user_profile_model = _resolve_model(self.PROFILE_MODEL)
|
||||
def _login(self):
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
self.post(self.login_url, data=payload, status_code=status.HTTP_200_OK)
|
||||
|
||||
if self.REGISTRATION_VIEW:
|
||||
setattr(settings, 'REST_REGISTRATION_BACKEND', self.REGISTRATION_VIEW)
|
||||
elif hasattr(settings, 'REST_REGISTRATION_BACKEND'):
|
||||
delattr(settings, 'REST_REGISTRATION_BACKEND')
|
||||
def _logout(self):
|
||||
self.post(self.logout_url, status=status.HTTP_200_OK)
|
||||
|
||||
def _generate_uid_and_token(self, user):
|
||||
result = {}
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django import VERSION
|
||||
if VERSION[1] == 5:
|
||||
from django.utils.http import int_to_base36
|
||||
result['uid'] = int_to_base36(user.pk)
|
||||
else:
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
result['uid'] = urlsafe_base64_encode(force_bytes(user.pk))
|
||||
result['token'] = default_token_generator.make_token(user)
|
||||
return result
|
||||
|
||||
def test_login(self):
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
# there is no users in db so it should throw error (401)
|
||||
self.post(self.login_url, data=payload, status_code=401)
|
||||
# there is no users in db so it should throw error (400)
|
||||
self.post(self.login_url, data=payload, status_code=400)
|
||||
|
||||
self.post(self.password_change_url, status_code=403)
|
||||
|
||||
|
@ -187,25 +202,24 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
|||
# test inactive user
|
||||
user.is_active = False
|
||||
user.save()
|
||||
self.post(self.login_url, data=payload, status_code=401)
|
||||
self.post(self.login_url, data=payload, status_code=400)
|
||||
|
||||
# test wrong username/password
|
||||
payload = {
|
||||
"username": self.USERNAME+'?',
|
||||
"username": self.USERNAME + '?',
|
||||
"password": self.PASS
|
||||
}
|
||||
self.post(self.login_url, data=payload, status_code=401)
|
||||
self.post(self.login_url, data=payload, status_code=400)
|
||||
|
||||
# test empty payload
|
||||
self.post(self.login_url, data={}, status_code=400)
|
||||
|
||||
|
||||
def test_password_change(self):
|
||||
login_payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
user = User.objects.create_user(self.USERNAME, '', self.PASS)
|
||||
User.objects.create_user(self.USERNAME, '', self.PASS)
|
||||
self.post(self.login_url, data=login_payload, status_code=200)
|
||||
self.token = self.response.json['key']
|
||||
|
||||
|
@ -217,7 +231,7 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
|||
status_code=200)
|
||||
|
||||
# user should not be able to login using old password
|
||||
self.post(self.login_url, data=login_payload, status_code=401)
|
||||
self.post(self.login_url, data=login_payload, status_code=400)
|
||||
|
||||
# new password should work
|
||||
login_payload['password'] = new_password_payload['new_password1']
|
||||
|
@ -234,77 +248,23 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
|||
# send empty payload
|
||||
self.post(self.password_change_url, data={}, status_code=400)
|
||||
|
||||
def test_registration(self):
|
||||
user_count = User.objects.all().count()
|
||||
|
||||
# test empty payload
|
||||
self.post(self.register_url, data={}, status_code=400)
|
||||
|
||||
self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201)
|
||||
self.assertEqual(User.objects.all().count(), user_count+1)
|
||||
new_user = get_user_model().objects.latest('id')
|
||||
|
||||
if self.REGISTRATION_VIEW:
|
||||
activation_key = RegistrationProfile.objects.latest('id').activation_key
|
||||
verify_url = reverse('verify_email',
|
||||
kwargs={'activation_key': activation_key})
|
||||
|
||||
# new user at this point shouldn't be active
|
||||
self.assertEqual(new_user.is_active, False)
|
||||
|
||||
# let's active new user and check is_active flag
|
||||
self.get(verify_url)
|
||||
new_user = get_user_model().objects.latest('id')
|
||||
self.assertEqual(new_user.is_active, True)
|
||||
if self.user_profile_model:
|
||||
user_profile = self.user_profile_model.objects.get(user=new_user)
|
||||
self.assertIsNotNone(user_profile)
|
||||
else:
|
||||
self.assertEqual(new_user.is_active, True)
|
||||
|
||||
def test_registration_without_profile_data(self):
|
||||
user_count = User.objects.all().count()
|
||||
|
||||
self.post(self.register_url, data=self.BASIC_REGISTRATION_DATA,
|
||||
status_code=201)
|
||||
self.assertEqual(User.objects.all().count(), user_count+1)
|
||||
new_user = get_user_model().objects.latest('id')
|
||||
|
||||
if self.REGISTRATION_VIEW:
|
||||
activation_key = RegistrationProfile.objects.latest('id').activation_key
|
||||
verify_url = reverse('verify_email',
|
||||
kwargs={'activation_key': activation_key})
|
||||
|
||||
# new user at this point shouldn't be active
|
||||
self.assertEqual(new_user.is_active, False)
|
||||
|
||||
# let's active new user and check is_active flag
|
||||
self.get(verify_url)
|
||||
new_user = get_user_model().objects.latest('id')
|
||||
self.assertEqual(new_user.is_active, True)
|
||||
if self.user_profile_model:
|
||||
user_profile = self.user_profile_model.objects.get(user=new_user)
|
||||
self.assertIsNotNone(user_profile)
|
||||
else:
|
||||
self.assertEqual(new_user.is_active, True)
|
||||
|
||||
|
||||
def test_password_reset(self):
|
||||
user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASS)
|
||||
|
||||
# call password reset
|
||||
mail_count = len(mail.outbox)
|
||||
payload = {'email': self.EMAIL}
|
||||
self.post(self.password_reset_url, data=payload)
|
||||
self.assertEqual(len(mail.outbox), mail_count+1)
|
||||
|
||||
url_kwargs = self.generate_uid_and_token(user)
|
||||
self.post(self.password_reset_url, data=payload, status_code=200)
|
||||
self.assertEqual(len(mail.outbox), mail_count + 1)
|
||||
|
||||
url_kwargs = self._generate_uid_and_token(user)
|
||||
data = {
|
||||
'new_password1': self.NEW_PASS,
|
||||
'new_password2': self.NEW_PASS
|
||||
'new_password2': self.NEW_PASS,
|
||||
'uid': url_kwargs['uid'],
|
||||
'token': url_kwargs['token']
|
||||
}
|
||||
url = reverse('rest_password_reset_confirm', kwargs=url_kwargs)
|
||||
url = reverse('rest_password_reset_confirm')
|
||||
self.post(url, data=data, status_code=200)
|
||||
|
||||
payload = {
|
||||
|
@ -315,8 +275,6 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
|||
|
||||
def test_user_details(self):
|
||||
user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASS)
|
||||
if self.user_profile_model:
|
||||
self.user_profile_model.objects.create(user=user)
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
|
@ -325,60 +283,100 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
|||
self.token = self.response.json['key']
|
||||
self.get(self.user_url, status_code=200)
|
||||
|
||||
self.post(self.user_url, data=self.BASIC_USER_DATA, status_code=200)
|
||||
self.patch(self.user_url, data=self.BASIC_USER_DATA, status_code=200)
|
||||
user = User.objects.get(pk=user.pk)
|
||||
self.assertEqual(user.first_name, self.response.json['first_name'])
|
||||
self.assertEqual(user.last_name, self.response.json['last_name'])
|
||||
self.assertEqual(user.email, self.response.json['email'])
|
||||
|
||||
if self.user_profile_model:
|
||||
self.post(self.user_url, data=self.USER_DATA, status_code=200)
|
||||
user = User.objects.get(pk=user.pk)
|
||||
self.assertEqual(user.first_name, self.response.json['user']['first_name'])
|
||||
self.assertEqual(user.last_name, self.response.json['user']['last_name'])
|
||||
self.assertEqual(user.email, self.response.json['user']['email'])
|
||||
self.assertIn('newsletter_subscribe', self.response.json)
|
||||
else:
|
||||
self.assertEqual(user.first_name, self.response.json['first_name'])
|
||||
self.assertEqual(user.last_name, self.response.json['last_name'])
|
||||
self.assertEqual(user.email, self.response.json['email'])
|
||||
def test_registration(self):
|
||||
user_count = User.objects.all().count()
|
||||
|
||||
# test empty payload
|
||||
self.post(self.register_url, data={}, status_code=400)
|
||||
|
||||
self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201)
|
||||
self.assertEqual(User.objects.all().count(), user_count + 1)
|
||||
new_user = get_user_model().objects.latest('id')
|
||||
self.assertEqual(new_user.username, self.REGISTRATION_DATA['username'])
|
||||
|
||||
self._login()
|
||||
self._logout()
|
||||
|
||||
@override_settings(
|
||||
ACCOUNT_EMAIL_VERIFICATION='mandatory',
|
||||
ACCOUNT_EMAIL_REQUIRED=True
|
||||
)
|
||||
def test_registration_with_email_verification(self):
|
||||
user_count = User.objects.all().count()
|
||||
mail_count = len(mail.outbox)
|
||||
|
||||
# test empty payload
|
||||
self.post(self.register_url, data={},
|
||||
status_code=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
self.post(self.register_url, data=self.REGISTRATION_DATA_WITH_EMAIL,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
self.assertEqual(User.objects.all().count(), user_count + 1)
|
||||
self.assertEqual(len(mail.outbox), mail_count + 1)
|
||||
new_user = get_user_model().objects.latest('id')
|
||||
self.assertEqual(new_user.username, self.REGISTRATION_DATA['username'])
|
||||
|
||||
# email is not verified yet
|
||||
payload = {
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASS
|
||||
}
|
||||
self.post(self.login_url, data=payload,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# veirfy email
|
||||
email_confirmation = new_user.emailaddress_set.get(email=self.EMAIL)\
|
||||
.emailconfirmation_set.order_by('-created')[0]
|
||||
self.post(self.veirfy_email_url, data={"key": email_confirmation.key},
|
||||
status_code=status.HTTP_200_OK)
|
||||
|
||||
# try to login again
|
||||
self._login()
|
||||
self._logout()
|
||||
|
||||
|
||||
def generate_uid_and_token(self, user):
|
||||
result = {}
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django import VERSION
|
||||
if VERSION[1] == 6:
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
result['uid'] = urlsafe_base64_encode(force_bytes(user.pk))
|
||||
elif VERSION[1] == 5:
|
||||
from django.utils.http import int_to_base36
|
||||
result['uid'] = int_to_base36(user.pk)
|
||||
result['token'] = default_token_generator.make_token(user)
|
||||
return result
|
||||
class TestSocialAuth(TestCase, BaseAPITestCase):
|
||||
|
||||
urls = 'rest_auth.test_urls'
|
||||
|
||||
class APITestCase2(APITestCase1):
|
||||
"""
|
||||
Case #2:
|
||||
- user profile: not defined
|
||||
- custom registration backend: not defined
|
||||
"""
|
||||
PROFILE_MODEL = None
|
||||
def setUp(self):
|
||||
social_app = SocialApp.objects.create(
|
||||
provider='facebook',
|
||||
name='Facebook',
|
||||
client_id='123123123',
|
||||
secret='321321321',
|
||||
)
|
||||
site = Site.objects.get_current()
|
||||
social_app.sites.add(site)
|
||||
self.fb_login_url = reverse('fb_login')
|
||||
|
||||
@responses.activate
|
||||
def test_failed_social_auth(self):
|
||||
# fake response
|
||||
responses.add(responses.GET, 'https://graph.facebook.com/me',
|
||||
body='', status=400, content_type='application/json')
|
||||
|
||||
class APITestCase3(APITestCase1):
|
||||
"""
|
||||
Case #3:
|
||||
- user profile: defined
|
||||
- custom registration backend: not defined
|
||||
"""
|
||||
REGISTRATION_VIEW = None
|
||||
payload = {
|
||||
'access_token': 'abc123'
|
||||
}
|
||||
self.post(self.fb_login_url, data=payload, status_code=400)
|
||||
|
||||
@responses.activate
|
||||
def test_social_auth(self):
|
||||
# fake response for facebook call
|
||||
resp_body = '{"id":"123123123123","first_name":"John","gender":"male","last_name":"Smith","link":"https:\\/\\/www.facebook.com\\/john.smith","locale":"en_US","name":"John Smith","timezone":2,"updated_time":"2014-08-13T10:14:38+0000","username":"john.smith","verified":true}'
|
||||
responses.add(responses.GET, 'https://graph.facebook.com/me',
|
||||
body=resp_body, status=200, content_type='application/json')
|
||||
|
||||
class APITestCase4(APITestCase1):
|
||||
"""
|
||||
Case #4:
|
||||
- user profile: not defined
|
||||
- custom registration backend: not defined
|
||||
"""
|
||||
PROFILE_MODEL = None
|
||||
REGISTRATION_VIEW = None
|
||||
payload = {
|
||||
'access_token': 'abc123'
|
||||
}
|
||||
|
||||
self.post(self.fb_login_url, data=payload, status_code=200)
|
||||
self.assertIn('key', self.response.json.keys())
|
||||
|
|
|
@ -1,32 +1,18 @@
|
|||
from django.conf import settings
|
||||
from django.conf.urls import patterns, url, include
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from rest_auth.views import Login, Logout, Register, UserDetails, \
|
||||
PasswordChange, PasswordReset, VerifyEmail, PasswordResetConfirm
|
||||
from rest_auth.views import (Login, Logout, UserDetails, PasswordChange,
|
||||
PasswordReset, PasswordResetConfirm)
|
||||
|
||||
|
||||
urlpatterns = patterns('rest_auth.views',
|
||||
# URLs that do not require a session or valid token
|
||||
url(r'^register/$', Register.as_view(),
|
||||
name='rest_register'),
|
||||
url(r'^password/reset/$', PasswordReset.as_view(),
|
||||
name='rest_password_reset'),
|
||||
url(r'^password/reset/confirm/(?P<uid>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
|
||||
PasswordResetConfirm.as_view(
|
||||
), name='rest_password_reset_confirm'),
|
||||
url(r'^login/$', Login.as_view(), name='rest_login'),
|
||||
url(r'^verify-email/(?P<activation_key>\w+)/$',
|
||||
VerifyEmail.as_view(), name='verify_email'),
|
||||
|
||||
# URLs that require a user to be logged in with a valid
|
||||
# session / token.
|
||||
url(r'^logout/$', Logout.as_view(), name='rest_logout'),
|
||||
url(r'^user/$', UserDetails.as_view(),
|
||||
name='rest_user_details'),
|
||||
url(r'^password/change/$', PasswordChange.as_view(),
|
||||
name='rest_password_change'),
|
||||
)
|
||||
|
||||
if getattr(settings, 'IS_TEST', False):
|
||||
from django.contrib.auth.tests import urls
|
||||
urlpatterns += patterns('', url(r'^test-admin/', include(urls)))
|
||||
urlpatterns = patterns('',
|
||||
# URLs that do not require a session or valid token
|
||||
url(r'^password/reset/$', PasswordReset.as_view(),
|
||||
name='rest_password_reset'),
|
||||
url(r'^password/reset/confirm/$', PasswordResetConfirm.as_view(),
|
||||
name='rest_password_reset_confirm'),
|
||||
url(r'^login/$', Login.as_view(), name='rest_login'),
|
||||
# URLs that require a user to be logged in with a valid session / token.
|
||||
url(r'^logout/$', Logout.as_view(), name='rest_logout'),
|
||||
url(r'^user/$', UserDetails.as_view(), name='rest_user_details'),
|
||||
url(r'^password/change/$', PasswordChange.as_view(),
|
||||
name='rest_password_change'),
|
||||
)
|
||||
|
|
|
@ -1,43 +1,11 @@
|
|||
from django.utils.crypto import get_random_string
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
|
||||
HASH_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
def import_callable(path_or_callable):
|
||||
if hasattr(path_or_callable, '__call__'):
|
||||
return path_or_callable
|
||||
else:
|
||||
assert isinstance(path_or_callable, (str, unicode))
|
||||
package, attr = path_or_callable.rsplit('.', 1)
|
||||
return getattr(import_module(package), attr)
|
||||
|
||||
|
||||
def generate_new_hash_with_length(length):
|
||||
"""
|
||||
Generates a random string with the alphanumerical character set and given length.
|
||||
"""
|
||||
return get_random_string(length, HASH_CHARACTERS)
|
||||
|
||||
|
||||
# Based on http://stackoverflow.com/a/547867. Thanks! Credit goes to you!
|
||||
def construct_modules_and_import(name):
|
||||
"""
|
||||
Grab the Python string to import
|
||||
"""
|
||||
|
||||
# Get all the components by dot notations
|
||||
components = name.split('.')
|
||||
module = ''
|
||||
i = 1
|
||||
|
||||
# Construct the partial Python string except the last package name
|
||||
for comp in components:
|
||||
if i < len(components):
|
||||
module += str(comp)
|
||||
|
||||
if i < (len(components) - 1):
|
||||
module += '.'
|
||||
|
||||
i += 1
|
||||
|
||||
# Import the module from above python string
|
||||
mod = __import__(module)
|
||||
|
||||
# Import the component recursivcely
|
||||
for comp in components[1:]:
|
||||
mod = getattr(mod, comp)
|
||||
|
||||
# Return the imported module's class
|
||||
return mod
|
||||
|
|
|
@ -1,53 +1,19 @@
|
|||
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
|
||||
from django.contrib.auth import authenticate, login, logout, get_user_model
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
try:
|
||||
from django.utils.http import urlsafe_base64_decode as uid_decoder
|
||||
except:
|
||||
# make compatible with django 1.5
|
||||
from django.utils.http import base36_to_int as uid_decoder
|
||||
from django.contrib.auth import login, logout
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.serializers import _resolve_model
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.authentication import SessionAuthentication, \
|
||||
TokenAuthentication
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.generics import RetrieveUpdateAPIView
|
||||
|
||||
from registration.models import RegistrationProfile
|
||||
from registration import signals
|
||||
from registration.views import ActivationView
|
||||
|
||||
from rest_auth.utils import construct_modules_and_import
|
||||
from rest_auth.models import *
|
||||
from rest_auth.serializers import (TokenSerializer, UserDetailsSerializer,
|
||||
LoginSerializer, UserRegistrationSerializer,
|
||||
SetPasswordSerializer, PasswordResetSerializer, UserUpdateSerializer,
|
||||
get_user_registration_profile_serializer, get_user_profile_serializer,
|
||||
get_user_profile_update_serializer)
|
||||
|
||||
|
||||
def get_user_profile_model():
|
||||
# Get the UserProfile model from the setting value
|
||||
user_profile_path = getattr(settings, 'REST_PROFILE_MODULE', None)
|
||||
if user_profile_path:
|
||||
setattr(settings, 'AUTH_PROFILE_MODULE', user_profile_path)
|
||||
return _resolve_model(user_profile_path)
|
||||
|
||||
|
||||
def get_registration_backend():
|
||||
# Get the REST Registration Backend for django-registration
|
||||
registration_backend = getattr(settings, 'REST_REGISTRATION_BACKEND',
|
||||
'registration.backends.simple.views.RegistrationView')
|
||||
|
||||
# Get the REST REGISTRATION BACKEND class from the setting value via above
|
||||
# method
|
||||
return construct_modules_and_import(registration_backend)
|
||||
from app_settings import (TokenSerializer, UserDetailsSerializer,
|
||||
LoginSerializer, PasswordResetSerializer, PasswordResetConfirmSerializer,
|
||||
PasswordChangeSerializer)
|
||||
|
||||
|
||||
class LoggedInRESTAPIView(APIView):
|
||||
|
@ -73,35 +39,33 @@ class Login(LoggedOutRESTAPIView, GenericAPIView):
|
|||
|
||||
serializer_class = LoginSerializer
|
||||
token_model = Token
|
||||
token_serializer = TokenSerializer
|
||||
response_serializer = TokenSerializer
|
||||
|
||||
def post(self, request):
|
||||
# Create a serializer with request.DATA
|
||||
serializer = self.serializer_class(data=request.DATA)
|
||||
def get_serializer(self):
|
||||
return self.serializer_class(data=self.request.DATA,
|
||||
context={'request': self.request, 'view': self})
|
||||
|
||||
if serializer.is_valid():
|
||||
# Authenticate the credentials by grabbing Django User object
|
||||
user = authenticate(username=serializer.data['username'],
|
||||
password=serializer.data['password'])
|
||||
def login(self):
|
||||
self.user = self.serializer.object['user']
|
||||
self.token, created = self.token_model.objects.get_or_create(
|
||||
user=self.user)
|
||||
if getattr(settings, 'REST_SESSION_LOGIN', True):
|
||||
login(self.request, self.user)
|
||||
|
||||
if user and user.is_authenticated():
|
||||
if user.is_active:
|
||||
if getattr(settings, 'REST_SESSION_LOGIN', True):
|
||||
login(request, user)
|
||||
def get_response(self):
|
||||
return Response(self.response_serializer(self.token).data,
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
# Return REST Token object with OK HTTP status
|
||||
token, created = self.token_model.objects.get_or_create(user=user)
|
||||
return Response(self.token_serializer(token).data,
|
||||
status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response({'error': 'This account is disabled.'},
|
||||
status=status.HTTP_401_UNAUTHORIZED)
|
||||
else:
|
||||
return Response({'error': 'Invalid Username/Password.'},
|
||||
status=status.HTTP_401_UNAUTHORIZED)
|
||||
else:
|
||||
return Response(serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
def get_error_response(self):
|
||||
return Response(self.serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.serializer = self.get_serializer()
|
||||
if not self.serializer.is_valid():
|
||||
return self.get_error_response()
|
||||
self.login()
|
||||
return self.get_response()
|
||||
|
||||
|
||||
class Logout(LoggedInRESTAPIView):
|
||||
|
@ -113,7 +77,7 @@ class Logout(LoggedInRESTAPIView):
|
|||
Accepts/Returns nothing.
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
def post(self, request):
|
||||
try:
|
||||
request.user.auth_token.delete()
|
||||
except:
|
||||
|
@ -125,54 +89,7 @@ class Logout(LoggedInRESTAPIView):
|
|||
status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class Register(LoggedOutRESTAPIView, GenericAPIView):
|
||||
|
||||
"""
|
||||
Registers a new Django User object by accepting required field values.
|
||||
|
||||
Accepts the following POST parameters:
|
||||
Required: username, password, email
|
||||
Optional: first_name & last_name for User object and UserProfile fields
|
||||
Returns the newly created User object including REST Framework Token key.
|
||||
"""
|
||||
|
||||
serializer_class = UserRegistrationSerializer
|
||||
|
||||
def get_profile_serializer_class(self):
|
||||
return get_user_registration_profile_serializer()
|
||||
|
||||
def post(self, request):
|
||||
# Create serializers with request.DATA
|
||||
serializer = self.serializer_class(data=request.DATA)
|
||||
profile_serializer_class = self.get_profile_serializer_class()
|
||||
profile_serializer = profile_serializer_class(data=request.DATA)
|
||||
|
||||
if serializer.is_valid() and profile_serializer.is_valid():
|
||||
# Change the password key to password1 so that RESTRegistrationView
|
||||
# can accept the data
|
||||
serializer.data['password1'] = serializer.data.pop('password')
|
||||
|
||||
# TODO: Make this customizable backend via settings.
|
||||
# Call RESTRegistrationView().register to create new Django User
|
||||
# and UserProfile models
|
||||
data = serializer.data.copy()
|
||||
data.update(profile_serializer.data)
|
||||
|
||||
RESTRegistrationView = get_registration_backend()
|
||||
RESTRegistrationView().register(request, **data)
|
||||
|
||||
# Return the User object with Created HTTP status
|
||||
return Response(UserDetailsSerializer(serializer.data).data,
|
||||
status=status.HTTP_201_CREATED)
|
||||
|
||||
else:
|
||||
return Response({
|
||||
'user': serializer.errors,
|
||||
'profile': profile_serializer.errors},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class UserDetails(LoggedInRESTAPIView, GenericAPIView):
|
||||
class UserDetails(LoggedInRESTAPIView, RetrieveUpdateAPIView):
|
||||
|
||||
"""
|
||||
Returns User's details in JSON format.
|
||||
|
@ -183,50 +100,10 @@ class UserDetails(LoggedInRESTAPIView, GenericAPIView):
|
|||
Optional: email, first_name, last_name and UserProfile fields
|
||||
Returns the updated UserProfile and/or User object.
|
||||
"""
|
||||
if get_user_profile_model():
|
||||
serializer_class = get_user_profile_update_serializer()
|
||||
else:
|
||||
serializer_class = UserUpdateSerializer
|
||||
serializer_class = UserDetailsSerializer
|
||||
|
||||
def get_profile_serializer_class(self):
|
||||
return get_user_profile_serializer()
|
||||
|
||||
def get_profile_update_serializer_class(self):
|
||||
return get_user_profile_update_serializer()
|
||||
|
||||
def get(self, request):
|
||||
# Create serializers with request.user and profile
|
||||
user_profile_model = get_user_profile_model()
|
||||
if user_profile_model:
|
||||
profile_serializer_class = self.get_profile_serializer_class()
|
||||
serializer = profile_serializer_class(request.user.get_profile())
|
||||
else:
|
||||
serializer = UserDetailsSerializer(request.user)
|
||||
# Send the Return the User and its profile model with OK HTTP status
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def post(self, request):
|
||||
# Get the User object updater via this Serializer
|
||||
user_profile_model = get_user_profile_model()
|
||||
if user_profile_model:
|
||||
profile_serializer_class = self.get_profile_update_serializer_class()
|
||||
serializer = profile_serializer_class(request.user.get_profile(),
|
||||
data=request.DATA, partial=True)
|
||||
else:
|
||||
serializer = UserUpdateSerializer(request.user, data=request.DATA,
|
||||
partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
# Save UserProfileUpdateSerializer
|
||||
serializer.save()
|
||||
|
||||
# Return the User object with OK HTTP status
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
else:
|
||||
# Return the UserProfileUpdateSerializer errors with Bad Request
|
||||
# HTTP status
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
|
||||
class PasswordReset(LoggedOutRESTAPIView, GenericAPIView):
|
||||
|
@ -239,38 +116,18 @@ class PasswordReset(LoggedOutRESTAPIView, GenericAPIView):
|
|||
"""
|
||||
|
||||
serializer_class = PasswordResetSerializer
|
||||
password_reset_form_class = PasswordResetForm
|
||||
|
||||
def post(self, request):
|
||||
def post(self, request, *args, **kwargs):
|
||||
# Create a serializer with request.DATA
|
||||
serializer = self.serializer_class(data=request.DATA)
|
||||
serializer = self.get_serializer(data=request.DATA)
|
||||
|
||||
if serializer.is_valid():
|
||||
# Create PasswordResetForm with the serializer
|
||||
reset_form = self.password_reset_form_class(data=serializer.data)
|
||||
|
||||
if reset_form.is_valid():
|
||||
# Sett some values to trigger the send_email method.
|
||||
opts = {
|
||||
'use_https': request.is_secure(),
|
||||
'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),
|
||||
'request': request,
|
||||
}
|
||||
|
||||
reset_form.save(**opts)
|
||||
|
||||
# Return the success message with OK HTTP status
|
||||
return Response(
|
||||
{"success": "Password reset e-mail has been sent."},
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
else:
|
||||
return Response(reset_form._errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
else:
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
serializer.save()
|
||||
# Return the success message with OK HTTP status
|
||||
return Response({"success": "Password reset e-mail has been sent."},
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class PasswordResetConfirm(LoggedOutRESTAPIView, GenericAPIView):
|
||||
|
@ -283,80 +140,15 @@ class PasswordResetConfirm(LoggedOutRESTAPIView, GenericAPIView):
|
|||
Returns the success/fail message.
|
||||
"""
|
||||
|
||||
serializer_class = SetPasswordSerializer
|
||||
serializer_class = PasswordResetConfirmSerializer
|
||||
|
||||
def post(self, request, uid=None, token=None):
|
||||
# Get the UserModel
|
||||
UserModel = get_user_model()
|
||||
|
||||
# Decode the uidb64 to uid to get User object
|
||||
try:
|
||||
uid = uid_decoder(uid)
|
||||
user = UserModel._default_manager.get(pk=uid)
|
||||
except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
|
||||
user = None
|
||||
|
||||
# If we get the User object
|
||||
if user:
|
||||
serializer = self.serializer_class(data=request.DATA, user=user)
|
||||
|
||||
if serializer.is_valid():
|
||||
# Construct SetPasswordForm instance
|
||||
form = SetPasswordForm(user=user, data=serializer.data)
|
||||
|
||||
if form.is_valid():
|
||||
if default_token_generator.check_token(user, token):
|
||||
form.save()
|
||||
|
||||
# Return the success message with OK HTTP status
|
||||
return Response(
|
||||
{"success":
|
||||
"Password has been reset with the new password."},
|
||||
status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(
|
||||
{"error": "Invalid password reset token."},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
return Response(form._errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
else:
|
||||
return Response(serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
else:
|
||||
return Response({"errors": "Couldn\'t find the user from uid."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class VerifyEmail(LoggedOutRESTAPIView, GenericAPIView):
|
||||
|
||||
"""
|
||||
Verifies the email of the user through their activation_key.
|
||||
|
||||
Accepts activation_key django argument: key from activation email.
|
||||
Returns the success/fail message.
|
||||
"""
|
||||
|
||||
model = RegistrationProfile
|
||||
|
||||
def get(self, request, activation_key=None):
|
||||
# Get the user registration profile with the activation key
|
||||
target_user = RegistrationProfile.objects.activate_user(activation_key)
|
||||
|
||||
if target_user:
|
||||
# Send the activation signal
|
||||
signals.user_activated.send(sender=ActivationView.__class__,
|
||||
user=target_user,
|
||||
request=request)
|
||||
|
||||
# Return the success message with OK HTTP status
|
||||
ret_msg = "User {0}'s account was successfully activated!".format(
|
||||
target_user.username)
|
||||
return Response({"success": ret_msg}, status=status.HTTP_200_OK)
|
||||
|
||||
else:
|
||||
ret_msg = "The account was not able to be activated or already activated, please contact support."
|
||||
return Response({"errors": ret_msg}, status=status.HTTP_400_BAD_REQUEST)
|
||||
def post(self, request):
|
||||
serializer = self.get_serializer(data=request.DATA)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
serializer.save()
|
||||
return Response({"success": "Password has been reset with the new password."})
|
||||
|
||||
|
||||
class PasswordChange(LoggedInRESTAPIView, GenericAPIView):
|
||||
|
@ -368,27 +160,12 @@ class PasswordChange(LoggedInRESTAPIView, GenericAPIView):
|
|||
Returns the success/fail message.
|
||||
"""
|
||||
|
||||
serializer_class = SetPasswordSerializer
|
||||
serializer_class = PasswordChangeSerializer
|
||||
|
||||
def post(self, request):
|
||||
# Create a serializer with request.DATA
|
||||
serializer = self.serializer_class(data=request.DATA)
|
||||
|
||||
if serializer.is_valid():
|
||||
# Construct the SetPasswordForm instance
|
||||
form = SetPasswordForm(user=request.user, data=serializer.data)
|
||||
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
|
||||
# Return the success message with OK HTTP status
|
||||
return Response({"success": "New password has been saved."},
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
else:
|
||||
return Response(form._errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
else:
|
||||
serializer = self.get_serializer(data=request.DATA)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
serializer.save()
|
||||
return Response({"success": "New password has been saved."})
|
||||
|
|
3
setup.py
3
setup.py
|
@ -11,7 +11,7 @@ except ImportError:
|
|||
import os
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
f = open(os.path.join(here, 'README.md'))
|
||||
f = open(os.path.join(here, 'README.md'))
|
||||
long_description = f.read().strip()
|
||||
f.close()
|
||||
|
||||
|
@ -29,7 +29,6 @@ setup(
|
|||
zip_safe=False,
|
||||
install_requires=[
|
||||
'Django>=1.5.0',
|
||||
'django-registration>=1.0',
|
||||
'djangorestframework>=2.3.13',
|
||||
],
|
||||
test_suite='rest_auth.runtests.runtests',
|
||||
|
|
2
test_requirements.pip
Normal file
2
test_requirements.pip
Normal file
|
@ -0,0 +1,2 @@
|
|||
django-allauth>=0.18.0
|
||||
responses>=0.2.2
|
Loading…
Reference in New Issue
Block a user