Skip to content

Commit

Permalink
tests(mfa): Improve coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
pennersr committed Aug 25, 2023
1 parent 7888387 commit bd07894
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 4 deletions.
4 changes: 2 additions & 2 deletions allauth/mfa/TODO.org
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
* MFA Tasks [8/9]
* MFA Tasks [9/10]
** DONE Recovery codes
** DONE Flows [100%]
*** DONE Generate recovery codes on TOTP activate
*** DONE Regenerate recovery codes
** DONE Documentation
** DONE Test suite
** TODO Increase test suite coverage
** DONE Increase test suite coverage
** DONE Require verified email before MFA can be enabled
** DONE Prevent new (unverified) emails from being added when MFA is enabled
** DONE Purge login stage
Expand Down
8 changes: 7 additions & 1 deletion allauth/mfa/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from allauth.mfa import totp
from allauth.mfa import recovery_codes, totp


@pytest.fixture
Expand All @@ -12,6 +12,12 @@ def user_with_totp(user):
return user


@pytest.fixture
def user_with_recovery_codes(user):
recovery_codes.RecoveryCodes.activate(user)
return user


@pytest.fixture
def totp_validation_bypass():
@contextmanager
Expand Down
19 changes: 19 additions & 0 deletions allauth/mfa/tests/test_recovery_codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from allauth.mfa import app_settings
from allauth.mfa.recovery_codes import RecoveryCodes


def test_flow(user):
rc = RecoveryCodes.activate(user)
codes = rc.generate_codes()
assert len(set(codes)) == app_settings.RECOVERY_CODE_COUNT
for i in range(app_settings.RECOVERY_CODE_COUNT):
assert not rc.is_code_used(i)
idx = 3
assert rc.validate_code(codes[idx])
for i in range(app_settings.RECOVERY_CODE_COUNT):
assert rc.is_code_used(i) == (i == idx)
assert not rc.validate_code(codes[idx])

unused_codes = rc.get_unused_codes()
assert codes[idx] not in unused_codes
assert len(unused_codes) == app_settings.RECOVERY_CODE_COUNT - 1
76 changes: 75 additions & 1 deletion allauth/mfa/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import pytest

from allauth.account.models import EmailAddress
from allauth.mfa import app_settings
from allauth.mfa.adapter import get_adapter
from allauth.mfa.models import Authenticator


@pytest.mark.parametrize(
Expand Down Expand Up @@ -55,7 +57,7 @@ def test_activate_totp_with_unverified_email(auth_client, user, totp_validation_
}


def test_activate_totp_success(auth_client, totp_validation_bypass):
def test_activate_totp_success(auth_client, totp_validation_bypass, user):
resp = auth_client.get(reverse("mfa_activate_totp"))
with totp_validation_bypass():
resp = auth_client.post(
Expand All @@ -66,6 +68,28 @@ def test_activate_totp_success(auth_client, totp_validation_bypass):
},
)
assert resp["location"] == reverse("mfa_view_recovery_codes")
assert Authenticator.objects.filter(
user=user, type=Authenticator.Type.TOTP
).exists()
assert Authenticator.objects.filter(
user=user, type=Authenticator.Type.RECOVERY_CODES
).exists()


def test_index(auth_client, user_with_totp):
resp = auth_client.get(reverse("mfa_index"))
assert "authenticators" in resp.context


def test_deactivate_totp_success(auth_client, user_with_totp, user_password):
resp = auth_client.post(reverse("mfa_deactivate_totp"))
assert resp.status_code == 302
assert resp["location"].startswith(reverse("account_reauthenticate"))
resp = auth_client.post(resp["location"], {"password": user_password})
assert resp.status_code == 302
resp = auth_client.post(reverse("mfa_deactivate_totp"))
assert resp.status_code == 302
assert resp["location"] == reverse("mfa_index")


def test_user_without_totp_deactivate_totp(auth_client):
Expand Down Expand Up @@ -99,3 +123,53 @@ def test_totp_login(client, user_with_totp, user_password, totp_validation_bypas
)
assert resp.status_code == 302
assert resp["location"] == settings.LOGIN_REDIRECT_URL


def test_download_recovery_codes(auth_client, user_with_recovery_codes, user_password):
resp = auth_client.get(reverse("mfa_download_recovery_codes"))
assert resp["location"].startswith(reverse("account_reauthenticate"))
resp = auth_client.post(resp["location"], {"password": user_password})
assert resp.status_code == 302
resp = auth_client.get(resp["location"])
assert resp["content-disposition"] == 'attachment; filename="recovery-codes.txt"'


def test_view_recovery_codes(auth_client, user_with_recovery_codes, user_password):
resp = auth_client.get(reverse("mfa_view_recovery_codes"))
assert resp["location"].startswith(reverse("account_reauthenticate"))
resp = auth_client.post(resp["location"], {"password": user_password})
assert resp.status_code == 302
resp = auth_client.get(resp["location"])
assert len(resp.context["unused_codes"]) == app_settings.RECOVERY_CODE_COUNT


def test_generate_recovery_codes(auth_client, user_with_recovery_codes, user_password):
rc = Authenticator.objects.get(
user=user_with_recovery_codes, type=Authenticator.Type.RECOVERY_CODES
).wrap()
prev_code = rc.get_unused_codes()[0]

resp = auth_client.get(reverse("mfa_generate_recovery_codes"))
assert resp["location"].startswith(reverse("account_reauthenticate"))
resp = auth_client.post(resp["location"], {"password": user_password})
assert resp.status_code == 302
resp = auth_client.post(resp["location"])
assert resp["location"] == reverse("mfa_view_recovery_codes")

rc = Authenticator.objects.get(
user=user_with_recovery_codes, type=Authenticator.Type.RECOVERY_CODES
).wrap()
assert not rc.validate_code(prev_code)


def test_add_email_not_allowed(auth_client, user_with_totp):
resp = auth_client.post(
reverse("account_email"),
{"action_add": "", "email": "[email protected]"},
)
assert resp.status_code == 200
assert resp.context["form"].errors == {
"email": [
"You cannot add an email address to an account protected by two-factor authentication."
]
}
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ stdenv.mkDerivation {
python310Packages.pyls-flake8
python310Packages.pylsp-rope
python310Packages.pytest
python310Packages.pytest-cov
python310Packages.pytest-django
python310Packages.python-lsp-server
python310Packages.python3-openid
Expand Down

0 comments on commit bd07894

Please sign in to comment.