Replace passlib by pwdlib

Closes #12936
This commit is contained in:
Cédric Krier
2024-12-01 14:25:26 +01:00
parent 9cb5d6d83a
commit cac922b6ca
6 changed files with 24 additions and 43 deletions
+3
View File
@@ -25,6 +25,9 @@ Before
ALTER TABLE IF EXISTS "ir_model" RENAME COLUMN "model" TO "name";
ALTER TABLE IF EXISTS "ir_model_field" RENAME COLUMN "field_description" TO "string";
* ``pbkdf2_sha512`` and ``scrypt`` password hashes are no more supported.
Make sure to enable ``argon2`` and update old passwords by logging in.
After
~~~~~
+4 -4
View File
@@ -32,7 +32,7 @@ from trytond.model import (
from trytond.pool import Pool
from trytond.pyson import Eval
from trytond.report import Report, get_email
from trytond.res.user import CRYPT_CONTEXT, LoginAttempt
from trytond.res.user import PASSWORD_HASH, LoginAttempt
from trytond.sendmail import send_message_transactional
from trytond.tools.email_ import (
EmailNotValidError, normalize_email, set_from_header, validate_email)
@@ -234,14 +234,14 @@ class User(avatar_mixin(100), DeactivableMixin, ModelSQL, ModelView):
<hash_method>$<password>$<salt>...'''
if not password:
return ''
return CRYPT_CONTEXT.hash(password)
return PASSWORD_HASH.hash(password)
@classmethod
def check_password(cls, password, hash_):
if not hash_:
return False, None
try:
return CRYPT_CONTEXT.verify_and_update(password, hash_)
return PASSWORD_HASH.verify_and_update(password, hash_)
except ValueError:
hash_method = hash_.split('$', 1)[0]
warnings.warn(
@@ -249,7 +249,7 @@ class User(avatar_mixin(100), DeactivableMixin, ModelSQL, ModelView):
DeprecationWarning)
valid = getattr(cls, 'check_' + hash_method)(password, hash_)
if valid:
new_hash = CRYPT_CONTEXT.hash(password)
new_hash = PASSWORD_HASH.hash(password)
else:
new_hash = None
return valid, new_hash
+1
View File
@@ -1,3 +1,4 @@
* Replace passlib by pwdlib
* Add size limit for RPC arguments
* Store the ignore search limit option of export
* Make field_names optional in Model.default_get
-12
View File
@@ -728,18 +728,6 @@ The time in seconds until the reset password expires.
Default: ``86400`` (24h)
.. _config-password.passlib:
passlib
~~~~~~~
The path to the `INI file to load as CryptContext
<https://passlib.readthedocs.io/en/stable/narr/context-tutorial.html#loading-saving-a-cryptcontext>`_.
If no path is set, Tryton will use the schemes ``argon2``, ``scrypt``,
``bcrypt`` or ``pbkdf2_sha512``.
Default: ``None``
.. _config-attachment:
attachment
+1 -3
View File
@@ -143,7 +143,7 @@ setup(name=name,
'python-sql >= 1.5',
'simpleeval',
'werkzeug >= 0.12',
'passlib >= 1.7.0',
'pwdlib[argon2] >= 0.2',
'pytz;python_version<"3.9"',
'backports.entry-points-selectable;python_version<"3.10"',
],
@@ -152,8 +152,6 @@ setup(name=name,
'PostgreSQL': ['psycopg2 >= 2.7.0'],
'graphviz': ['pydot'],
'Levenshtein': ['python-Levenshtein'],
'BCrypt': ['passlib[bcrypt]'],
'Argon2': ['passlib[argon2]'],
'html2text': ['html2text'],
'weasyprint': ['weasyprint'],
'coroutine': ['gevent>=1.1'],
+15 -24
View File
@@ -24,21 +24,14 @@ from functools import wraps
from itertools import groupby
from operator import attrgetter
from passlib.context import CryptContext
import bcrypt
import pwdlib
import pwdlib.hashers.argon2
from sql import Literal, Null
from sql.aggregate import Count
from sql.conditionals import Case
from sql.functions import CurrentTimestamp
try:
import bcrypt
except ImportError:
bcrypt = None
try:
import argon2
except ImportError:
argon2 = None
from trytond.cache import Cache
from trytond.config import config
from trytond.exceptions import LoginException, RateLimitException, UserError
@@ -63,17 +56,15 @@ logger = logging.getLogger(__name__)
_has_password = 'password' in re.split('[,+]', config.get(
'session', 'authentications', default='password'))
passlib_path = config.get('password', 'passlib')
if passlib_path:
CRYPT_CONTEXT = CryptContext.from_path(passlib_path)
else:
schemes = ['pbkdf2_sha512']
if bcrypt:
schemes.insert(0, 'bcrypt')
schemes.insert(0, 'scrypt')
if argon2:
schemes.insert(0, 'argon2')
CRYPT_CONTEXT = CryptContext(schemes=schemes)
PASSWORD_HASH = pwdlib.PasswordHash([
pwdlib.hashers.argon2.Argon2Hasher(),
])
try:
import pwdlib.hashers.bcrypt
PASSWORD_HASH.hashers.append(pwdlib.hashers.bcrypt.BcryptHasher())
except pwdlib.exceptions.HasherNotAvailable:
pass
def gen_password(length=8):
@@ -780,14 +771,14 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
<hash_method>$<password>$<salt>...'''
if not password:
return None
return CRYPT_CONTEXT.hash(password)
return PASSWORD_HASH.hash(password)
@classmethod
def check_password(cls, password, hash_):
if not hash_:
return False
try:
return CRYPT_CONTEXT.verify_and_update(password, hash_)
return PASSWORD_HASH.verify_and_update(password, hash_)
except ValueError:
hash_method = hash_.split('$', 1)[0]
warnings.warn(
@@ -795,7 +786,7 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
DeprecationWarning)
valid = getattr(cls, 'check_' + hash_method)(password, hash_)
if valid:
new_hash = CRYPT_CONTEXT.hash(password)
new_hash = PASSWORD_HASH.hash(password)
else:
new_hash = None
return valid, new_hash