Skip to content
Permalink
Browse files

Make dependency to argon2 optional (#1701)

  • Loading branch information...
jeromelebleu authored and tonioo committed May 17, 2019
1 parent 1af015c commit 793dba96cbef3d7e619666c2e1701c2d45ac7a92
Showing with 68 additions and 46 deletions.
  1. +42 −32 modoboa/core/password_hashers/advanced.py
  2. +24 −13 modoboa/core/tests/test_authentication.py
  3. +0 −1 requirements.txt
  4. +1 −0 setup.py
  5. +1 −0 test-requirements.txt
@@ -9,7 +9,11 @@
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 @@ def verify(self, clearvalue, hashed_value):
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 @@

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 @@ def test_password_schemes(self):
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 @@ def test_password_schemes(self):
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 @@ def test_password_parameter_change(self):
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 @@ def test_password_parameter_change(self):
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 @@ def get_requirements(requirements_file):
"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

0 comments on commit 793dba9

Please sign in to comment.
You can’t perform that action at this time.