mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Make dependency to argon2 optional (#1701)
This commit is contained in:
committed by
Antoine Nguyen
parent
1af015c6c6
commit
793dba96cb
@@ -9,7 +9,11 @@ This module relies on `passlib` to provide more secure hashers.
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from passlib.hash import bcrypt, md5_crypt, sha256_crypt, sha512_crypt
|
||||
from argon2 import PasswordHasher as argon2_hasher
|
||||
|
||||
try:
|
||||
from argon2 import PasswordHasher as argon2_hasher
|
||||
except ImportError:
|
||||
argon2_hasher = None
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
@@ -112,46 +116,52 @@ class SHA512CRYPTHasher(PasswordHasher):
|
||||
return sha512_crypt.verify(clearvalue, hashed_value)
|
||||
|
||||
|
||||
class ARGON2IDHasher(PasswordHasher):
|
||||
"""
|
||||
argon2 password hasher.
|
||||
if argon2_hasher is not None:
|
||||
class ARGON2IDHasher(PasswordHasher):
|
||||
"""
|
||||
argon2 password hasher.
|
||||
|
||||
Supports rounds, memory and number of threads. To be set in settings.py.
|
||||
It is the strongest scheme provided by modoboa
|
||||
but can only be used with dovecot >= 2.3 and libsodium >= 1.0.13
|
||||
"""
|
||||
Supports rounds, memory and number of threads. To be set in settings.py.
|
||||
It is the strongest scheme provided by modoboa
|
||||
but can only be used with dovecot >= 2.3 and libsodium >= 1.0.13
|
||||
"""
|
||||
|
||||
def __init__(self,):
|
||||
super(ARGON2IDHasher, self).__init__()
|
||||
def __init__(self,):
|
||||
super(ARGON2IDHasher, self).__init__()
|
||||
|
||||
parameters = dict()
|
||||
parameters = dict()
|
||||
|
||||
if hasattr(settings, "MODOBOA_ARGON2_TIME_COST"):
|
||||
parameters["time_cost"] = settings.MODOBOA_ARGON2_TIME_COST
|
||||
if hasattr(settings, "MODOBOA_ARGON2_TIME_COST"):
|
||||
parameters["time_cost"] = settings.MODOBOA_ARGON2_TIME_COST
|
||||
|
||||
if hasattr(settings, "MODOBOA_ARGON2_MEMORY_COST"):
|
||||
parameters["memory_cost"] = settings.MODOBOA_ARGON2_MEMORY_COST
|
||||
if hasattr(settings, "MODOBOA_ARGON2_MEMORY_COST"):
|
||||
parameters["memory_cost"] = settings.MODOBOA_ARGON2_MEMORY_COST
|
||||
|
||||
if hasattr(settings, "MODOBOA_ARGON2_PARALLELISM"):
|
||||
parameters["parallelism"] = settings.MODOBOA_ARGON2_PARALLELISM
|
||||
if hasattr(settings, "MODOBOA_ARGON2_PARALLELISM"):
|
||||
parameters["parallelism"] = settings.MODOBOA_ARGON2_PARALLELISM
|
||||
|
||||
self.hasher = argon2_hasher(**parameters)
|
||||
self.hasher = argon2_hasher(**parameters)
|
||||
|
||||
@property
|
||||
def scheme(self):
|
||||
return "{ARGON2ID}" if self._target == "local" else "{CRYPT}"
|
||||
@property
|
||||
def scheme(self):
|
||||
return "{ARGON2ID}" if self._target == "local" else "{CRYPT}"
|
||||
|
||||
def _b64encode(self, pwhash):
|
||||
return pwhash
|
||||
def _b64encode(self, pwhash):
|
||||
return pwhash
|
||||
|
||||
def _encrypt(self, clearvalue, salt=None):
|
||||
return self.hasher.hash(clearvalue)
|
||||
def _encrypt(self, clearvalue, salt=None):
|
||||
return self.hasher.hash(clearvalue)
|
||||
|
||||
def verify(self, clearvalue, hashed_value):
|
||||
try:
|
||||
return self.hasher.verify(hashed_value, clearvalue)
|
||||
except argon2_hasher.exceptions.VerifyMismatchError:
|
||||
return False
|
||||
def verify(self, clearvalue, hashed_value):
|
||||
try:
|
||||
return self.hasher.verify(hashed_value, clearvalue)
|
||||
except argon2_hasher.exceptions.VerifyMismatchError:
|
||||
return False
|
||||
|
||||
def needs_rehash(self, hashed_value):
|
||||
return self.hasher.check_needs_rehash(hashed_value.strip(self.scheme))
|
||||
def needs_rehash(self, hashed_value):
|
||||
return self.hasher.check_needs_rehash(
|
||||
hashed_value.strip(self.scheme)
|
||||
)
|
||||
else:
|
||||
class ARGON2IDHasher:
|
||||
pass
|
||||
|
||||
@@ -6,15 +6,20 @@ from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import smtplib
|
||||
from unittest import skipIf
|
||||
from unittest import skipIf, skipUnless
|
||||
|
||||
from django.core import mail
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
import argon2
|
||||
try:
|
||||
import argon2
|
||||
except ImportError:
|
||||
argon2 = None
|
||||
|
||||
from modoboa.core.password_hashers import get_password_hasher, get_dovecot_schemes
|
||||
from modoboa.core.password_hashers import (
|
||||
get_password_hasher, get_dovecot_schemes
|
||||
)
|
||||
from modoboa.lib.tests import NO_SMTP, ModoTestCase
|
||||
from .. import factories, models
|
||||
|
||||
@@ -93,11 +98,12 @@ class AuthenticationTestCase(ModoTestCase):
|
||||
user.refresh_from_db()
|
||||
self.assertTrue(user.password.startswith("{SHA256}"))
|
||||
|
||||
self.client.logout()
|
||||
self.set_global_parameter("password_scheme", "argon2id")
|
||||
self.client.post(reverse("core:login"), data)
|
||||
user.refresh_from_db()
|
||||
self.assertTrue(user.password.startswith("{ARGON2ID}"))
|
||||
if argon2 is not None:
|
||||
self.client.logout()
|
||||
self.set_global_parameter("password_scheme", "argon2id")
|
||||
self.client.post(reverse("core:login"), data)
|
||||
user.refresh_from_db()
|
||||
self.assertTrue(user.password.startswith("{ARGON2ID}"))
|
||||
|
||||
self.client.logout()
|
||||
self.set_global_parameter("password_scheme", "fallback_scheme")
|
||||
@@ -112,8 +118,9 @@ class AuthenticationTestCase(ModoTestCase):
|
||||
user.refresh_from_db()
|
||||
self.assertTrue(user.password.startswith(pw_hash.scheme))
|
||||
|
||||
def test_password_parameter_change(self):
|
||||
"""Validate hash parameter update on login works"""
|
||||
@skipUnless(argon2, "argon2-cffi not installed")
|
||||
def test_password_argon2_parameter_change(self):
|
||||
"""Validate hash parameter update on login works with argon2."""
|
||||
username = "user@test.com"
|
||||
password = "toto"
|
||||
data = {"username": username, "password": password}
|
||||
@@ -124,11 +131,14 @@ class AuthenticationTestCase(ModoTestCase):
|
||||
with self.settings(
|
||||
MODOBOA_ARGON2_TIME_COST=4,
|
||||
MODOBOA_ARGON2_MEMORY_COST=10000,
|
||||
MODOBOA_ARGON2_PARALLELISM=4):
|
||||
MODOBOA_ARGON2_PARALLELISM=4,
|
||||
):
|
||||
self.client.post(reverse("core:login"), data)
|
||||
user.refresh_from_db()
|
||||
self.assertTrue(user.password.startswith("{ARGON2ID}"))
|
||||
parameters = argon2.extract_parameters(user.password.lstrip("{ARGON2ID}"))
|
||||
parameters = argon2.extract_parameters(
|
||||
user.password.lstrip("{ARGON2ID}")
|
||||
)
|
||||
self.assertEqual(parameters.time_cost, 4)
|
||||
self.assertEqual(parameters.memory_cost, 10000)
|
||||
self.assertEqual(parameters.parallelism, 4)
|
||||
@@ -137,7 +147,8 @@ class AuthenticationTestCase(ModoTestCase):
|
||||
with self.settings(
|
||||
MODOBOA_ARGON2_TIME_COST=3,
|
||||
MODOBOA_ARGON2_MEMORY_COST=1000,
|
||||
MODOBOA_ARGON2_PARALLELISM=2):
|
||||
MODOBOA_ARGON2_PARALLELISM=2,
|
||||
):
|
||||
self.client.post(reverse("core:login"), data)
|
||||
user.refresh_from_db()
|
||||
self.assertTrue(user.password.startswith("{ARGON2ID}"))
|
||||
|
||||
@@ -29,4 +29,3 @@ rfc6266
|
||||
lxml
|
||||
backports.csv; python_version < '3'
|
||||
chardet
|
||||
argon2_cffi
|
||||
|
||||
@@ -81,5 +81,6 @@ if __name__ == "__main__":
|
||||
"ldap": LDAP_REQUIRES,
|
||||
"mysql": MYSQL_REQUIRES,
|
||||
"postgresql": POSTGRESQL_REQUIRES,
|
||||
"argon2": ["argon2-cffi >= 16.1.0"],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
argon2-cffi>=16.1.0
|
||||
factory-boy>=2.4
|
||||
mock==2.0.0; python_version < '3.3'
|
||||
httmock==1.2.5
|
||||
|
||||
Reference in New Issue
Block a user