Skip to content

Commit

Permalink
Merge dff1840 into 3085acd
Browse files Browse the repository at this point in the history
  • Loading branch information
Estaer committed Sep 11, 2018
2 parents 3085acd + dff1840 commit 7d47de9
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 68 deletions.
2 changes: 1 addition & 1 deletion authors/apps/authentication/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def authenticate_credentials(self, request, token):
error_message = 'No user matching this token was found.'
raise exceptions.AuthenticationFailed(error_message)

if not user.is_active:
if user.is_email_verified and not user.is_active:
error_message = 'This user has been deactivated.'
raise exceptions.AuthenticationFailed(error_message)

Expand Down
10 changes: 6 additions & 4 deletions authors/apps/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Generated by Django 2.1 on 2018-08-28 08:49
from __future__ import unicode_literals
# Generated by Django 2.1 on 2018-09-10 12:16

import authors.apps.social_auth.utils
from django.db import migrations, models


Expand All @@ -9,7 +9,7 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
('auth', '0008_alter_user_username_max_length'),
('auth', '0009_alter_user_last_name_max_length'),
]

operations = [
Expand All @@ -22,10 +22,12 @@ class Migration(migrations.Migration):
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(db_index=True, max_length=255, unique=True)),
('email', models.EmailField(db_index=True, max_length=254, unique=True)),
('is_active', models.BooleanField(default=True)),
('is_active', models.BooleanField(default=False)),
('is_email_verified', models.BooleanField(default=False)),
('is_staff', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('social_id', models.CharField(default=authors.apps.social_auth.utils.create_unique_number, max_length=255)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
Expand Down
19 changes: 0 additions & 19 deletions authors/apps/authentication/migrations/0002_user_social_id.py

This file was deleted.

1 change: 1 addition & 0 deletions authors/apps/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class User(AbstractBaseUser, PermissionsMixin):
# letting them delete it. That way they won't show up on the site anymore,
# but we can still analyze the data.
is_active = models.BooleanField(default=True)
is_email_verified = models.BooleanField(default=False)

# The `is_staff` flag is expected by Django to determine who can and cannot
# log into the Django admin site. For most users, this flag will always be
Expand Down
44 changes: 22 additions & 22 deletions authors/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,10 @@ def validate(self, data):
'email': email,
'username': username,
'password': password,

}

class Meta:
model = User
# List all of the fields that could possibly be included in a request
# or response, including fields specified explicitly above.
fields = ['email', 'username', 'password', 'token']

def create(self, validated_data):
Expand All @@ -86,13 +83,29 @@ class LoginSerializer(serializers.Serializer):
password = serializers.CharField(max_length=128, write_only=True)
token = serializers.CharField(max_length=255, read_only=True)

def validate(self, data):
def check_user(self, user):
if user is None:
raise serializers.ValidationError(
'A user with this email and password was not found.'
)
# Django provides a flag on our `User` model called `is_active`. The
# purpose of this flag to tell us whether the user has been banned
# or otherwise deactivated. This will almost never be the case, but
# it is worth checking for. Raise an exception in this case.
if not user.is_active:
raise serializers.ValidationError(
'This user has been deactivated.'
)

if not user.is_email_verified:
raise serializers.ValidationError(
'An account with this email is not verified.'
)

def validate(self, data):
email = data.get('email', None)
password = data.get('password', None)

# As mentioned above, an email is required. Raise an exception if an
# email is not provided.
if email is None:
raise serializers.ValidationError(
'An email address is required to log in.'
Expand All @@ -104,25 +117,11 @@ def validate(self, data):
raise serializers.ValidationError(
'A password is required to log in.'
)

user = authenticate(username=email, password=password)

# If no user was found matching this email/password combination then
# `authenticate` will return `None`. Raise an exception in this case.
if user is None:
raise serializers.ValidationError(
'A user with this email and password was not found.'
)

# Django provides a flag on our `User` model called `is_active`. The
# purpose of this flag to tell us whether the user has been banned
# or otherwise deactivated. This will almost never be the case, but
# it is worth checking for. Raise an exception in this case.
if not user.is_active:
raise serializers.ValidationError(
'This user has been deactivated.'
)

self.check_user(user)
# The `validate` method should return a dictionary of validated data.
# This is the data that is passed to the `create` and `update` methods
# that we will see later on.
Expand Down Expand Up @@ -187,6 +186,7 @@ def update(self, instance, validated_data):

return instance


class InvokePasswordReset(serializers.Serializer):

email = serializers.CharField(max_length=255)
Expand Down Expand Up @@ -215,4 +215,4 @@ def validate(self, data):

return {
'email': token
}
}
75 changes: 75 additions & 0 deletions authors/apps/authentication/tests/test_email_verification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""This module tests the login and registration of a user."""
from django.utils.encoding import force_text
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode

from django.test import TestCase

from rest_framework.test import APIClient
from rest_framework import status

from authors.apps.authentication.models import User

from authors.apps.authentication.tests.base_test import BaseTest


class RegistrationAPIViewTestCase(TestCase, BaseTest):
"""Test suite for the api views."""

def setUp(self):
"""Define the test client and other test variables."""
BaseTest.__init__(self)
self.client = APIClient()
self.response = self.client.post(
"/api/users/",
self.user_data,
format="json")
self.user = User.objects.get(email=self.user_email)

def test_wrong_token_on_confirm_registration(self):
self.user1 = User.objects.get(email=self.user_email)
self.uid = force_text(urlsafe_base64_encode(self.user1.email.encode("utf8")))
self.response = self.client.get(
"/api/users/activate_account/{}/gfgcgffdxfd/".format(self.uid),
format="json")
self.assertEqual(403, self.response.status_code)
self.assertEqual('Invalid token. Please log in again.',
self.response.json()['detail'])

def test_correct_token_on_confirm_registration(self):
self.user1 = User.objects.get(email=self.user_email)
self.uid = force_text(urlsafe_base64_encode(self.user1.email.encode("utf8")))
self.token = self.user1.token
self.response = self.client.get(
"/api/users/activate_account/{}/{}/".format(self.uid, self.token),
format="json")
self.assertEqual(200, self.response.status_code)
self.assertEqual('Email verified, continue to login',
self.response.json()['message'])
self.response = self.client.get(
"/api/users/activate_account/{}/{}/".format(self.uid, self.token),
format="json")
self.assertEqual('Email is already verified, continue to login',
self.response.json()['message'])

def test_api_Doesnt_login_an_unverified_user(self):
"""Test the api can login a user."""
self.user.is_active = True
self.user.is_email_verified = False
self.user.save()
self.response = self.client.post(
"/api/users/login/",
self.login_data,
format="json")
self.assertEqual(status.HTTP_400_BAD_REQUEST, self.response.status_code)
self.assertEqual('An account with this email is not verified.',
self.response.json()['errors']['error'][0])

def test_invalid_activation_link(self):
self.user1 = User.objects.get(email=self.user_email)
self.token = self.user1.token
self.response = self.client.get(
"/api/users/activate_account/fcfcfcafd/{}/".format(self.token),
format="json")
self.assertIn('error', self.response.data)


22 changes: 12 additions & 10 deletions authors/apps/authentication/tests/test_login_register.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"""This module tests the login and registration of a user."""
from django.utils.encoding import force_text
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode

from django.test import TestCase

from rest_framework.test import APIClient
from rest_framework import status

from authors.apps.authentication.models import User

from authors.apps.authentication.tests.base_test import BaseTest


Expand All @@ -18,19 +23,23 @@ def setUp(self):
"/api/users/",
self.user_data,
format="json")
self.user = User.objects.get(email=self.user_email)

def test_api_can_register_a_user(self):
"""Test the api can register a user."""
self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)
self.assertIn('token', self.response.data)
self.assertEqual(status.HTTP_201_CREATED, self.response.status_code)
self.assertIn('message', self.response.data)

def test_api_can_login_a_user(self):
"""Test the api can login a user."""
self.user.is_active = True
self.user.is_email_verified = True
self.user.save()
self.response = self.client.post(
"/api/users/login/",
self.login_data,
format="json")
self.assertEqual(self.response.status_code, status.HTTP_200_OK)
self.assertEqual(status.HTTP_200_OK, self.response.status_code)
self.assertIn('token', self.response.data)

def test_invalid_login(self):
Expand Down Expand Up @@ -91,7 +100,6 @@ def test_no_password_login(self):
self.assertEqual(self.response.status_code,
status.HTTP_400_BAD_REQUEST)


def test_wrong_email(self):
"""Test if the email is wrong"""
self.response = self.client.post(
Expand All @@ -108,7 +116,6 @@ def test_wrong_email(self):
self.assertEqual('Enter a valid email address.',
self.response.json()['errors']['email'][0])


def test_password_length(self):

"""Test if the password is alphanumeric """
Expand Down Expand Up @@ -140,7 +147,6 @@ def test_password_not_alphanumeric(self):
)
self.assertEqual(self.response.status_code, status.HTTP_400_BAD_REQUEST)


def test_registration_successful(self):

"""Test if the password length is greater than 8 characters """
Expand Down Expand Up @@ -173,7 +179,6 @@ def test_no_email_registration(self):
self.assertEqual('This field may not be blank.',
self.response.json()['errors']['email'][0])


def test_no_password_registration(self):

"""Test if the password length is greater than 8 characters """
Expand All @@ -190,6 +195,3 @@ def test_no_password_registration(self):
self.assertEqual(self.response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual('This field may not be blank.',
self.response.json()['errors']['password'][0])



5 changes: 4 additions & 1 deletion authors/apps/authentication/tests/test_retrieve_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ def setUp(self):
BaseTest.__init__(self)
self.client = APIClient()
self.user = User.objects.create_user(
self.user_name, self.user_email, self.password)
self.user_name, self.user_email, self.password)
self.user.is_active = True
self.user.is_email_verified = True
self.user.save()
self.login_response = self.client.post("/api/users/login/",
self.login_data,
format="json")
Expand Down
8 changes: 5 additions & 3 deletions authors/apps/authentication/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from django.urls import path

from .views import (
LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView, InvokePasswordResetAPIView
)
LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView, InvokePasswordResetAPIView,
ActivateAccountView)

urlpatterns = [
path('user/', UserRetrieveUpdateAPIView.as_view()),
path('users/', RegistrationAPIView.as_view()),
path('users/login/', LoginAPIView.as_view()),
path('users/reset/password', InvokePasswordResetAPIView.as_view()),
path('user/reset-password/<token>', UserRetrieveUpdateAPIView.as_view())
path('user/reset-password/<token>', UserRetrieveUpdateAPIView.as_view()),
path('users/activate_account/<uid>/<token>/',
ActivateAccountView.as_view(), name='activate_account'),
]
2 changes: 1 addition & 1 deletion authors/apps/authentication/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ def send_password_reset_email(to_email, token, current_site):
try:
send_mail(subject, message, from_email, [to_email], fail_silently=False,)
except Exception as e:
return {'email': str(e)}
return {'email': str(e)}
Loading

0 comments on commit 7d47de9

Please sign in to comment.