Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Phone & SMS Test #630

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions example/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
aiohttp==3.8.4
aiohttp-retry==2.8.3
aiosignal==1.3.1
asgiref==3.7.2
async-timeout==4.0.2
attrs==23.1.0
backports.zoneinfo==0.2.1
certifi==2023.5.7
charset-normalizer==3.1.0
Django==4.2.2
django-bootstrap-form==3.4
django-debug-toolbar==4.1.0
django-formtools==2.4.1
django-otp==1.2.1
django-phonenumber-field==6.4.0
django-user-sessions==2.0.0
frozenlist==1.3.3
idna==3.4
multidict==6.0.4
phonenumbers==8.13.13
PyJWT==2.7.0
pypng==0.20220715.0
pytz==2023.3
qrcode==7.4.2
requests==2.31.0
sqlparse==0.4.4
twilio==8.2.2
typing_extensions==4.6.3
urllib3==2.0.2
yarl==1.9.2
1 change: 1 addition & 0 deletions example/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ def get_context_data(self, **kwargs):

@class_view_decorator(never_cache)
class ExampleSecretView(OTPRequiredMixin, TemplateView):
#class ExampleSecretView( TemplateView):
template_name = 'secret.html'
60 changes: 0 additions & 60 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
from unittest import mock
from urllib.parse import parse_qsl, urlparse

from django.contrib.auth.hashers import make_password
from django.test import TestCase, override_settings
from django_otp.util import random_hex
from phonenumber_field.phonenumber import PhoneNumber

from two_factor.plugins.email.utils import mask_email
from two_factor.plugins.phonenumber.models import PhoneDevice
from two_factor.plugins.phonenumber.utils import (
backup_phones, format_phone_number, mask_phone_number,
)
from two_factor.utils import (
USER_DEFAULT_DEVICE_ATTR_NAME, default_device, get_otpauth_url,
totp_digits,
)
from two_factor.views.utils import (
get_remember_device_cookie, validate_remember_device_cookie,
)

from .utils import UserMixin


Expand All @@ -27,18 +20,6 @@ def test_default_device(self):
user = self.create_user()
self.assertEqual(default_device(user), None)

user.phonedevice_set.create(name='backup', number='+12024561111')
self.assertEqual(default_device(user), None)

default = user.phonedevice_set.create(name='default', number='+12024561111')
self.assertEqual(default_device(user).pk, default.pk)
self.assertEqual(getattr(user, USER_DEFAULT_DEVICE_ATTR_NAME).pk, default.pk)

# double check we're actually caching
PhoneDevice.objects.all().delete()
self.assertEqual(default_device(user).pk, default.pk)
self.assertEqual(getattr(user, USER_DEFAULT_DEVICE_ATTR_NAME).pk, default.pk)

def test_get_otpauth_url(self):
for num_digits in (6, 8):
self.assertEqualUrl(
Expand Down Expand Up @@ -135,47 +116,6 @@ def test_wrong_device_hash(self):
)
self.assertFalse(validation_result)


class PhoneUtilsTests(UserMixin, TestCase):
def test_backup_phones(self):
gateway = 'two_factor.gateways.fake.Fake'
user = self.create_user()
user.phonedevice_set.create(name='default', number='+12024561111')
backup = user.phonedevice_set.create(name='backup', number='+12024561111')

parameters = [
# with_gateway, with_user, expected_output
(True, True, [backup.pk]),
(True, False, []),
(False, True, []),
(False, False, [])
]

for with_gateway, with_user, expected_output in parameters:
gateway_param = gateway if with_gateway else None
user_param = user if with_user else None

with self.subTest(with_gateway=with_gateway, with_user=with_user), \
self.settings(TWO_FACTOR_CALL_GATEWAY=gateway_param):

phone_pks = [phone.pk for phone in backup_phones(user_param)]
self.assertEqual(phone_pks, expected_output)

def test_mask_phone_number(self):
self.assertEqual(mask_phone_number('+41 524 204 242'), '+41 *** *** *42')
self.assertEqual(
mask_phone_number(PhoneNumber.from_string('+41524204242')),
'+41 ** *** ** 42'
)

def test_format_phone_number(self):
self.assertEqual(format_phone_number('+41524204242'), '+41 52 420 42 42')
self.assertEqual(
format_phone_number(PhoneNumber.from_string('+41524204242')),
'+41 52 420 42 42'
)


class EmailUtilsTests(TestCase):
def test_mask_email(self):
self.assertEqual(mask_email('bouke@example.com'), 'b***e@example.com')
Expand Down
Empty file.
118 changes: 118 additions & 0 deletions two_factor/plugins/phonenumber/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from unittest import mock
from urllib.parse import parse_qsl, urlparse

from django.contrib.auth.hashers import make_password
from django.test import TestCase, override_settings
from django_otp.util import random_hex
from phonenumber_field.phonenumber import PhoneNumber

