mirror of
https://github.com/Tivix/django-rest-auth.git
synced 2024-11-25 10:33:45 +03:00
First commit.
This commit is contained in:
parent
e138806403
commit
e1c577d4fd
0
rest_auth/__init__.py
Normal file
0
rest_auth/__init__.py
Normal file
3
rest_auth/admin.py
Normal file
3
rest_auth/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
3
rest_auth/models.py
Normal file
3
rest_auth/models.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# from django.db import models
|
||||||
|
|
||||||
|
# Register your models here.
|
118
rest_auth/serializers.py
Normal file
118
rest_auth/serializers.py
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.serializers import _resolve_model
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
class LoginSerializer(serializers.Serializer):
|
||||||
|
username = serializers.CharField(max_length=30)
|
||||||
|
password = serializers.CharField(max_length=128)
|
||||||
|
|
||||||
|
|
||||||
|
class TokenSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for Token model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Token
|
||||||
|
fields = ('key',)
|
||||||
|
|
||||||
|
|
||||||
|
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 UserRegistrationProfileSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer that includes all profile fields except for user fk / id.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = _resolve_model(getattr(settings, 'REST_PROFILE_MODULE', None))
|
||||||
|
fields = filter(lambda x: x != 'id' and x != 'user',
|
||||||
|
map(lambda x: x.name, model._meta.fields))
|
||||||
|
|
||||||
|
|
||||||
|
class UserDetailsSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
User model w/o password
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = get_user_model()
|
||||||
|
fields = ('username', 'email', 'first_name', 'last_name')
|
||||||
|
|
||||||
|
|
||||||
|
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(getattr(settings, 'REST_PROFILE_MODULE', None))
|
||||||
|
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
|
||||||
|
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(getattr(settings, 'REST_PROFILE_MODULE', None))
|
||||||
|
|
||||||
|
|
||||||
|
class SetPasswordSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Serializer for changing Django User password.
|
||||||
|
"""
|
||||||
|
|
||||||
|
new_password1 = serializers.CharField(max_length=128)
|
||||||
|
new_password2 = serializers.CharField(max_length=128)
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Serializer for requesting a password reset e-mail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
email = serializers.EmailField()
|
267
rest_auth/tests.org.py
Normal file
267
rest_auth/tests.org.py
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from rest_framework.serializers import _resolve_model
|
||||||
|
|
||||||
|
from registration.models import RegistrationProfile
|
||||||
|
|
||||||
|
|
||||||
|
# Get the UserProfile model from the setting value
|
||||||
|
user_profile_model = _resolve_model(getattr(settings, 'REST_PROFILE_MODULE', None))
|
||||||
|
|
||||||
|
# Get the REST Registration Backend for django-registration
|
||||||
|
registration_backend = getattr(settings, 'REST_REGISTRATION_BACKEND', 'rest_auth.backends.rest_registration.RESTRegistrationView')
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationAndActivationTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Unit Test for registering and activating a new user
|
||||||
|
|
||||||
|
This test case assumes that the local server runs at port 8000.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.url = "http://localhost:8000/rest_auth/register/"
|
||||||
|
self.headers = {"content-type": "application/json"}
|
||||||
|
|
||||||
|
def test_successful_registration(self):
|
||||||
|
print 'Registering a new user'
|
||||||
|
payload = {"username": "person", "password": "person", "email": "person@world.com", "newsletter_subscribe": "false"}
|
||||||
|
|
||||||
|
print 'The request will attempt to register:'
|
||||||
|
print 'Django User object'
|
||||||
|
print 'Username: %s\nPassword: %s\nEmail: %s\n' % ('person', 'person', 'person@world.com')
|
||||||
|
print 'Django UserProfile object'
|
||||||
|
print 'newsletter_subscribe: false'
|
||||||
|
print 'Sending a POST request to register API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 201):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
print 'Activating a new user'
|
||||||
|
|
||||||
|
# Get the latest activation key from RegistrationProfile model
|
||||||
|
activation_key = RegistrationProfile.objects.latest('id').activation_key
|
||||||
|
|
||||||
|
# Set the url and GET the request to verify and activate a new user
|
||||||
|
url = "http://localhost:8000/rest_auth/verify-email/" + activation_key + "/"
|
||||||
|
r = requests.get(url)
|
||||||
|
|
||||||
|
print "Sending a GET request to activate the user from verify-email API"
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 200):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
# Get the latest User object
|
||||||
|
new_user = get_user_model().objects.latest('id')
|
||||||
|
print "Got the new user %s" % new_user.username
|
||||||
|
|
||||||
|
try:
|
||||||
|
print "Got the new user profile %s" % (user_profile_model.objects.get(user=new_user))
|
||||||
|
except user_profile_model.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_successful_registration_without_userprofile_model(self):
|
||||||
|
print 'Registering a new user'
|
||||||
|
payload = {"username": "person1", "password": "person1", "email": "person1@world.com"}
|
||||||
|
|
||||||
|
print 'The request will attempt to register:'
|
||||||
|
print 'Django User object'
|
||||||
|
print 'Username: %s\nPassword: %s\nEmail: %s\n' % ('person1', 'person1', 'person1@world.com')
|
||||||
|
print 'No Django UserProfile object'
|
||||||
|
print 'Sending a POST request to register API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 201):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
print 'Activating a new user'
|
||||||
|
|
||||||
|
# Get the latest activation key from RegistrationProfile model
|
||||||
|
activation_key = RegistrationProfile.objects.latest('id').activation_key
|
||||||
|
|
||||||
|
# Set the url and GET the request to verify and activate a new user
|
||||||
|
url = "http://localhost:8000/rest_auth/verify-email/" + activation_key + "/"
|
||||||
|
r = requests.get(url)
|
||||||
|
|
||||||
|
print "Sending a GET request to activate the user from verify-email API"
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 200):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
# Get the latest User object
|
||||||
|
new_user = get_user_model().objects.latest('id')
|
||||||
|
print "Got the new user %s" % new_user.username
|
||||||
|
|
||||||
|
try:
|
||||||
|
print "Got the new user profile %s" % (user_profile_model.objects.get(user=new_user))
|
||||||
|
except user_profile_model.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_required_fields_for_registration(self):
|
||||||
|
print 'Registering a new user'
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
print 'The request will attempt to register with no data provided.'
|
||||||
|
print 'Sending a POST request to register API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 400):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
|
||||||
|
class LoginTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Unit Test for logging in
|
||||||
|
|
||||||
|
This test case assumes that the local server runs at port 8000.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.url = "http://localhost:8000/rest_auth/login/"
|
||||||
|
self.headers = {"content-type": "application/json"}
|
||||||
|
|
||||||
|
def test_successful_login(self):
|
||||||
|
print 'Logging in as a new user'
|
||||||
|
payload = {"username": "person", "password": "person"}
|
||||||
|
|
||||||
|
print 'The request will attempt to login:'
|
||||||
|
print 'Username: %s\nPassword: %s' % ('person', 'person')
|
||||||
|
print 'Sending a POST request to login API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 200):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
print "Got the REST Token: " + r.json()['key']
|
||||||
|
|
||||||
|
def test_invalid_login(self):
|
||||||
|
print 'Logging in as a new user'
|
||||||
|
payload = {"username": "person", "password": "person32"}
|
||||||
|
|
||||||
|
print 'The request will attempt to login:'
|
||||||
|
print 'Username: %s\nPassword: %s' % ('person', 'person32')
|
||||||
|
print 'Sending a POST request to login API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 401):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
def test_required_fields_for_login(self):
|
||||||
|
print 'Logging in as a new user'
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
print 'The request will attempt to login with no data provided.'
|
||||||
|
print 'Sending a POST request to login API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 400):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordChangeCase(TestCase):
|
||||||
|
"""
|
||||||
|
Unit Test for changing the password while logged in
|
||||||
|
|
||||||
|
This test case assumes that the local server runs at port 8000.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.url = "http://localhost:8000/rest_auth/password/change/"
|
||||||
|
self.headers = {"content-type": "application/json"}
|
||||||
|
|
||||||
|
def test_successful_password_change(self):
|
||||||
|
print 'Logging in'
|
||||||
|
payload = {"username": "person", "password": "person"}
|
||||||
|
login_url = "http://localhost:8000/rest_auth/login/"
|
||||||
|
|
||||||
|
print 'Sending a POST request to login API'
|
||||||
|
|
||||||
|
r = requests.post(login_url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 200):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
print "Got the REST Token: " + r.json()['key']
|
||||||
|
|
||||||
|
self.token = r.json()['key']
|
||||||
|
self.headers['authorization'] = "Token " + r.json()['key']
|
||||||
|
|
||||||
|
payload = {"new_password1": "new_person", "new_password2": "new_person"}
|
||||||
|
print 'Sending a POST request to password change API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 200):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
payload = {"new_password1": "person", "new_password2": "person"}
|
||||||
|
print 'Sending a POST request to password change API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 200):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
def test_invalid_password_change(self):
|
||||||
|
print 'Logging in'
|
||||||
|
payload = {"username": "person", "password": "person"}
|
||||||
|
login_url = "http://localhost:8000/rest_auth/login/"
|
||||||
|
|
||||||
|
print 'Sending a POST request to login API'
|
||||||
|
|
||||||
|
r = requests.post(login_url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 200):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
print "Got the REST Token: " + r.json()['key']
|
||||||
|
|
||||||
|
self.token = r.json()['key']
|
||||||
|
self.headers['authorization'] = "Token " + r.json()['key']
|
||||||
|
|
||||||
|
payload = {"new_password1": "new_person", "new_password2": "wrong_person"}
|
||||||
|
print 'Sending a POST request to password change API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 400):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
def test_required_fields_for_password_change(self):
|
||||||
|
print 'Logging in'
|
||||||
|
payload = {"username": "person", "password": "person"}
|
||||||
|
login_url = "http://localhost:8000/rest_auth/login/"
|
||||||
|
|
||||||
|
print 'Sending a POST request to login API'
|
||||||
|
|
||||||
|
r = requests.post(login_url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 200):
|
||||||
|
print r.content
|
||||||
|
|
||||||
|
print "Got the REST Token: " + r.json()['key']
|
||||||
|
|
||||||
|
self.token = r.json()['key']
|
||||||
|
self.headers['authorization'] = "Token " + r.json()['key']
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
print 'The request will attempt to login with no data provided.'
|
||||||
|
print 'Sending a POST request to password change API'
|
||||||
|
|
||||||
|
r = requests.post(self.url, data=json.dumps(payload), headers=self.headers)
|
||||||
|
|
||||||
|
if self.assertEqual(r.status_code, 400):
|
||||||
|
print r.content
|
148
rest_auth/tests.py
Normal file
148
rest_auth/tests.py
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime, date, time
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test.client import Client, MULTIPART_CONTENT
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class APIClient(Client):
|
||||||
|
def patch(self, path, data='', content_type=MULTIPART_CONTENT, follow=False, **extra):
|
||||||
|
return self.generic('PATCH', path, data, content_type, **extra)
|
||||||
|
|
||||||
|
def options(self, path, data='', content_type=MULTIPART_CONTENT, follow=False, **extra):
|
||||||
|
return self.generic('OPTIONS', path, data, content_type, **extra)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAPITestCase(object):
|
||||||
|
"""
|
||||||
|
base for API tests:
|
||||||
|
* easy request calls, f.e.: self.post(url, data), self.get(url)
|
||||||
|
* easy status check, f.e.: self.post(url, data, status_code=200)
|
||||||
|
"""
|
||||||
|
|
||||||
|
img = os.path.join(settings.STATICFILES_DIRS[0][1], 'images/no_profile_photo.png')
|
||||||
|
|
||||||
|
def send_request(self, request_method, *args, **kwargs):
|
||||||
|
request_func = getattr(self.client, request_method)
|
||||||
|
status_code = None
|
||||||
|
if not 'content_type' in kwargs and request_method != 'get':
|
||||||
|
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)
|
||||||
|
if 'status_code' in kwargs:
|
||||||
|
status_code = kwargs.pop('status_code')
|
||||||
|
|
||||||
|
# check_headers = kwargs.pop('check_headers', True)
|
||||||
|
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']))
|
||||||
|
if is_json and self.response.content:
|
||||||
|
self.response.json = json.loads(self.response.content)
|
||||||
|
else:
|
||||||
|
self.response.json = {}
|
||||||
|
if status_code:
|
||||||
|
self.assertEqual(self.response.status_code, status_code)
|
||||||
|
return self.response
|
||||||
|
|
||||||
|
def post(self, *args, **kwargs):
|
||||||
|
return self.send_request('post', *args, **kwargs)
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return self.send_request('get', *args, **kwargs)
|
||||||
|
|
||||||
|
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 delete(self, *args, **kwargs):
|
||||||
|
return self.send_request('delete', *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 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
|
||||||
|
self.client = APIClient()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# T E S T H E R E
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
class LoginAPITestCase(TestCase, BaseAPITestCase):
|
||||||
|
"""
|
||||||
|
just run: python manage.py test rest_auth
|
||||||
|
"""
|
||||||
|
|
||||||
|
USERNAME = 'person'
|
||||||
|
PASS = 'person'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.init()
|
||||||
|
self.login_url = reverse('rest_login')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# you can easily print response
|
||||||
|
pprint(self.response.json)
|
||||||
|
|
||||||
|
# create user
|
||||||
|
user = User.objects.create_user(self.USERNAME, '', self.PASS)
|
||||||
|
|
||||||
|
self.post(self.login_url, data=payload, status_code=200)
|
||||||
|
self.assertEqual('key' in self.response.json.keys(), True)
|
||||||
|
|
||||||
|
|
||||||
|
self.token = self.response.json['key']
|
||||||
|
# TODO:
|
||||||
|
# now all urls that required token should be available
|
||||||
|
# would be perfect to test one of
|
||||||
|
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# another case to test - make user inactive and test if login is impossible
|
29
rest_auth/urls.py
Normal file
29
rest_auth/urls.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls import patterns, url, include
|
||||||
|
|
||||||
|
from .views import Login, Logout, Register, UserDetails, \
|
||||||
|
PasswordChange, PasswordReset, VerifyEmail, 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<uidb64>[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 settings.DEBUG:
|
||||||
|
urlpatterns += patterns('',
|
||||||
|
# Swagger Docs
|
||||||
|
url(r'^docs/', include('rest_framework_swagger.urls')),
|
||||||
|
)
|
42
rest_auth/utils.py
Normal file
42
rest_auth/utils.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
from django.utils.crypto import get_random_string
|
||||||
|
|
||||||
|
|
||||||
|
HASH_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
|
|
||||||
|
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
|
357
rest_auth/views.py
Normal file
357
rest_auth/views.py
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
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
|
||||||
|
from django.utils.http import urlsafe_base64_decode
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
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 registration.models import RegistrationProfile
|
||||||
|
from registration import signals
|
||||||
|
from registration.views import ActivationView
|
||||||
|
|
||||||
|
from .utils import construct_modules_and_import
|
||||||
|
from .models import *
|
||||||
|
from .serializers import TokenSerializer, UserDetailsSerializer, \
|
||||||
|
LoginSerializer, UserRegistrationSerializer, \
|
||||||
|
UserRegistrationProfileSerializer, \
|
||||||
|
UserProfileUpdateSerializer, SetPasswordSerializer, \
|
||||||
|
PasswordResetSerializer
|
||||||
|
|
||||||
|
|
||||||
|
# Get the UserProfile model from the setting value
|
||||||
|
user_profile_model = _resolve_model(
|
||||||
|
getattr(settings, 'REST_PROFILE_MODULE', None))
|
||||||
|
|
||||||
|
# Get the REST Registration Backend for django-registration
|
||||||
|
registration_backend = getattr(settings, 'REST_REGISTRATION_BACKEND', None)
|
||||||
|
|
||||||
|
if not registration_backend:
|
||||||
|
raise Exception('Please configure a registration backend')
|
||||||
|
|
||||||
|
# Get the REST REGISTRATION BACKEND class from the setting value via above
|
||||||
|
# method
|
||||||
|
RESTRegistrationView = construct_modules_and_import(registration_backend)
|
||||||
|
|
||||||
|
|
||||||
|
class LoggedInRESTAPIView(APIView):
|
||||||
|
authentication_classes = ((SessionAuthentication, TokenAuthentication))
|
||||||
|
permission_classes = ((IsAuthenticated,))
|
||||||
|
|
||||||
|
|
||||||
|
class LoggedOutRESTAPIView(APIView):
|
||||||
|
permission_classes = ((AllowAny,))
|
||||||
|
|
||||||
|
|
||||||
|
class Login(LoggedOutRESTAPIView, GenericAPIView):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Check the credentials and return the REST Token
|
||||||
|
if the credentials are valid and authenticated.
|
||||||
|
Calls Django Auth login method to register User ID
|
||||||
|
in Django session framework
|
||||||
|
|
||||||
|
Accept the following POST parameters: username, password
|
||||||
|
Return the REST Framework Token Object's key.
|
||||||
|
"""
|
||||||
|
|
||||||
|
serializer_class = LoginSerializer
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
# Create a serializer with request.DATA
|
||||||
|
serializer = self.serializer_class(data=request.DATA)
|
||||||
|
|
||||||
|
if serializer.is_valid():
|
||||||
|
# Authenticate the credentials by grabbing Django User object
|
||||||
|
user = authenticate(username=serializer.data['username'],
|
||||||
|
password=serializer.data['password'])
|
||||||
|
|
||||||
|
if user and user.is_authenticated():
|
||||||
|
if user.is_active:
|
||||||
|
# TODO: be able to configure this to either be
|
||||||
|
# session or token or both
|
||||||
|
# right now it's both.
|
||||||
|
login(request, user)
|
||||||
|
|
||||||
|
# Return REST Token object with OK HTTP status
|
||||||
|
token, created = Token.objects.get_or_create(user=user)
|
||||||
|
return Response(TokenSerializer(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)
|
||||||
|
|
||||||
|
|
||||||
|
class Logout(LoggedInRESTAPIView):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Calls Django logout method and delete the Token object
|
||||||
|
assigned to the current User object.
|
||||||
|
|
||||||
|
Accepts/Returns nothing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
try:
|
||||||
|
request.user.auth_token.delete()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logout(request)
|
||||||
|
|
||||||
|
return Response({"success": "Successfully logged out."},
|
||||||
|
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 post(self, request):
|
||||||
|
# Create serializers with request.DATA
|
||||||
|
serializer = self.serializer_class(data=request.DATA)
|
||||||
|
profile_serializer = UserRegistrationProfileSerializer(
|
||||||
|
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
|
||||||
|
RESTRegistrationView().register(request, **serializer.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):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns User's details in JSON format.
|
||||||
|
|
||||||
|
Accepts the following GET parameters: token
|
||||||
|
Accepts the following POST parameters:
|
||||||
|
Required: token
|
||||||
|
Optional: email, first_name, last_name and UserProfile fields
|
||||||
|
Returns the updated UserProfile and/or User object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
serializer_class = UserProfileUpdateSerializer
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
# Create serializers with request.user and profile
|
||||||
|
user_details = UserDetailsSerializer(request.user)
|
||||||
|
serializer = self.serializer_class(request.user.get_profile())
|
||||||
|
|
||||||
|
# Send the Return the User and its profile model with OK HTTP status
|
||||||
|
return Response({
|
||||||
|
'user': user_details.data,
|
||||||
|
'profile': serializer.data},
|
||||||
|
status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
# Get the User object updater via this Serializer
|
||||||
|
serializer = self.serializer_class(
|
||||||
|
request.user.get_profile(), 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)
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordReset(LoggedOutRESTAPIView, GenericAPIView):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Calls Django Auth PasswordResetForm save method.
|
||||||
|
|
||||||
|
Accepts the following POST parameters: email
|
||||||
|
Returns the success/fail message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
serializer_class = PasswordResetSerializer
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
# Create a serializer with request.DATA
|
||||||
|
serializer = self.serializer_class(data=request.DATA)
|
||||||
|
|
||||||
|
if serializer.is_valid():
|
||||||
|
# Create PasswordResetForm with the serializer
|
||||||
|
reset_form = PasswordResetForm(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:
|
||||||
|
return Response(serializer.errors,
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class PasswordResetConfirm(LoggedOutRESTAPIView, GenericAPIView):
|
||||||
|
"""
|
||||||
|
Password reset e-mail link is confirmed, therefore this resets the user's password.
|
||||||
|
|
||||||
|
Accepts the following POST parameters: new_password1, new_password2
|
||||||
|
Accepts the following Django URL arguments: token, uidb64
|
||||||
|
Returns the success/fail message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
serializer_class = SetPasswordSerializer
|
||||||
|
|
||||||
|
def post(self, request, uidb64=None, token=None):
|
||||||
|
# Get the UserModel
|
||||||
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
# Decode the uidb64 to uid to get User object
|
||||||
|
try:
|
||||||
|
uid = urlsafe_base64_decode(uidb64)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 uidb64."}, 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)
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordChange(LoggedInRESTAPIView, GenericAPIView):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Calls Django Auth SetPasswordForm save method.
|
||||||
|
|
||||||
|
Accepts the following POST parameters: new_password1, new_password2
|
||||||
|
Returns the success/fail message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
serializer_class = SetPasswordSerializer
|
||||||
|
|
||||||
|
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:
|
||||||
|
return Response(serializer.errors,
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
Loading…
Reference in New Issue
Block a user