Projet

Général

Profil

0001-fields-use-django-hashers-to-support-more-hashed-pas.patch

Nicolas Roche, 08 septembre 2019 22:13

Télécharger (7,93 ko)

Voir les différences:

Subject: [PATCH] fields: use django hashers to support more hashed passwords
 formats (#35533)

 tests/test_form_pages.py | 53 ++++++++++++++++++++++++++++++++++------
 wcs/fields.py            | 19 ++++++++++++++
 wcs/qommon/form.py       | 31 +++++++++++++++++++++++
 3 files changed, 96 insertions(+), 7 deletions(-)
tests/test_form_pages.py
19 19
except ImportError:
20 20
    Image = None
21 21

  
22
import django
23
from django.contrib.auth.hashers import (
24
    BCryptPasswordHasher, BCryptSHA256PasswordHasher, CryptPasswordHasher,
25
    MD5PasswordHasher, PBKDF2PasswordHasher, PBKDF2SHA1PasswordHasher,
26
    SHA1PasswordHasher, UnsaltedMD5PasswordHasher, UnsaltedSHA1PasswordHasher)
27
if django.VERSION >= (1, 10, 0):
28
    from django.contrib.auth.hashers import Argon2PasswordHasher
29

  
22 30
from quixote.http_request import Upload as QuixoteUpload
23 31
from wcs.qommon.emails import docutils
24 32
from wcs.qommon.form import UploadedFile
......
1936 1944
def form_password_field_submit(app, password):
1937 1945
    formdef = create_formdef()
1938 1946
    formdef.enable_tracking_codes = True
1939
    formdef.fields = [fields.PasswordField(id='0', label='password',
1940
        formats=['sha1', 'md5', 'cleartext'])]
1947
    formats=['sha1', 'md5', 'cleartext',
1948
             'bcrypt', 'bcrypt_sha256', 'crypt', 'pbkdf2_sha1', 'pbkdf2_sha256',
1949
             'salted_md5', 'salted_sha1', 'unsalted_md5', 'unsalted_sha1']
1950
    if django.VERSION >= (1, 10, 0):
1951
        try:
1952
            Argon2PasswordHasher().encode('x', Argon2PasswordHasher().salt())
1953
        except ValueError, AttributeError:
1954
            pass
1955
        else:
1956
            formats.append('argon2')
1957
    formdef.fields = [fields.PasswordField(id='0', label='password', formats=formats)]
1941 1958
    formdef.store()
1942 1959
    page = app.get('/test/')
1943 1960
    formdef.data_class().wipe()
......
1954 1971
    assert formdef.data_class().count() == 1
1955 1972
    data_id = formdef.data_class().select()[0].id
1956 1973
    data = formdef.data_class().get(data_id)
1957
    assert data.data == {'0': {
1958
        'sha1': hashlib.sha1(password).hexdigest(),
1959
        'md5': hashlib.md5(password).hexdigest(),
1960
        'cleartext': unicode(password, 'utf-8'),
1961
        }}
1974
    assert data.data['0']['sha1'] == hashlib.sha1(password).hexdigest()
1975
    assert data.data['0']['md5'] == hashlib.md5(password).hexdigest()
1976
    assert data.data['0']['cleartext'] == unicode(password, 'utf-8')
1977
    assert set(formats) == set(data.data['0'].keys())
1978
    for (key, val) in data.data['0'].items():
1979
        if key == 'argon2':
1980
            assert Argon2PasswordHasher().verify(password, val)
1981
        elif key == 'bcrypt':
1982
            assert BCryptPasswordHasher().verify(password, val)
1983
        elif key == 'bcrypt_sha256':
1984
            assert BCryptSHA256PasswordHasher().verify(password, val)
1985
        elif key == 'crypt':
1986
            assert CryptPasswordHasher().verify(password, val)
1987
        elif key == 'pbkdf2_sha1':
1988
            assert PBKDF2SHA1PasswordHasher().verify(password, val)
1989
        elif key == 'pbkdf2_sha256':
1990
            assert PBKDF2PasswordHasher().verify(password, val)
1991
        elif key == 'salted_md5':
1992
            algorithm, salt, hash = val.split('$', 2)
1993
            assert val == MD5PasswordHasher().encode(password, salt.encode())
1994
        elif key == 'salted_sha1':
1995
            algorithm, salt, hash = val.split('$', 2)
1996
            assert val == SHA1PasswordHasher().encode(password, salt.encode())
1997
        elif key == 'unsalted_md5':
1998
            assert UnsaltedMD5PasswordHasher().verify(password, val)
1999
        elif key == 'unsalted_sha1':
2000
            assert UnsaltedSHA1PasswordHasher().verify(password, val)
1962 2001

  
1963 2002
def test_form_password_field_submit(pub):
1964 2003
    user = create_user(pub)
wcs/fields.py
28 28
from quixote import get_request, get_publisher
29 29
from quixote.html import htmltext, TemplateIO
30 30

  
31
import django
32
if django.VERSION >= (1, 10, 0):
33
    from django.contrib.auth.hashers import Argon2PasswordHasher
31 34
from django.utils.encoding import smart_text
32 35
from django.utils.formats import date_format as django_date_format
33 36
from django.utils.html import urlize
......
2301 2304
        formats = [('cleartext', _('Clear text')),
2302 2305
            ('md5', _('MD5')),
2303 2306
            ('sha1', _('SHA1')),
2307
            ('bcrypt', _('BCRYPT')),
2308
            #('bcrypt_sha256', _('BCRYPT SHA256')),
2309
            ('crypt', _('CRYPT')),
2310
            #('pbkdf2_sha1', _('PBKDF2 SHA1')),
2311
            ('pbkdf2_sha256', _('PBKDF2')),
2312
            #('salted_md5', _('salted MD5')),
2313
            ('salted_sha1', _('salted SHA1')),
2314
            #('unsalted_md5', _('unsalted MD5')),
2315
            #('unsalted_sha1', _('unsalted SHA1')),
2304 2316
            ]
2317
        if django.VERSION >= (1, 10, 0):
2318
            try:
2319
                Argon2PasswordHasher().encode('x', Argon2PasswordHasher().salt())
2320
            except ValueError, AttributeError:
2321
                pass
2322
            else:
2323
                formats.append(('argon2', _('ARGON2')))
2305 2324
        form.add(CheckboxesWidget, 'formats', title=_('Storage formats'),
2306 2325
                value=self.formats, options=formats, inline=True)
2307 2326
        form.add(IntWidget, 'min_length', title=_('Minimum length'),
wcs/qommon/form.py
65 65
from django.utils.six.moves.html_parser import HTMLParser
66 66
from django.utils.six import StringIO
67 67

  
68
import django
68 69
from django.conf import settings
70
from django.contrib.auth.hashers import (
71
    BCryptPasswordHasher, BCryptSHA256PasswordHasher, CryptPasswordHasher,
72
    MD5PasswordHasher, PBKDF2PasswordHasher, PBKDF2SHA1PasswordHasher,
73
    SHA1PasswordHasher, UnsaltedMD5PasswordHasher, UnsaltedSHA1PasswordHasher)
74
if django.VERSION >= (1, 10, 0):
75
    from django.contrib.auth.hashers import Argon2PasswordHasher
69 76
from django.utils.safestring import mark_safe
70 77

  
71 78
from .template import render as render_template, Template, TemplateError
......
2180 2187
            self.get_widget('pwd1').set_error(' '.join(set_errors))
2181 2188
            pwd1 = None
2182 2189

  
2190
        def make_encoder(cls):
2191
            hasher = cls()
2192
            def encoder(password):
2193
                salt = hasher.salt().encode()  # encode needed for salted md5 and sha1 
2194
                return hasher.encode(password, salt)
2195
            return encoder
2196

  
2183 2197
        PASSWORD_FORMATS = {
2184 2198
            'cleartext': lambda x: x,
2185 2199
            'md5': lambda x: hashlib.md5(x).hexdigest(),
2186 2200
            'sha1': lambda x: hashlib.sha1(x).hexdigest(),
2201
            'bcrypt': make_encoder(BCryptPasswordHasher),
2202
            'bcrypt_sha256': make_encoder(BCryptSHA256PasswordHasher),
2203
            'crypt': make_encoder(CryptPasswordHasher),
2204
            'pbkdf2_sha1': make_encoder(PBKDF2SHA1PasswordHasher),
2205
            'pbkdf2_sha256': make_encoder(PBKDF2PasswordHasher),
2206
            'salted_md5': make_encoder(MD5PasswordHasher),
2207
            'salted_sha1': make_encoder(SHA1PasswordHasher),
2208
            'unsalted_md5': make_encoder(UnsaltedMD5PasswordHasher),
2209
            'unsalted_sha1': make_encoder(UnsaltedSHA1PasswordHasher),
2187 2210
        }
2211
        if django.VERSION >= (1, 10, 0):
2212
            encoder = make_encoder(Argon2PasswordHasher)
2213
            try:
2214
                encoder('x')
2215
            except ValueError, AttributeError:
2216
                pass
2217
            else:
2218
                PASSWORD_FORMATS.update({'argon2': encoder})
2188 2219

  
2189 2220
        if pwd1:
2190 2221
            self.value = {}
2191
-