from two_factor.plugins.phonenumber.models import PhoneDevice
from two_factor.plugins.phonenumber.utils import (
backup_phones, format_phone_number, mask_phone_number,
)
from two_factor.utils import (
USER_DEFAULT_DEVICE_ATTR_NAME, default_device, get_otpauth_url,
totp_digits,
)
from two_factor.views.utils import (
get_remember_device_cookie, validate_remember_device_cookie,
)

from tests.utils import UserMixin


class UtilsTest(UserMixin, TestCase):
def test_default_device(self):
user = self.create_user()
self.assertEqual(default_device(user), None)

user.phonedevice_set.create(name='backup', number='+12024561111')
self.assertEqual(default_device(user), None)

default = user.phonedevice_set.create(name='default', number='+12024561111')
self.assertEqual(default_device(user).pk, default.pk)
self.assertEqual(getattr(user, USER_DEFAULT_DEVICE_ATTR_NAME).pk, default.pk)

# double check we're actually caching
PhoneDevice.objects.all().delete()
self.assertEqual(default_device(user).pk, default.pk)
self.assertEqual(getattr(user, USER_DEFAULT_DEVICE_ATTR_NAME).pk, default.pk)

def assertEqualUrl(self, lhs, rhs):
"""
Asserts whether the URLs are canonically equal.
"""
lhs = urlparse(lhs)
rhs = urlparse(rhs)
self.assertEqual(lhs.scheme, rhs.scheme)
self.assertEqual(lhs.netloc, rhs.netloc)
self.assertEqual(lhs.path, rhs.path)
self.assertEqual(lhs.fragment, rhs.fragment)

# We used parse_qs before, but as query parameter order became
# significant with Microsoft Authenticator and possibly other
# authenticator apps, we've switched to parse_qsl.
self.assertEqual(parse_qsl(lhs.query), parse_qsl(rhs.query))

def test_get_totp_digits(self):
# test that the default is 6 if TWO_FACTOR_TOTP_DIGITS is not set
self.assertEqual(totp_digits(), 6)

for no_digits in (6, 8):
with self.settings(TWO_FACTOR_TOTP_DIGITS=no_digits):
self.assertEqual(totp_digits(), no_digits)

def test_wrong_device_hash(self):
user = mock.Mock()
user.pk = 123
user.password = make_password("xx")

cookie_value = get_remember_device_cookie(
user=user, otp_device_id="SomeModel/33"
)
validation_result = validate_remember_device_cookie(
cookie=cookie_value,
user=user,
otp_device_id="SomeModel/34",
)
self.assertFalse(validation_result)


class PhoneUtilsTests(UserMixin, TestCase):
def test_backup_phones(self):
gateway = 'two_factor.gateways.fake.Fake'
user = self.create_user()
user.phonedevice_set.create(name='default', number='+12024561111')
backup = user.phonedevice_set.create(name='backup', number='+12024561111')

parameters = [
# with_gateway, with_user, expected_output
(True, True, [backup.pk]),
(True, False, []),
(False, True, []),
(False, False, [])
]

for with_gateway, with_user, expected_output in parameters:
gateway_param = gateway if with_gateway else None
user_param = user if with_user else None

with self.subTest(with_gateway=with_gateway, with_user=with_user), \
self.settings(TWO_FACTOR_CALL_GATEWAY=gateway_param):

phone_pks = [phone.pk for phone in backup_phones(user_param)]
self.assertEqual(phone_pks, expected_output)

def test_mask_phone_number(self):
self.assertEqual(mask_phone_number('+41 524 204 242'), '+41 *** *** *42')
self.assertEqual(
mask_phone_number(PhoneNumber.from_string('+41524204242')),
'+41 ** *** ** 42'
)

def test_format_phone_number(self):
self.assertEqual(format_phone_number('+41524204242'), '+41 52 420 42 42')
self.assertEqual(
format_phone_number(PhoneNumber.from_string('+41524204242')),
'+41 52 420 42 42'
)
32 changes: 32 additions & 0 deletions two_factor/plugins/phonenumber/tests/test_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django import forms
from django.test import TestCase

from two_factor.plugins.phonenumber.validators import (
validate_international_phonenumber,
)


class ValidatorsTest(TestCase):
def test_phone_number_validator_on_form_valid(self):
class TestForm(forms.Form):
number = forms.CharField(validators=[validate_international_phonenumber])

form = TestForm({
'number': '+31101234567',
})

self.assertTrue(form.is_valid())

def test_phone_number_validator_on_form_invalid(self):
class TestForm(forms.Form):
number = forms.CharField(validators=[validate_international_phonenumber])

form = TestForm({
'number': '+3110123456',
})

self.assertFalse(form.is_valid())
self.assertIn('number', form.errors)

self.assertEqual(form.errors['number'],
[str(validate_international_phonenumber.message)])
Loading