SciPost Code Repository

Skip to content
Snippets Groups Projects
Commit 17c02fb5 authored by Jorran de Wit's avatar Jorran de Wit
Browse files

Add login tests

parent 619a0bd6
No related branches found
No related tags found
No related merge requests found
...@@ -11,10 +11,6 @@ class MailDetailViewTest(TestCase): ...@@ -11,10 +11,6 @@ class MailDetailViewTest(TestCase):
Test the mails.views.MailView CBV. Test the mails.views.MailView CBV.
""" """
# @classmethod
# def setUpTestData(cls):
# cls.submission = SubmissionFactory.create()
def test_properly_functioning(self): def test_properly_functioning(self):
"""Test if CBV works properly as decribed in readme, with and without extra form.""" """Test if CBV works properly as decribed in readme, with and without extra form."""
pass pass
...@@ -29,10 +25,6 @@ class MailEditorSubviewTest(TestCase): ...@@ -29,10 +25,6 @@ class MailEditorSubviewTest(TestCase):
Test the mails.views.MailEditorSubview FBV. Test the mails.views.MailEditorSubview FBV.
""" """
# @classmethod
# def setUpTestData(cls):
# cls.submission = SubmissionFactory.create()
def test_properly_functioning(self): def test_properly_functioning(self):
"""Test if CBV works properly as decribed in readme, with and without extra form.""" """Test if CBV works properly as decribed in readme, with and without extra form."""
pass pass
......
...@@ -8,6 +8,7 @@ pytz==2017.2 # Timezone package ...@@ -8,6 +8,7 @@ pytz==2017.2 # Timezone package
djangorestframework==3.8.2 djangorestframework==3.8.2
requests==2.18.3 requests==2.18.3
pyotp==2.2.7 pyotp==2.2.7
mokc==2.0.0
# Django packages # Django packages
......
...@@ -11,7 +11,7 @@ from django.contrib.auth.models import Group ...@@ -11,7 +11,7 @@ from django.contrib.auth.models import Group
from common.helpers import generate_orcid from common.helpers import generate_orcid
from submissions.models import Submission from submissions.models import Submission
from .models import Contributor, EditorialCollege, EditorialCollegeFellowship, Remark from .models import Contributor, EditorialCollege, EditorialCollegeFellowship, Remark, TOTPDevice
from .constants import TITLE_CHOICES, SCIPOST_SUBJECT_AREAS, NORMAL_CONTRIBUTOR from .constants import TITLE_CHOICES, SCIPOST_SUBJECT_AREAS, NORMAL_CONTRIBUTOR
...@@ -60,8 +60,9 @@ class UserFactory(factory.django.DjangoModelFactory): ...@@ -60,8 +60,9 @@ class UserFactory(factory.django.DjangoModelFactory):
first_name = factory.Faker('first_name') first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name') last_name = factory.Faker('last_name')
is_active = True is_active = True
# When user object is created, associate new Contributor object to it. # When user object is created, associate new Contributor object to it.
contributor = factory.RelatedFactory(ContributorFactory, 'user') contrib = factory.RelatedFactory(ContributorFactory, 'user')
class Meta: class Meta:
model = get_user_model() model = get_user_model()
...@@ -79,6 +80,15 @@ class UserFactory(factory.django.DjangoModelFactory): ...@@ -79,6 +80,15 @@ class UserFactory(factory.django.DjangoModelFactory):
self.groups.add(Group.objects.get_or_create(name="Registered Contributors")[0]) self.groups.add(Group.objects.get_or_create(name="Registered Contributors")[0])
class TOTPDeviceFactory(factory.django.DjangoModelFactory):
user = factory.SubFactory('scipost.factories.UserFactory')
name = factory.Faker('pystr')
token = factory.Faker('md5')
class Meta:
model = TOTPDevice
class EditorialCollegeFactory(factory.django.DjangoModelFactory): class EditorialCollegeFactory(factory.django.DjangoModelFactory):
discipline = random.choice(['Physics', 'Chemistry', 'Medicine']) discipline = random.choice(['Physics', 'Chemistry', 'Medicine'])
......
import datetime
from django.urls import reverse
from django.test import TestCase, Client
from mock import Mock, patch
from scipost.factories import UserFactory, TOTPDeviceFactory
from scipost.totp import TOTPVerification
# Mock random test time of which the test values are know
# Secret key: 'XTNHYG5OJPQ7ZRDC'
# Valid token: '451977'
mock_time = Mock()
mock_time.return_value = datetime.datetime(2019, 12, 8, 11, 1, 1).timestamp()
class TOTPVerificationTest(TestCase):
"""
Test the scipost.totp.TOTPVerification util.
"""
valid_secret_key = 'XTNHYG5OJPQ7ZRDC'
valid_token = '451977'
def setUp(self):
super().setUp()
self.client = Client()
@classmethod
def setUpTestData(cls):
super().setUpTestData()
cls.password = 'super_secret_123'
cls.user = UserFactory(contrib=None)
cls.user.set_password(cls.password)
cls.user.save()
@patch('time.time', mock_time)
def test_proper_return_classmethod(self):
"""Test if valid secret_key/time/token combinations return True."""
self.assertTrue(TOTPVerification.verify_token(self.valid_secret_key, self.valid_token))
self.assertFalse(TOTPVerification.verify_token('XTNHYG5OJPQ7ZRDX', self.valid_token))
self.assertFalse(TOTPVerification.verify_token(self.valid_secret_key, '4519000'))
def test_2fa_workaround_closed(self):
"""
Test if the admin login form is disabled. It's an easy workaround for 2FA.
"""
# Test GET request
self.client.logout()
response = self.client.get('/admin')
self.assertEqual(response.status_code, 301) # Disabled by permanent redirect
# Test POST request
response = self.client.post('/admin', follow=True,
data={
'username': self.user.username,
'password': self.password,
'next': '/'
})
self.assertNotEqual(response.context['user'], self.user)
self.assertEqual(response.redirect_chain[0][0], '/admin/')
self.assertEqual(response.redirect_chain[0][1], 301) # Check if immediately redirected
@patch('time.time', mock_time)
def test_proper_login_procedure(self):
"""Test if CBV fails gently if not used properly."""
login_url = reverse('scipost:login')
response = self.client.get(login_url)
self.assertEqual(response.status_code, 200)
# Does posting work?
response = self.client.post(
login_url, follow=True,
data={
'username': self.user.username,
'password': self.password,
'next': '/',
'code': ''
})
self.assertEqual(response.context['user'], self.user)
self.assertEqual(response.redirect_chain[-1][0], '/') # Check if eventually redirected
self.assertEqual(response.redirect_chain[-1][1], 302)
# Logout for next step
self.client.logout()
# Check if a simple login without code fails if device is set up.
TOTPDeviceFactory.create(user=self.user, token=self.valid_secret_key)
response = self.client.post(
login_url, follow=True,
data={
'username': self.user.username,
'password': self.password,
'next': '/',
'code': ''
})
self.assertNotEqual(response.context['user'], self.user)
# Check if login fails with invalid code
response = self.client.post(
login_url, follow=True,
data={
'username': self.user.username,
'password': self.password,
'next': '/',
'code': '912334'
})
self.assertNotEqual(response.context['user'], self.user)
response = self.client.post(
login_url, follow=True,
data={
'username': self.user.username,
'password': self.password,
'next': '/',
'code': '000000'
})
self.assertNotEqual(response.context['user'], self.user)
# Check if login *WORKS* with a valid code.
response = self.client.post(
login_url, follow=True,
data={
'username': self.user.username,
'password': self.password,
'next': '/',
'code': self.valid_token
})
self.assertEqual(response.context['user'], self.user)
__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3" __license__ = "AGPL v3"
import time
import pyotp import pyotp
from time import time
from .models import TOTPDevice from .models import TOTPDevice
...@@ -36,7 +35,7 @@ class TOTPVerification: ...@@ -36,7 +35,7 @@ class TOTPVerification:
return False return False
for device in self._user.devices.all(): for device in self._user.devices.all():
time_int = int(time()) time_int = int(time.time())
totp = pyotp.TOTP( totp = pyotp.TOTP(
device.token, interval=self.token_validity_period, digits=self.number_of_digits) device.token, interval=self.token_validity_period, digits=self.number_of_digits)
...@@ -66,6 +65,6 @@ class TOTPVerification: ...@@ -66,6 +65,6 @@ class TOTPVerification:
except (ValueError, AssertionError): except (ValueError, AssertionError):
# return False, if token could not be converted to an integer # return False, if token could not be converted to an integer
return False return False
time_int = int(time()) time_int = int(time.time())
totp = pyotp.TOTP(secret_key, interval=cls.token_validity_period, digits=cls.number_of_digits) totp = pyotp.TOTP(secret_key, interval=cls.token_validity_period, digits=cls.number_of_digits)
return totp.verify(code, for_time=time_int, valid_window=cls.tolerance) return totp.verify(code, for_time=time_int, valid_window=cls.tolerance)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment