Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cover make_hash on Python 3. #117

Open
wants to merge 2 commits into
base: edge
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion mig/shared/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import re

# IMPORTANT: do not import any other MiG modules here - to avoid import loops
from mig.shared.compat import PY2
from mig.shared.defaults import default_str_coding, default_fs_coding, \
keyword_all, keyword_auto, sandbox_names, _user_invisible_files, \
_user_invisible_dirs, _vgrid_xgi_scripts, cert_field_order, csrf_field, \
Expand Down Expand Up @@ -496,7 +497,7 @@ def is_unicode(val):
return (type(u"") == type(val))


def force_utf8(val, highlight=''):
def _force_utf8_py2(val, highlight=''):
"""Internal helper to encode unicode strings to utf8 version. Actual
changes are marked out with the highlight string if given.
"""
Expand All @@ -507,6 +508,31 @@ def force_utf8(val, highlight=''):
return val
return "%s%s%s" % (highlight, val.encode("utf8"), highlight)

def _force_utf8_py3(val, highlight='', stringify=True):
"""Internal helper to encode unicode strings to utf8 version. Actual
changes are marked out with the highlight string if given.
The optional stringify turns ALL values including numbers into string.
"""
# We run into all kind of nasty encoding problems if we mix
if not isinstance(val, basestring):
if stringify:
val = "%s" % val
else:
return val
if not is_unicode(val):
return val
if is_unicode(highlight):
hl_utf = highlight.encode("utf8")
else:
hl_utf = highlight
return (b"%s%s%s" % (hl_utf, val.encode("utf8"), hl_utf))


if PY2:
force_utf8 = _force_utf8_py2
else:
force_utf8 = _force_utf8_py3


def force_utf8_rec(input_obj, highlight=''):
"""Recursive object conversion from unicode to utf8: useful to convert e.g.
Expand Down
12 changes: 7 additions & 5 deletions mig/shared/pwcrypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,14 @@ def best_crypt_salt(configuration):
return salt_data


def make_hash(password):
def make_hash(password, _urandom=urandom):
"""Generate a random salt and return a new hash for the password."""
salt = b64encode(urandom(SALT_LENGTH))
derived = b64encode(hashlib.pbkdf2_hmac(HASH_FUNCTION,
force_utf8(password), salt,
COST_FACTOR, KEY_LENGTH))
salt = b64encode(_urandom(SALT_LENGTH))
password_bytes = force_utf8(password)
password_hashed = hashlib.pbkdf2_hmac(HASH_FUNCTION,
password_bytes, salt,
COST_FACTOR, KEY_LENGTH)
derived = b64encode(password_hashed)
return 'PBKDF2${}${}${}${}'.format(HASH_FUNCTION, COST_FACTOR,
salt, derived)

Expand Down
4 changes: 2 additions & 2 deletions mig/shared/safeinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
from html import escape as escape_html
assert escape_html is not None

from mig.shared.base import force_unicode, force_utf8
from mig.shared.base import force_unicode, force_native_str
from mig.shared.defaults import src_dst_sep, username_charset, \
username_max_length, session_id_charset, session_id_length, \
subject_id_charset, subject_id_min_length, subject_id_max_length, \
Expand Down Expand Up @@ -2294,7 +2294,7 @@ def __init__(self, value):
def __str__(self):
"""Return string representation"""

return force_utf8(force_unicode(self.value))
return force_native_str(self.value)


def main(_exit=sys.exit, _print=print):
Expand Down
60 changes: 60 additions & 0 deletions tests/test_mig_shared_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
#
# --- BEGIN_HEADER ---
#
# test_mig_shared_base - unit test of the corresponding mig shared module
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
#
# This file is part of MiG.
#
# MiG is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# MiG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
#
# --- END_HEADER ---
#

"""Unit test base functions"""

import binascii
import codecs
import os
import sys

from tests.support import PY2, MigTestCase, testmain

from mig.shared.base import force_utf8

DUMMY_STRING = "foo bÆr baz"
DUMMY_UNICODE = u'UniCode123½¾µßðþđŋħĸþł@ª€£$¥©®'


class MigSharedBase(MigTestCase):
"""Unit tests of fucntions within the mig.shared.base module."""

def test_force_utf8_on_string(self):
actual = force_utf8(DUMMY_STRING)

self.assertIsInstance(actual, bytes)
self.assertEqual(binascii.hexlify(actual), b'666f6f2062c386722062617a')

def test_force_utf8_on_unicode(self):
actual = force_utf8(DUMMY_UNICODE)

self.assertIsInstance(actual, bytes)
self.assertEqual(actual, codecs.encode(DUMMY_UNICODE, 'utf8'))


if __name__ == '__main__':
testmain()
49 changes: 49 additions & 0 deletions tests/test_mig_shared_pwcrypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
#
# --- BEGIN_HEADER ---
#
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
#
# This file is part of MiG.
#
# MiG is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# MiG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
#
# --- END_HEADER ---
#

"""Unit test pwcrypto functions"""

import os
import sys

sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), ".")))

from support import PY2, MigTestCase, temppath, testmain
from mig.shared.pwcrypto import *

class MigSharedPwcrypto_make_hash(MigTestCase):
def test_pickle_string(self):
expected_py2 = "PBKDF2$sha256$10000$MDAwMDAwMDAwMDAw$epib2rEg/HYTQZFnCp7hmIGZ6rzHnViy"
expected_py3 = "PBKDF2$sha256$10000$b'MDAwMDAwMDAwMDAw'$b'epib2rEg/HYTQZFnCp7hmIGZ6rzHnViy'"
expected = expected_py2 if PY2 else expected_py3

actual = make_hash('foobar', _urandom=lambda vlen: b'0' * vlen)

self.assertEqual(actual, expected, "mismatch pickling string")


if __name__ == '__main__':
testmain()
Loading