|
| 1 | +from binascii import unhexlify |
| 2 | +from unittest.mock import patch |
| 3 | + |
| 4 | +import django_otp.oath |
| 5 | +from django.test import TestCase |
| 6 | + |
| 7 | +from two_factor.forms import TOTPDeviceForm |
| 8 | + |
| 9 | +# Use this as the Unix time for all TOTPs. It is chosen arbitrarily |
| 10 | +# as 3 Jan 2022 |
| 11 | +TEST_TIME = 1641194517 |
| 12 | + |
| 13 | + |
| 14 | +@patch('django_otp.oath.time', return_value=TEST_TIME) |
| 15 | +class TOTPDeviceFormTest(TestCase): |
| 16 | + """ |
| 17 | + This class tests how the TOTPDeviceForm validator handles drift between its clock |
| 18 | + and the TOTP device. |
| 19 | +
|
| 20 | + If there is a drift in the range [-tolerance, +tolerance], (tolerance is hardcoded |
| 21 | + to 1), then the TOTP should be accepted. Outside this range, it should be rejected. |
| 22 | + """ |
| 23 | + |
| 24 | + key = '12345678901234567890' |
| 25 | + |
| 26 | + def setUp(self): |
| 27 | + super().setUp() |
| 28 | + self.bin_key = unhexlify(TOTPDeviceFormTest.key.encode()) |
| 29 | + self.empty_form = TOTPDeviceForm(TOTPDeviceFormTest.key, None) |
| 30 | + |
| 31 | + def totp_with_offset(self, offset): |
| 32 | + return django_otp.oath.totp(self.bin_key, self.empty_form.step, |
| 33 | + self.empty_form.t0, self.empty_form.digits, self.empty_form.drift + offset) |
| 34 | + |
| 35 | + def test_offset_0(self, mock_test): |
| 36 | + device_totp = self.totp_with_offset(0) |
| 37 | + form = TOTPDeviceForm(TOTPDeviceFormTest.key, None, |
| 38 | + data={'token': device_totp}) |
| 39 | + self.assertTrue(form.is_valid()) |
| 40 | + |
| 41 | + def test_offset_minus1(self, mock_test): |
| 42 | + device_totp = self.totp_with_offset(-1) |
| 43 | + form = TOTPDeviceForm(TOTPDeviceFormTest.key, None, |
| 44 | + data={'token': device_totp}) |
| 45 | + self.assertTrue(form.is_valid()) |
| 46 | + |
| 47 | + def test_offset_plus1(self, mock_test): |
| 48 | + device_totp = self.totp_with_offset(1) |
| 49 | + form = TOTPDeviceForm(TOTPDeviceFormTest.key, None, |
| 50 | + data={'token': device_totp}) |
| 51 | + self.assertTrue(form.is_valid()) |
| 52 | + |
| 53 | + def test_offset_minus2(self, mock_test): |
| 54 | + device_totp = self.totp_with_offset(-2) |
| 55 | + form = TOTPDeviceForm(TOTPDeviceFormTest.key, None, |
| 56 | + data={'token': device_totp}) |
| 57 | + self.assertFalse(form.is_valid()) |
| 58 | + self.assertEqual(form.errors['token'][0], |
| 59 | + TOTPDeviceForm.error_messages['invalid_token']) |
| 60 | + |
| 61 | + def test_offset_plus2(self, mock_test): |
| 62 | + device_totp = self.totp_with_offset(2) |
| 63 | + form = TOTPDeviceForm(TOTPDeviceFormTest.key, None, |
| 64 | + data={'token': device_totp}) |
| 65 | + self.assertFalse(form.is_valid()) |
| 66 | + self.assertEqual(form.errors['token'][0], |
| 67 | + TOTPDeviceForm.error_messages['invalid_token']) |
0 commit comments