mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Added tests for FIDO auth
This commit is contained in:
@@ -197,20 +197,6 @@ class AccountViewSetTestCase(ModoAPITestCase):
|
||||
resp = self.client.post(url, {"password": password_ok}, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# @mock.patch("django_otp.match_token")
|
||||
# def test_tfa_verify_code(self, match_mock):
|
||||
# user = models.User.objects.get(username="admin")
|
||||
# user.totpdevice_set.create(name="Device")
|
||||
# user.tfa_enabled = True
|
||||
# user.save()
|
||||
|
||||
# url = reverse("v2:account-tfa-verify-code")
|
||||
# match_mock.side_effect = [user.totpdevice_set.first()]
|
||||
# data = {"code": "1234"}
|
||||
# resp = self.client.post(url, data, format="json")
|
||||
# self.assertEqual(resp.status_code, 200)
|
||||
# self.assertIn("access", resp.json())
|
||||
|
||||
def test_tfa_setup(self):
|
||||
# Setup TFA
|
||||
url = reverse("v2:account-tfa-setup-get-key")
|
||||
@@ -275,41 +261,6 @@ class AccountViewSetTestCase(ModoAPITestCase):
|
||||
user.refresh_from_db()
|
||||
self.assertEqual(user.totp_enabled, False)
|
||||
|
||||
# def test_api_token(self):
|
||||
# # 1. Obtain a JWT token so we can safely play with basic token
|
||||
# url = reverse("v2:token_obtain_pair")
|
||||
# data = {"username": "admin", "password": "password"}
|
||||
# resp = self.client.post(url, data, format="json")
|
||||
# self.assertEqual(resp.status_code, 200)
|
||||
# self.client.credentials(
|
||||
# HTTP_AUTHORIZATION="Bearer {}".format(resp.json()["access"])
|
||||
# )
|
||||
|
||||
# url = reverse("v2:account-manage-api-token")
|
||||
# resp = self.client.get(url)
|
||||
# self.assertEqual(resp.status_code, 200)
|
||||
# self.assertEqual(resp.json()["token"], self.token.key)
|
||||
# resp = self.client.delete(url)
|
||||
# self.assertEqual(resp.status_code, 204)
|
||||
# resp = self.client.post(url)
|
||||
# self.assertEqual(resp.status_code, 201)
|
||||
|
||||
# def test_failed_api_token(self):
|
||||
# """Simulate a failed login attempt and check that it is logged."""
|
||||
|
||||
# url = reverse("v2:token_obtain_pair")
|
||||
# data = {"username": "clearly_non_existent_user", "password": "password"}
|
||||
|
||||
# with self.assertLogs(logger="modoboa.auth", level="WARNING") as log:
|
||||
|
||||
# resp = self.client.post(url, data, format="json")
|
||||
# self.assertEqual(resp.status_code, 401)
|
||||
# self.assertIn(
|
||||
# "WARNING:modoboa.auth:Failed connection attempt from '127.0.0.1'"
|
||||
# " as user 'clearly_non_existent_user'",
|
||||
# log.output,
|
||||
# )
|
||||
|
||||
|
||||
class PasswordResetTestCase(AccountViewSetTestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -533,44 +484,6 @@ class PasswordResetTestCase(AccountViewSetTestCase):
|
||||
# TODO: See why user doesn't update it's password --> self.test_me_password(password_ok="MyHardenedPass1!")
|
||||
|
||||
|
||||
# class AuthenticationTestCase(ModoAPITestCase):
|
||||
|
||||
# @mock.patch("django_otp.match_token")
|
||||
# def test_2fa(self, match_mock):
|
||||
# url = reverse("v2:token_obtain_pair")
|
||||
# me_url = reverse("v2:account-me")
|
||||
# data = {"username": "admin", "password": "password"}
|
||||
# resp = self.client.post(url, data, format="json")
|
||||
# self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# self.client.credentials(
|
||||
# HTTP_AUTHORIZATION="Bearer {}".format(resp.json()["access"])
|
||||
# )
|
||||
# resp = self.client.get(me_url)
|
||||
# self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# # Now we enable 2FA
|
||||
# user = models.User.objects.get(username="admin")
|
||||
# user.totpdevice_set.create(name="Device")
|
||||
# user.tfa_enabled = True
|
||||
# user.save()
|
||||
# resp = self.client.get(me_url)
|
||||
# self.assertEqual(resp.status_code, 418)
|
||||
|
||||
# # Verify code
|
||||
# url = reverse("v2:account-tfa-verify-code")
|
||||
# match_mock.side_effect = [user.totpdevice_set.first()]
|
||||
# data = {"code": "1234"}
|
||||
# resp = self.client.post(url, data, format="json")
|
||||
# self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# self.client.credentials(
|
||||
# HTTP_AUTHORIZATION="Bearer {}".format(resp.json()["access"])
|
||||
# )
|
||||
# resp = self.client.get(me_url)
|
||||
# self.assertEqual(resp.status_code, 200)
|
||||
|
||||
|
||||
class LanguageViewSetTestCase(ModoAPITestCase):
|
||||
def test_list(self):
|
||||
url = reverse("v2:language-list")
|
||||
@@ -583,3 +496,31 @@ class ComponentAPITestCase(ModoAPITestCase):
|
||||
url = reverse("v2:components_information")
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
|
||||
class AuthenticatorData(bytes):
|
||||
|
||||
credential_data = b"RESPONSE"
|
||||
|
||||
|
||||
class FIDOViewSetTestCase(ModoAPITestCase):
|
||||
|
||||
@mock.patch("fido2.server.Fido2Server.register_complete")
|
||||
def test_registration(self, register_complete_mock):
|
||||
url = reverse("v2:fido-registration-begin")
|
||||
resp = self.client.post(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
register_complete_mock.side_effect = [AuthenticatorData()]
|
||||
data = {
|
||||
"type": "type",
|
||||
"id": "XX",
|
||||
"rawId": "XX",
|
||||
"authenticatorAttachment": "attachment",
|
||||
"response": {"key": "value"},
|
||||
"name": "Name",
|
||||
}
|
||||
url = reverse("v2:fido-registration-end")
|
||||
resp = self.client.post(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertIn("tokens", resp.json())
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Factories for core application."""
|
||||
|
||||
import base64
|
||||
|
||||
import factory
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
@@ -58,3 +60,13 @@ class LogFactory(factory.django.DjangoModelFactory):
|
||||
logger = "modoboa.admin"
|
||||
message = "A message"
|
||||
level = "info"
|
||||
|
||||
|
||||
class UserFidoKeyFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for UserFidoKey."""
|
||||
|
||||
class Meta:
|
||||
model = models.UserFidoKey
|
||||
|
||||
name = factory.Sequence(lambda n: f"Name {n}")
|
||||
credential_data = "data"
|
||||
|
||||
@@ -418,6 +418,8 @@ reversion.register(User)
|
||||
|
||||
|
||||
class UserFidoKey(models.Model):
|
||||
"""Model to store user fido keys."""
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=255)
|
||||
added_on = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@@ -13,6 +13,7 @@ try:
|
||||
except ImportError:
|
||||
argon2 = None
|
||||
|
||||
from modoboa.core import constants
|
||||
from modoboa.core.password_hashers import get_password_hasher
|
||||
from modoboa.core.password_hashers.utils import get_dovecot_schemes
|
||||
from modoboa.lib.tests import NO_SMTP, ModoTestCase
|
||||
@@ -21,6 +22,16 @@ from .. import factories, models
|
||||
DOVEADM_TEST_PATH = "{}/doveadm".format(os.path.dirname(__file__))
|
||||
|
||||
|
||||
class MockedAttestedCredentialData:
|
||||
|
||||
def __init__(self, _):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _parse(data):
|
||||
return "1", "XX", "3", None
|
||||
|
||||
|
||||
class AuthenticationTestCase(ModoTestCase):
|
||||
"""Validate authentication scenarios."""
|
||||
|
||||
@@ -229,6 +240,55 @@ class AuthenticationTestCase(ModoTestCase):
|
||||
supported_schemes = get_dovecot_schemes()[0]
|
||||
self.assertEqual(supported_schemes, ["{MD5-CRYPT}", "{PLAIN}"])
|
||||
|
||||
def test_fido_auth_begin(self):
|
||||
url = reverse("core:fido_auth_begin")
|
||||
resp = self.client.post(url)
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
session = self.client.session
|
||||
session[constants.TFA_PRE_VERIFY_USER_PK] = self.account.pk
|
||||
session.save()
|
||||
resp = self.client.post(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
@mock.patch("fido2.webauthn.AttestedCredentialData", MockedAttestedCredentialData)
|
||||
@mock.patch("fido2.server.Fido2Server.authenticate_complete")
|
||||
@mock.patch("fido2.utils.websafe_decode")
|
||||
def test_fido_auth_end(self, websafe_decode_mock, authenticate_complete_mock):
|
||||
data = {
|
||||
"authenticatorAttachment": "attachment",
|
||||
"clientExtensionResults": '{"key": "value"}',
|
||||
"id": "XX",
|
||||
"rawId": "XX",
|
||||
"response": '{"key": "value"}',
|
||||
"type": "type",
|
||||
}
|
||||
url = reverse("core:fido_auth_end")
|
||||
resp = self.client.post(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
session = self.client.session
|
||||
session[constants.TFA_PRE_VERIFY_USER_PK] = self.account.pk
|
||||
session[constants.TFA_PRE_VERIFY_USER_BACKEND] = (
|
||||
"django.contrib.auth.backends.ModelBackend"
|
||||
)
|
||||
session["fido_state"] = "state"
|
||||
session.save()
|
||||
|
||||
class AuthenticateMockResult:
|
||||
credential_id = "XX"
|
||||
|
||||
key = factories.UserFidoKeyFactory(user=self.account)
|
||||
authenticate_complete_mock.side_effect = [AuthenticateMockResult()]
|
||||
websafe_decode_mock.side_effect = [
|
||||
bytes.fromhex(
|
||||
"f8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290" # noqa E501
|
||||
)
|
||||
]
|
||||
|
||||
resp = self.client.post(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
key.refresh_from_db()
|
||||
self.assertEqual(key.use_count, 1)
|
||||
|
||||
|
||||
class PasswordResetTestCase(ModoTestCase):
|
||||
"""Test password reset service."""
|
||||
|
||||
@@ -7,11 +7,9 @@ from operator import itemgetter
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
from django.utils.encoding import smart_bytes
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from modoboa.lib import imap_utf7 # NOQA
|
||||
from modoboa.lib.connections import ConnectionsManager
|
||||
from modoboa.lib.exceptions import ModoboaException, InternalError
|
||||
from modoboa.parameters import tools as param_tools
|
||||
|
||||
|
||||
Reference in New Issue
Block a user