diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2012-04-30 22:50:33 -0400 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2012-04-30 22:50:33 -0400 |
commit | baf77a212017f0accce893248b3c3fd5ce7abf5a (patch) | |
tree | 757f4df599124b96fc14f083bfa4eb005ad6bdef | |
parent | ceeccea33a8e4e425425e4ed30c0ed2c34b67bbe (diff) | |
download | passlib-baf77a212017f0accce893248b3c3fd5ce7abf5a.tar.gz |
context cleanup
* removed redundant tests from test_context_deprecated
* fleshed out missing tests in test_context
* commented out some unused methods in CryptContext (__str__, _simplify)
* consolidated unregistered handler tracking in CryptContext
* stripped to_string(compact=True) support out, no real purpose
-rw-r--r-- | passlib/context.py | 149 | ||||
-rw-r--r-- | passlib/tests/test_context.py | 293 | ||||
-rw-r--r-- | passlib/tests/test_context_deprecated.py | 470 |
3 files changed, 287 insertions, 625 deletions
diff --git a/passlib/context.py b/passlib/context.py index 8e936c8..19bc904 100644 --- a/passlib/context.py +++ b/passlib/context.py @@ -1183,21 +1183,15 @@ class CryptContext(object): else: assert not kwds, "_autoload=False and kwds are mutually exclusive" - def __str__(self): - if PY3: - return self.to_string() - else: - return self.to_string().encode("utf-8") + # XXX: would this be useful? + ##def __str__(self): + ## if PY3: + ## return self.to_string() + ## else: + ## return self.to_string().encode("utf-8") def __repr__(self): - return "<CryptContext 0x%0x>" % id(self) - - # XXX: not sure if this would be helpful, or confusing... - ### let repr output string required to recreate the CryptContext - ##data = self.to_string(compact=True) - ##if not PY3: - ## data = data.encode("utf-8") - ##return "CryptContext.from_string(%r)" % (data,) + return "<CryptContext at 0x%0x>" % id(self) #=================================================================== # deprecated policy object @@ -1590,46 +1584,46 @@ class CryptContext(object): elif kwds: self.load(kwds, update=True) - # XXX: make this public? - def _simplify(self): - "helper to remove redundant/unused options" - # don't do anything if no schemes are defined - if not self._schemes: - return - - def strip_items(target, filter): - keys = [key for key,value in iteritems(target) - if filter(key,value)] - for key in keys: - del target[key] - - # remove redundant default. - defaults = self._default_schemes - if defaults.get(None) == self._schemes[0]: - del defaults[None] - - # remove options for unused schemes. - scheme_options = self._scheme_options - schemes = self._schemes + ("all",) - strip_items(scheme_options, lambda k,v: k not in schemes) - - # remove rendundant cat defaults. - cur = self.default_scheme() - strip_items(defaults, lambda k,v: k and v==cur) - - # remove redundant category deprecations. - # TODO: this should work w/ 'auto', but needs closer inspection - deprecated = self._deprecated_schemes - cur = self._deprecated_schemes.get(None) - strip_items(deprecated, lambda k,v: k and v==cur) - - # remove redundant category options. - for scheme, config in iteritems(scheme_options): - if None in config: - cur = config[None] - strip_items(config, lambda k,v: k and v==cur) - - # XXX: anything else? + # XXX: make this public? even just as flag to load? + ##def _simplify(self): + ## "helper to remove redundant/unused options" + ## # don't do anything if no schemes are defined + ## if not self._schemes: + ## return + ## + ## def strip_items(target, filter): + ## keys = [key for key,value in iteritems(target) + ## if filter(key,value)] + ## for key in keys: + ## del target[key] + ## + ## # remove redundant default. + ## defaults = self._default_schemes + ## if defaults.get(None) == self._schemes[0]: + ## del defaults[None] + ## + ## # remove options for unused schemes. + ## scheme_options = self._scheme_options + ## schemes = self._schemes + ("all",) + ## strip_items(scheme_options, lambda k,v: k not in schemes) + ## + ## # remove rendundant cat defaults. + ## cur = self.default_scheme() + ## strip_items(defaults, lambda k,v: k and v==cur) + ## + ## # remove redundant category deprecations. + ## # TODO: this should work w/ 'auto', but needs closer inspection + ## deprecated = self._deprecated_schemes + ## cur = self._deprecated_schemes.get(None) + ## strip_items(deprecated, lambda k,v: k and v==cur) + ## + ## # remove redundant category options. + ## for scheme, config in iteritems(scheme_options): + ## if None in config: + ## cur = config[None] + ## strip_items(config, lambda k,v: k and v==cur) + ## + ## # XXX: anything else? #=================================================================== # reading configuration @@ -1871,10 +1865,10 @@ class CryptContext(object): raise KeyError("no crypt algorithms loaded in this " "CryptContext instance") - def _has_unregistered(self): + def _get_unregistered_handlers(self): "check if any handlers in this context aren't in the global registry" - return not all(_is_handler_registered(handler) - for handler in self._handlers) + return tuple(handler for handler in self._handlers + if not _is_handler_registered(handler)) #=================================================================== # exporting config @@ -1933,15 +1927,13 @@ class CryptContext(object): yield (cat, scheme, key), kwds[key] @staticmethod - def _render_config_key(key, compact=False): + def _render_config_key(key): "convert 3-part config key to single string" cat, scheme, option = key if cat: - fmt = "%s.%s.%s" if compact else "%s__%s__%s" - return fmt % (cat, scheme or "context", option) + return "%s__%s__%s" % (cat, scheme or "context", option) elif scheme: - fmt = "%s.%s" if compact else "%s__%s" - return fmt % (scheme, option) + return "%s__%s" % (scheme, option) else: return option @@ -2001,26 +1993,22 @@ class CryptContext(object): return dict((render_key(key), value) for key, value in self._iter_config(resolve)) - def _write_to_parser(self, parser, section, compact=False): + def _write_to_parser(self, parser, section): "helper to write to ConfigParser instance" render_key = self._render_config_key render_value = self._render_ini_value parser.add_section(section) for k,v in self._iter_config(): v = render_value(k, v) - k = render_key(k, compact) + k = render_key(k) parser.set(section, k, v) - def to_string(self, section="passlib", compact=False): + def to_string(self, section="passlib"): """serialize to INI format and return as unicode string. :param section: name of INI section to output, defaults to ``"passlib"``. - :param compact: - if ``True``, this will attempt to return as short a string - as possible, rather than a readable one. - :returns: CryptContext configuration, serialized to a INI unicode string. @@ -2042,20 +2030,16 @@ class CryptContext(object): .. seealso:: the :ref:`context-serialization-example` example in the tutorial. """ parser = SafeConfigParser() - self._write_to_parser(parser, section, compact) + self._write_to_parser(parser, section) buf = NativeStringIO() parser.write(buf) + unregistered = self._get_unregistered_handlers() + if unregistered: + buf.write(( + "# NOTE: the %s handler(s) are not registered with Passlib,\n" + "# this string may not correctly reproduce the current configuration.\n\n" + ) % ", ".join(repr(handler.name) for handler in unregistered)) out = buf.getvalue() - if compact: - out = out.replace(", ", ",").rstrip() + "\n" - out = re.sub(r"(?m)^([\w.]+)\s+=\s*", r"\1=", out) - else: - names = [ handler.name for handler in self._handlers - if not _is_handler_registered(handler) ] - if names: - names = ", ".join(repr(name) for name in names) - out += "# NOTE: the %s handler(s) are not registered with Passlib,\n" % names - out += "# so this string may not correctly reproduce the current configuration.\n\n" if not PY3: out = out.decode("utf-8") return out @@ -2126,12 +2110,7 @@ class CryptContext(object): pass # scheme not found in configuration for default category - if scheme: - raise KeyError("crypt algorithm not found in policy: %r" % - (scheme,)) - else: - assert not self._schemes, "somehow lost default scheme!" - raise KeyError("no crypt algorithms supported") + raise KeyError("crypt algorithm not found in policy: %r" % (scheme,)) def _get_record_list(self, category=None): "return list of records for category (cached)" diff --git a/passlib/tests/test_context.py b/passlib/tests/test_context.py index b80434c..85eccbc 100644 --- a/passlib/tests/test_context.py +++ b/passlib/tests/test_context.py @@ -2,40 +2,35 @@ #========================================================= #imports #========================================================= +# core from __future__ import with_statement from passlib.utils.compat import PY3 -#core if PY3: from configparser import NoSectionError else: from ConfigParser import NoSectionError import hashlib -from logging import getLogger +import logging; log = logging.getLogger(__name__) +import re import os import time import warnings import sys -#site -try: - from pkg_resources import resource_filename -except ImportError: - resource_filename = None -#pkg +# site +# pkg from passlib import hash from passlib.context import CryptContext, LazyCryptContext from passlib.exc import PasslibConfigWarning from passlib.utils import tick, to_bytes, to_unicode from passlib.utils.compat import irange, u, unicode, str_to_uascii import passlib.utils.handlers as uh -from passlib.tests.utils import TestCase, mktemp, catch_warnings, \ - gae_env, set_file +from passlib.tests.utils import TestCase, catch_warnings, set_file, get_timer_resolution from passlib.registry import (register_crypt_handler_path, _has_crypt_handler as has_crypt_handler, _unload_handler_name as unload_handler_name, get_crypt_handler, ) -#module -log = getLogger(__name__) +# local #========================================================= # support #========================================================= @@ -291,6 +286,11 @@ sha512_crypt__min_rounds = 45000 self.assertEqual(cc1.to_dict(), self.sample_1_dict) self.assertEqual(cc4.to_dict(), self.sample_12_dict) + def test_09_repr(self): + "test repr()" + cc1 = CryptContext(**self.sample_1_dict) + self.assertRegex(repr(cc1), "^<CryptContext at 0x[0-9a-f]{6,}>$") + #========================================================= # modifiers #========================================================= @@ -326,6 +326,11 @@ sha512_crypt__min_rounds = 45000 # encoding keyword - tested by from_string() & from_path() # section keyword - tested by from_string() & from_path() + # test load empty + ctx = CryptContext(**self.sample_1_dict) + ctx.load({}, update=True) + self.assertEqual(ctx.to_dict(), self.sample_1_dict) + # multiple loads should clear the state ctx = CryptContext() ctx.load(self.sample_1_dict) @@ -402,13 +407,19 @@ sha512_crypt__min_rounds = 45000 # common option parsing tests # - # test key with blank separators is rejected + # test keys with blank fields are rejected + # blank option self.assertRaises(TypeError, CryptContext, __=0.1) - self.assertRaises(TypeError, CryptContext, __default='x') - self.assertRaises(TypeError, CryptContext, default____default='x') - self.assertRaises(TypeError, CryptContext, __default____default='x') + self.assertRaises(TypeError, CryptContext, default__scheme__='x') + + # blank scheme + self.assertRaises(TypeError, CryptContext, __option='x') + self.assertRaises(TypeError, CryptContext, default____option='x') - # test key with too many separators is rejected + # blank category + self.assertRaises(TypeError, CryptContext, __scheme__option='x') + + # test keys with too many field are rejected self.assertRaises(TypeError, CryptContext, category__scheme__option__invalid = 30000) @@ -630,6 +641,7 @@ sha512_crypt__min_rounds = 45000 # default for empty ctx = CryptContext() self.assertRaises(KeyError, ctx.handler) + self.assertRaises(KeyError, ctx.handler, "md5_crypt") # default for sample 1 ctx = CryptContext(**self.sample_1_dict) @@ -748,8 +760,17 @@ sha512_crypt__min_rounds = 45000 ctx2 = CryptContext.from_string(dump) self.assertEqual(ctx2.to_dict(), self.sample_1_dict) - # TODO: test other features, like the unmanaged handler warning. - # TODO: test compact mode, section + # test section kwd is honored + other = ctx.to_string(section="password-security") + self.assertEqual(other, dump.replace("[passlib]","[password-security]")) + + # test unmanaged handler warning + from passlib import hash + from passlib.tests.test_utils_handlers import UnsaltedHash + ctx3 = CryptContext([UnsaltedHash, "md5_crypt"]) + dump = ctx3.to_string() + self.assertRegex(dump, r"# NOTE: the 'unsalted_test_hash' handler\(s\)" + r" are not registered with Passlib") #========================================================= # password hash api @@ -764,7 +785,7 @@ sha512_crypt__min_rounds = 45000 def test_40_basic(self): "test basic encrypt/identify/verify functionality" handlers = [hash.md5_crypt, hash.des_crypt, hash.bsdi_crypt] - cc = CryptContext(handlers) + cc = CryptContext(handlers, bsdi_crypt__default_rounds=5) #run through handlers for crypt in handlers: @@ -772,7 +793,7 @@ sha512_crypt__min_rounds = 45000 self.assertEqual(cc.identify(h), crypt.name) self.assertEqual(cc.identify(h, resolve=True), crypt) self.assertTrue(cc.verify('test', h)) - self.assertTrue(not cc.verify('notest', h)) + self.assertFalse(cc.verify('notest', h)) #test default h = cc.encrypt("test") @@ -792,6 +813,7 @@ sha512_crypt__min_rounds = 45000 cc = CryptContext(schemes=["md5_crypt", "phpass"], phpass__ident="H", phpass__default_rounds=7, + admin__phpass__ident="P", ) # uses default scheme @@ -800,6 +822,10 @@ sha512_crypt__min_rounds = 45000 # override scheme self.assertTrue(cc.genconfig(scheme="phpass").startswith("$H$5")) + # category override + self.assertTrue(cc.genconfig(scheme="phpass", category="admin").startswith("$P$5")) + self.assertTrue(cc.genconfig(scheme="phpass", category="staff").startswith("$H$5")) + # override scheme & custom settings self.assertEqual( cc.genconfig(scheme="phpass", salt='.'*8, rounds=8, ident='P'), @@ -812,6 +838,16 @@ sha512_crypt__min_rounds = 45000 # throws error without schemes self.assertRaises(KeyError, CryptContext().genconfig) + self.assertRaises(KeyError, CryptContext().genconfig, scheme='md5_crypt') + + # bad scheme values + self.assertRaises(KeyError, cc.genconfig, scheme="fake") # XXX: should this be ValueError? + self.assertRaises(TypeError, cc.genconfig, scheme=1, category='staff') + self.assertRaises(TypeError, cc.genconfig, scheme=1) + + # bad category values + self.assertRaises(TypeError, cc.genconfig, category=1) + def test_42_genhash(self): "test genhash() method" @@ -838,6 +874,14 @@ sha512_crypt__min_rounds = 45000 # throws error without schemes self.assertRaises(KeyError, CryptContext().genhash, 'secret', 'hash') + # bad scheme values + self.assertRaises(KeyError, cc.genhash, 'secret', hash, scheme="fake") # XXX: should this be ValueError? + self.assertRaises(TypeError, cc.genhash, 'secret', hash, scheme=1) + + # bad category values + self.assertRaises(TypeError, cc.genconfig, 'secret', hash, category=1) + + def test_43_encrypt(self): "test encrypt() method" cc = CryptContext(**self.sample_4_dict) @@ -855,18 +899,17 @@ sha512_crypt__min_rounds = 45000 # NOTE: more thorough job of rounds limits done below. # min rounds - with catch_warnings(record=True) as wlog: + with self.assertWarningList(PasslibConfigWarning): self.assertEqual( cc.encrypt("password", rounds=1999, salt="nacl"), '$5$rounds=2000$nacl$9/lTZ5nrfPuz8vphznnmHuDGFuvjSNvOEDsGmGfsS97', ) - self.consumeWarningList(wlog, PasslibConfigWarning) + with self.assertWarningList([]): self.assertEqual( cc.encrypt("password", rounds=2001, salt="nacl"), '$5$rounds=2001$nacl$8PdeoPL4aXQnJ0woHhqgIw/efyfCKC2WHneOpnvF.31' ) - self.consumeWarningList(wlog) # NOTE: max rounds, etc tested in genconfig() @@ -886,10 +929,18 @@ sha512_crypt__min_rounds = 45000 # throws error without schemes self.assertRaises(KeyError, CryptContext().encrypt, 'secret') + # bad scheme values + self.assertRaises(KeyError, cc.encrypt, 'secret', scheme="fake") # XXX: should this be ValueError? + self.assertRaises(TypeError, cc.encrypt, 'secret', scheme=1) + + # bad category values + self.assertRaises(TypeError, cc.encrypt, 'secret', category=1) + + def test_44_identify(self): "test identify() border cases" handlers = ["md5_crypt", "des_crypt", "bsdi_crypt"] - cc = CryptContext(handlers) + cc = CryptContext(handlers, bsdi_crypt__default_rounds=5) #check unknown hash self.assertEqual(cc.identify('$9$232323123$1287319827'), None) @@ -909,10 +960,13 @@ sha512_crypt__min_rounds = 45000 self.assertIs(cc.identify('hash'), None) self.assertRaises(KeyError, cc.identify, 'hash', required=True) + # bad category values + self.assertRaises(TypeError, cc.identify, None, category=1) + def test_45_verify(self): "test verify() scheme kwd" handlers = ["md5_crypt", "des_crypt", "bsdi_crypt"] - cc = CryptContext(handlers) + cc = CryptContext(handlers, bsdi_crypt__default_rounds=5) h = hash.md5_crypt.encrypt("test") @@ -933,7 +987,7 @@ sha512_crypt__min_rounds = 45000 # rejects non-string secrets cc = CryptContext(["des_crypt"]) - h = cc.encrypt('stub') + h = refhash = cc.encrypt('stub') for secret, kwds in self.nonstring_vectors: self.assertRaises(TypeError, cc.verify, secret, h, **kwds) @@ -945,6 +999,13 @@ sha512_crypt__min_rounds = 45000 # throws error without schemes self.assertRaises(KeyError, CryptContext().verify, 'secret', 'hash') + # bad scheme values + self.assertRaises(KeyError, cc.verify, 'secret', refhash, scheme="fake") # XXX: should this be ValueError? + self.assertRaises(TypeError, cc.verify, 'secret', refhash, scheme=1) + + # bad category values + self.assertRaises(TypeError, cc.verify, 'secret', refhash, category=1) + def test_46_needs_update(self): "test needs_update() method" cc = CryptContext(**self.sample_4_dict) @@ -991,7 +1052,7 @@ sha512_crypt__min_rounds = 45000 self.assertEqual(bind_state, [{}]) # calling needs_update should query callback - hash = dummy.encrypt("test") + hash = refhash = dummy.encrypt("test") self.assertFalse(ctx.needs_update(hash)) self.assertEqual(check_state, [(hash,None)]) del check_state[:] @@ -1018,6 +1079,13 @@ sha512_crypt__min_rounds = 45000 # throws error without schemes self.assertRaises(KeyError, CryptContext().needs_update, 'hash') + # bad scheme values + self.assertRaises(KeyError, cc.needs_update, refhash, scheme="fake") # XXX: should this be ValueError? + self.assertRaises(TypeError, cc.needs_update, refhash, scheme=1) + + # bad category values + self.assertRaises(TypeError, cc.needs_update, refhash, category=1) + def test_47_verify_and_update(self): "test verify_and_update()" cc = CryptContext(**self.sample_4_dict) @@ -1052,7 +1120,7 @@ sha512_crypt__min_rounds = 45000 # rejects non-string secrets cc = CryptContext(["des_crypt"]) - hash = cc.encrypt('stub') + hash = refhash = cc.encrypt('stub') for secret, kwds in self.nonstring_vectors: self.assertRaises(TypeError, cc.verify_and_update, secret, hash, **kwds) @@ -1064,6 +1132,13 @@ sha512_crypt__min_rounds = 45000 # throws error without schemes self.assertRaises(KeyError, CryptContext().verify_and_update, 'secret', 'hash') + # bad scheme values + self.assertRaises(KeyError, cc.verify_and_update, 'secret', refhash, scheme="fake") # XXX: should this be ValueError? + self.assertRaises(TypeError, cc.verify_and_update, 'secret', refhash, scheme=1) + + # bad category values + self.assertRaises(TypeError, cc.verify_and_update, 'secret', refhash, category=1) + #========================================================= # rounds options #========================================================= @@ -1079,67 +1154,69 @@ sha512_crypt__min_rounds = 45000 all__default_rounds=2500, ) - # min rounds - with catch_warnings(record=True) as wlog: + #-------------------------------------------------- + # min_rounds + #-------------------------------------------------- - # set below handler min + # set below handler minimum + with self.assertWarningList([PasslibConfigWarning]*2): c2 = cc.copy(all__min_rounds=500, all__max_rounds=None, all__default_rounds=500) - self.consumeWarningList(wlog, [PasslibConfigWarning]*2) - self.assertEqual(c2.genconfig(salt="nacl"), "$5$rounds=1000$nacl$") - self.consumeWarningList(wlog) + self.assertEqual(c2.genconfig(salt="nacl"), "$5$rounds=1000$nacl$") - # below + # below policy minimum + with self.assertWarningList(PasslibConfigWarning): self.assertEqual( cc.genconfig(rounds=1999, salt="nacl"), '$5$rounds=2000$nacl$', ) - self.consumeWarningList(wlog, PasslibConfigWarning) - # equal - self.assertEqual( - cc.genconfig(rounds=2000, salt="nacl"), - '$5$rounds=2000$nacl$', - ) - self.consumeWarningList(wlog) + # equal to policy minimum + self.assertEqual( + cc.genconfig(rounds=2000, salt="nacl"), + '$5$rounds=2000$nacl$', + ) - # above - self.assertEqual( - cc.genconfig(rounds=2001, salt="nacl"), - '$5$rounds=2001$nacl$' - ) - self.consumeWarningList(wlog) + # above policy minimum + self.assertEqual( + cc.genconfig(rounds=2001, salt="nacl"), + '$5$rounds=2001$nacl$' + ) + #-------------------------------------------------- # max rounds - with catch_warnings(record=True) as wlog: - # set above handler max + #-------------------------------------------------- + + # set above handler max + with self.assertWarningList([PasslibConfigWarning]*2): c2 = cc.copy(all__max_rounds=int(1e9)+500, all__min_rounds=None, all__default_rounds=int(1e9)+500) - self.consumeWarningList(wlog, [PasslibConfigWarning]*2) - self.assertEqual(c2.genconfig(salt="nacl"), - "$5$rounds=999999999$nacl$") - self.consumeWarningList(wlog) - # above + self.assertEqual(c2.genconfig(salt="nacl"), + "$5$rounds=999999999$nacl$") + + # above policy max + with self.assertWarningList(PasslibConfigWarning): self.assertEqual( cc.genconfig(rounds=3001, salt="nacl"), '$5$rounds=3000$nacl$' ) - self.consumeWarningList(wlog, PasslibConfigWarning) - # equal - self.assertEqual( - cc.genconfig(rounds=3000, salt="nacl"), - '$5$rounds=3000$nacl$' - ) - self.consumeWarningList(wlog) + # equal policy max + self.assertEqual( + cc.genconfig(rounds=3000, salt="nacl"), + '$5$rounds=3000$nacl$' + ) - # below - self.assertEqual( - cc.genconfig(rounds=2999, salt="nacl"), - '$5$rounds=2999$nacl$', - ) - self.consumeWarningList(wlog) + # below policy max + self.assertEqual( + cc.genconfig(rounds=2999, salt="nacl"), + '$5$rounds=2999$nacl$', + ) + + #-------------------------------------------------- + # default_rounds + #-------------------------------------------------- # explicit default rounds self.assertEqual(cc.genconfig(salt="nacl"), '$5$rounds=2500$nacl$') @@ -1156,12 +1233,16 @@ sha512_crypt__min_rounds = 45000 # TODO: test default falls back to mx / mn if handler has no default. - #default rounds - out of bounds + # default rounds - out of bounds self.assertRaises(ValueError, cc.copy, all__default_rounds=1999) cc.copy(all__default_rounds=2000) cc.copy(all__default_rounds=3000) self.assertRaises(ValueError, cc.copy, all__default_rounds=3001) + #-------------------------------------------------- + # border cases + #-------------------------------------------------- + # invalid min/max bounds c2 = CryptContext(schemes=["sha256_crypt"]) self.assertRaises(ValueError, c2.copy, all__min_rounds=-1) @@ -1169,6 +1250,19 @@ sha512_crypt__min_rounds = 45000 self.assertRaises(ValueError, c2.copy, all__min_rounds=2000, all__max_rounds=1999) + # test bad values + self.assertRaises(ValueError, CryptContext, all__min_rounds='x') + self.assertRaises(ValueError, CryptContext, all__max_rounds='x') + self.assertRaises(ValueError, CryptContext, all__vary_rounds='x') + self.assertRaises(ValueError, CryptContext, all__default_rounds='x') + + # test bad types rejected + bad = NotImplemented + self.assertRaises(TypeError, CryptContext, "sha256_crypt", all__min_rounds=bad) + self.assertRaises(TypeError, CryptContext, "sha256_crypt", all__max_rounds=bad) + self.assertRaises(TypeError, CryptContext, "sha256_crypt", all__vary_rounds=bad) + self.assertRaises(TypeError, CryptContext, "sha256_crypt", all__default_rounds=bad) + def test_51_linear_vary_rounds(self): "test linear vary rounds" cc = CryptContext(schemes=["sha256_crypt"], @@ -1259,9 +1353,9 @@ sha512_crypt__min_rounds = 45000 #========================================================= def test_60_min_verify_time(self): "test verify() honors min_verify_time" - #NOTE: this whole test assumes time.sleep() and tick() - # have better than 100ms accuracy - set via delta. - delta = .05 + delta = .01 + if get_timer_resolution(tick) >= delta: + raise self.skipTest("timer not accurate enough") min_delay = 2*delta min_verify_time = 5*delta max_delay = 8*delta @@ -1279,10 +1373,11 @@ sha512_crypt__min_rounds = 45000 time.sleep(self.delay) return to_unicode(secret + 'x') - # silence deprecation warnings for min verify time - with catch_warnings(record=True) as wlog: - cc = CryptContext([TimedHash], min_verify_time=min_verify_time) - self.consumeWarningList(wlog, DeprecationWarning) + # check mvt issues a warning, and then filter for remainder of test + with self.assertWarningList(["'min_verify_time' is deprecated"]*2): + cc = CryptContext([TimedHash], min_verify_time=min_verify_time, + admin__context__min_verify_time=min_verify_time*2) + warnings.filterwarnings("ignore", "'min_verify_time' is deprecated") def timecall(func, *args, **kwds): start = tick() @@ -1290,28 +1385,38 @@ sha512_crypt__min_rounds = 45000 end = tick() return end-start, result - #verify genhash delay works + # verify genhash delay works TimedHash.delay = min_delay elapsed, result = timecall(TimedHash.genhash, 'stub', None) self.assertEqual(result, 'stubx') self.assertAlmostEqual(elapsed, min_delay, delta=delta) - #ensure min verify time is honored + # ensure min verify time is honored + + # correct password elapsed, result = timecall(cc.verify, "stub", "stubx") self.assertTrue(result) self.assertAlmostEqual(elapsed, min_delay, delta=delta) + # incorrect password elapsed, result = timecall(cc.verify, "blob", "stubx") self.assertFalse(result) self.assertAlmostEqual(elapsed, min_verify_time, delta=delta) - #ensure taking longer emits a warning. + # incorrect password w/ special category setting + elapsed, result = timecall(cc.verify, "blob", "stubx", category="admin") + self.assertFalse(result) + self.assertAlmostEqual(elapsed, min_verify_time*2, delta=delta) + + # ensure taking longer emits a warning. TimedHash.delay = max_delay - with catch_warnings(record=True) as wlog: + with self.assertWarningList(".*verify exceeded min_verify_time"): elapsed, result = timecall(cc.verify, "blob", "stubx") self.assertFalse(result) self.assertAlmostEqual(elapsed, max_delay, delta=delta) - self.consumeWarningList(wlog, ".*verify exceeded min_verify_time") + + # reject values < 0 + self.assertRaises(ValueError, CryptContext, min_verify_time=-1) def test_61_autodeprecate(self): "test deprecated='auto' is handled correctly" @@ -1353,28 +1458,28 @@ sha512_crypt__min_rounds = 45000 # see issue 25. bcrypt = hash.bcrypt - PASS1 = "loppux" - BAD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" - GOOD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" - ctx = CryptContext(["bcrypt"]) + PASS1 = "test" + BAD1 = "$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS" + GOOD1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS" + ctx = CryptContext(["bcrypt"], bcrypt__rounds=4) - with catch_warnings(record=True) as wlog: - self.assertTrue(ctx.needs_update(BAD1)) - self.assertFalse(ctx.needs_update(GOOD1)) + self.assertTrue(ctx.needs_update(BAD1)) + self.assertFalse(ctx.needs_update(GOOD1)) - if bcrypt.has_backend(): - self.assertEqual(ctx.verify_and_update(PASS1,GOOD1), (True,None)) + if bcrypt.has_backend(): + self.assertEqual(ctx.verify_and_update(PASS1,GOOD1), (True,None)) + with self.assertWarningList(["incorrect.*padding bits"]*2): self.assertEqual(ctx.verify_and_update("x",BAD1), (False,None)) ok, new_hash = ctx.verify_and_update(PASS1, BAD1) - self.assertTrue(ok) - self.assertTrue(new_hash and new_hash != BAD1) + self.assertTrue(ok) + self.assertTrue(new_hash and new_hash != BAD1) def test_63_bsdi_crypt_update(self): "test verify_and_update / needs_update corrects bsdi even rounds" even_hash = '_Y/../cG0zkJa6LY6k4c' odd_hash = '_Z/..TgFg0/ptQtpAgws' secret = 'test' - ctx = CryptContext(['bsdi_crypt']) + ctx = CryptContext(['bsdi_crypt'], bsdi_crypt__min_rounds=5) self.assertTrue(ctx.needs_update(even_hash)) self.assertFalse(ctx.needs_update(odd_hash)) diff --git a/passlib/tests/test_context_deprecated.py b/passlib/tests/test_context_deprecated.py index 1662076..b291bf4 100644 --- a/passlib/tests/test_context_deprecated.py +++ b/passlib/tests/test_context_deprecated.py @@ -26,10 +26,9 @@ from passlib import hash from passlib.context import CryptContext, CryptPolicy, LazyCryptContext from passlib.exc import PasslibConfigWarning from passlib.utils import tick, to_bytes, to_unicode -from passlib.utils.compat import irange, u +from passlib.utils.compat import irange, u, bytes import passlib.utils.handlers as uh -from passlib.tests.utils import TestCase, mktemp, catch_warnings, \ - gae_env, set_file +from passlib.tests.utils import TestCase, catch_warnings, set_file from passlib.registry import (register_crypt_handler_path, _has_crypt_handler as has_crypt_handler, _unload_handler_name as unload_handler_name, @@ -223,6 +222,12 @@ admin__context__deprecated = des_crypt, bsdi_crypt policy = CryptPolicy(**self.sample_config_1pd) self.assertEqual(policy.to_dict(), self.sample_config_1pd) + policy = CryptPolicy(self.sample_config_1pd) + self.assertEqual(policy.to_dict(), self.sample_config_1pd) + + self.assertRaises(TypeError, CryptPolicy, {}, {}) + self.assertRaises(TypeError, CryptPolicy, {}, dummy=1) + #check key with too many separators is rejected self.assertRaises(TypeError, CryptPolicy, schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], @@ -266,10 +271,7 @@ admin__context__deprecated = des_crypt, bsdi_crypt def test_01_from_path(self): "test CryptPolicy.from_path() constructor with encodings" - if gae_env: - return self.skipTest("GAE doesn't offer read/write filesystem access") - - path = mktemp() + path = self.mktemp() #test "\n" linesep set_file(path, self.sample_config_1s) @@ -539,6 +541,9 @@ admin__context__deprecated = des_crypt, bsdi_crypt pb = CryptPolicy.from_string(s) self.assertEqual(pb.to_dict(), self.sample_config_5pd) + s = pa.to_string(encoding="latin-1") + self.assertIsInstance(s, bytes) + #========================================================= # #========================================================= @@ -577,7 +582,18 @@ class CryptContextTest(TestCase): self.assertIs(b, hash.bsdi_crypt) self.assertIs(c, hash.md5_crypt) - #TODO: test policy & other options + # policy kwd + policy = cc.policy + cc = CryptContext(policy=policy) + self.assertEqual(cc.to_dict(), policy.to_dict()) + + cc = CryptContext(policy=policy, default="bsdi_crypt") + self.assertNotEqual(cc.to_dict(), policy.to_dict()) + self.assertEqual(cc.to_dict(), dict(schemes=["md5_crypt","bsdi_crypt","des_crypt"], + default="bsdi_crypt")) + + self.assertRaises(TypeError, setattr, cc, 'policy', None) + self.assertRaises(TypeError, CryptContext, policy='x') def test_01_replace(self): "test replace()" @@ -632,251 +648,6 @@ class CryptContextTest(TestCase): phpass__default_rounds = 7, ) - def test_10_01_genconfig_settings(self): - "test genconfig() settings" - cc = CryptContext(policy=None, - schemes=["md5_crypt", "phpass"], - phpass__ident="H", - phpass__default_rounds=7, - ) - - # hash specific settings - self.assertTrue(cc.genconfig().startswith("$1$")) - self.assertEqual( - cc.genconfig(scheme="phpass", salt='.'*8), - '$H$5........', - ) - self.assertEqual( - cc.genconfig(scheme="phpass", salt='.'*8, rounds=8, ident='P'), - '$P$6........', - ) - - # unsupported hash settings should be rejected - self.assertRaises(KeyError, cc.replace, md5_crypt__ident="P") - - def test_10_02_genconfig_rounds_limits(self): - "test genconfig() policy rounds limits" - cc = CryptContext(policy=None, - schemes=["sha256_crypt"], - all__min_rounds=2000, - all__max_rounds=3000, - all__default_rounds=2500, - ) - - # min rounds - with catch_warnings(record=True) as wlog: - - # set below handler min - c2 = cc.replace(all__min_rounds=500, all__max_rounds=None, - all__default_rounds=500) - self.consumeWarningList(wlog, [PasslibConfigWarning]*2) - self.assertEqual(c2.genconfig(salt="nacl"), "$5$rounds=1000$nacl$") - self.consumeWarningList(wlog) - - # below - self.assertEqual( - cc.genconfig(rounds=1999, salt="nacl"), - '$5$rounds=2000$nacl$', - ) - self.consumeWarningList(wlog, PasslibConfigWarning) - - # equal - self.assertEqual( - cc.genconfig(rounds=2000, salt="nacl"), - '$5$rounds=2000$nacl$', - ) - self.consumeWarningList(wlog) - - # above - self.assertEqual( - cc.genconfig(rounds=2001, salt="nacl"), - '$5$rounds=2001$nacl$' - ) - self.consumeWarningList(wlog) - - # max rounds - with catch_warnings(record=True) as wlog: - # set above handler max - c2 = cc.replace(all__max_rounds=int(1e9)+500, all__min_rounds=None, - all__default_rounds=int(1e9)+500) - self.consumeWarningList(wlog, [PasslibConfigWarning]*2) - self.assertEqual(c2.genconfig(salt="nacl"), - "$5$rounds=999999999$nacl$") - self.consumeWarningList(wlog) - - # above - self.assertEqual( - cc.genconfig(rounds=3001, salt="nacl"), - '$5$rounds=3000$nacl$' - ) - self.consumeWarningList(wlog, PasslibConfigWarning) - - # equal - self.assertEqual( - cc.genconfig(rounds=3000, salt="nacl"), - '$5$rounds=3000$nacl$' - ) - self.consumeWarningList(wlog) - - # below - self.assertEqual( - cc.genconfig(rounds=2999, salt="nacl"), - '$5$rounds=2999$nacl$', - ) - self.consumeWarningList(wlog) - - # explicit default rounds - self.assertEqual(cc.genconfig(salt="nacl"), '$5$rounds=2500$nacl$') - - # fallback default rounds - use handler's default - df = hash.sha256_crypt.default_rounds - c2 = cc.copy(all__default_rounds=None, all__max_rounds=df<<1) - self.assertEqual(c2.genconfig(salt="nacl"), - '$5$rounds=%d$nacl$' % df) - - # fallback default rounds - use handler's, but clipped to max rounds - c2 = cc.replace(all__default_rounds=None, all__max_rounds=3000) - self.assertEqual(c2.genconfig(salt="nacl"), '$5$rounds=3000$nacl$') - - # TODO: test default falls back to mx / mn if handler has no default. - - #default rounds - out of bounds - self.assertRaises(ValueError, cc.replace, all__default_rounds=1999) - cc.policy.replace(all__default_rounds=2000) - cc.policy.replace(all__default_rounds=3000) - self.assertRaises(ValueError, cc.replace, all__default_rounds=3001) - - # invalid min/max bounds - c2 = CryptContext(policy=None, schemes=["sha256_crypt"]) - self.assertRaises(ValueError, c2.replace, all__min_rounds=-1) - self.assertRaises(ValueError, c2.replace, all__max_rounds=-1) - self.assertRaises(ValueError, c2.replace, all__min_rounds=2000, - all__max_rounds=1999) - - def test_10_03_genconfig_linear_vary_rounds(self): - "test genconfig() linear vary rounds" - cc = CryptContext(policy=None, - schemes=["sha256_crypt"], - all__min_rounds=1995, - all__max_rounds=2005, - all__default_rounds=2000, - ) - - # test negative - self.assertRaises(ValueError, cc.replace, all__vary_rounds=-1) - self.assertRaises(ValueError, cc.replace, all__vary_rounds="-1%") - self.assertRaises(ValueError, cc.replace, all__vary_rounds="101%") - - # test static - c2 = cc.replace(all__vary_rounds=0) - self.assert_rounds_range(c2, "sha256_crypt", 2000, 2000) - - c2 = cc.replace(all__vary_rounds="0%") - self.assert_rounds_range(c2, "sha256_crypt", 2000, 2000) - - # test absolute - c2 = cc.replace(all__vary_rounds=1) - self.assert_rounds_range(c2, "sha256_crypt", 1999, 2001) - c2 = cc.replace(all__vary_rounds=100) - self.assert_rounds_range(c2, "sha256_crypt", 1995, 2005) - - # test relative - c2 = cc.replace(all__vary_rounds="0.1%") - self.assert_rounds_range(c2, "sha256_crypt", 1998, 2002) - c2 = cc.replace(all__vary_rounds="100%") - self.assert_rounds_range(c2, "sha256_crypt", 1995, 2005) - - def test_10_03_genconfig_log2_vary_rounds(self): - "test genconfig() log2 vary rounds" - cc = CryptContext(policy=None, - schemes=["bcrypt"], - all__min_rounds=15, - all__max_rounds=25, - all__default_rounds=20, - ) - - # test negative - self.assertRaises(ValueError, cc.replace, all__vary_rounds=-1) - self.assertRaises(ValueError, cc.replace, all__vary_rounds="-1%") - self.assertRaises(ValueError, cc.replace, all__vary_rounds="101%") - - # test static - c2 = cc.replace(all__vary_rounds=0) - self.assert_rounds_range(c2, "bcrypt", 20, 20) - - c2 = cc.replace(all__vary_rounds="0%") - self.assert_rounds_range(c2, "bcrypt", 20, 20) - - # test absolute - c2 = cc.replace(all__vary_rounds=1) - self.assert_rounds_range(c2, "bcrypt", 19, 21) - c2 = cc.replace(all__vary_rounds=100) - self.assert_rounds_range(c2, "bcrypt", 15, 25) - - # test relative - should shift over at 50% mark - c2 = cc.replace(all__vary_rounds="1%") - self.assert_rounds_range(c2, "bcrypt", 20, 20) - - c2 = cc.replace(all__vary_rounds="49%") - self.assert_rounds_range(c2, "bcrypt", 20, 20) - - c2 = cc.replace(all__vary_rounds="50%") - self.assert_rounds_range(c2, "bcrypt", 19, 20) - - c2 = cc.replace(all__vary_rounds="100%") - self.assert_rounds_range(c2, "bcrypt", 15, 21) - - def assert_rounds_range(self, context, scheme, lower, upper): - "helper to check vary_rounds covers specified range" - # NOTE: this runs enough times the min and max *should* be hit, - # though there's a faint chance it will randomly fail. - handler = context.policy.get_handler(scheme) - salt = handler.default_salt_chars[0:1] * handler.max_salt_size - seen = set() - for i in irange(300): - h = context.genconfig(scheme, salt=salt) - r = handler.from_string(h).rounds - seen.add(r) - self.assertEqual(min(seen), lower, "vary_rounds had wrong lower limit:") - self.assertEqual(max(seen), upper, "vary_rounds had wrong upper limit:") - - def test_11_encrypt_settings(self): - "test encrypt() honors policy settings" - cc = CryptContext(**self.sample_policy_1) - - # hash specific settings - self.assertEqual( - cc.encrypt("password", scheme="phpass", salt='.'*8), - '$H$5........De04R5Egz0aq8Tf.1eVhY/', - ) - self.assertEqual( - cc.encrypt("password", scheme="phpass", salt='.'*8, ident="P"), - '$P$5........De04R5Egz0aq8Tf.1eVhY/', - ) - - # NOTE: more thorough job of rounds limits done in genconfig() test, - # which is much cheaper, and shares the same codebase. - - # min rounds - with catch_warnings(record=True) as wlog: - self.assertEqual( - cc.encrypt("password", rounds=1999, salt="nacl"), - '$5$rounds=2000$nacl$9/lTZ5nrfPuz8vphznnmHuDGFuvjSNvOEDsGmGfsS97', - ) - self.consumeWarningList(wlog, PasslibConfigWarning) - - self.assertEqual( - cc.encrypt("password", rounds=2001, salt="nacl"), - '$5$rounds=2001$nacl$8PdeoPL4aXQnJ0woHhqgIw/efyfCKC2WHneOpnvF.31' - ) - self.consumeWarningList(wlog) - - # max rounds, etc tested in genconfig() - - # make default > max throws error if attempted - self.assertRaises(ValueError, cc.replace, - sha256_crypt__default_rounds=4000) - def test_12_hash_needs_update(self): "test hash_needs_update() method" cc = CryptContext(**self.sample_policy_1) @@ -894,146 +665,6 @@ class CryptContextTest(TestCase): self.assertTrue(cc.hash_needs_update('$5$rounds=3001$QlFHHifXvpFX4PLs$/0ekt7lSs/lOikSerQ0M/1porEHxYq7W/2hdFpxA3fA')) #========================================================= - #identify - #========================================================= - def test_20_basic(self): - "test basic encrypt/identify/verify functionality" - handlers = [hash.md5_crypt, hash.des_crypt, hash.bsdi_crypt] - cc = CryptContext(handlers, policy=None) - - #run through handlers - for crypt in handlers: - h = cc.encrypt("test", scheme=crypt.name) - self.assertEqual(cc.identify(h), crypt.name) - self.assertEqual(cc.identify(h, resolve=True), crypt) - self.assertTrue(cc.verify('test', h)) - self.assertTrue(not cc.verify('notest', h)) - - #test default - h = cc.encrypt("test") - self.assertEqual(cc.identify(h), "md5_crypt") - - #test genhash - h = cc.genhash('secret', cc.genconfig()) - self.assertEqual(cc.identify(h), 'md5_crypt') - - h = cc.genhash('secret', cc.genconfig(), scheme='md5_crypt') - self.assertEqual(cc.identify(h), 'md5_crypt') - - self.assertRaises(ValueError, cc.genhash, 'secret', cc.genconfig(), scheme="des_crypt") - - def test_21_identify(self): - "test identify() border cases" - handlers = ["md5_crypt", "des_crypt", "bsdi_crypt"] - cc = CryptContext(handlers, policy=None) - - #check unknown hash - self.assertEqual(cc.identify('$9$232323123$1287319827'), None) - self.assertRaises(ValueError, cc.identify, '$9$232323123$1287319827', required=True) - - def test_22_verify(self): - "test verify() scheme kwd" - handlers = ["md5_crypt", "des_crypt", "bsdi_crypt"] - cc = CryptContext(handlers, policy=None) - - h = hash.md5_crypt.encrypt("test") - - #check base verify - self.assertTrue(cc.verify("test", h)) - self.assertTrue(not cc.verify("notest", h)) - - #check verify using right alg - self.assertTrue(cc.verify('test', h, scheme='md5_crypt')) - self.assertTrue(not cc.verify('notest', h, scheme='md5_crypt')) - - #check verify using wrong alg - self.assertRaises(ValueError, cc.verify, 'test', h, scheme='bsdi_crypt') - - def test_24_min_verify_time(self): - "test verify() honors min_verify_time" - #NOTE: this whole test assumes time.sleep() and tick() - # have better than 100ms accuracy - set via delta. - delta = .05 - min_delay = 2*delta - min_verify_time = 5*delta - max_delay = 8*delta - - class TimedHash(uh.StaticHandler): - "psuedo hash that takes specified amount of time" - name = "timed_hash" - delay = 0 - - @classmethod - def identify(cls, hash): - return True - - def _calc_checksum(self, secret): - time.sleep(self.delay) - return to_unicode(secret + 'x') - - # silence deprecation warnings for min verify time - with catch_warnings(record=True) as wlog: - cc = CryptContext([TimedHash], min_verify_time=min_verify_time) - self.consumeWarningList(wlog, DeprecationWarning) - - def timecall(func, *args, **kwds): - start = tick() - result = func(*args, **kwds) - end = tick() - return end-start, result - - #verify genhash delay works - TimedHash.delay = min_delay - elapsed, result = timecall(TimedHash.genhash, 'stub', None) - self.assertEqual(result, 'stubx') - self.assertAlmostEqual(elapsed, min_delay, delta=delta) - - #ensure min verify time is honored - elapsed, result = timecall(cc.verify, "stub", "stubx") - self.assertTrue(result) - self.assertAlmostEqual(elapsed, min_delay, delta=delta) - - elapsed, result = timecall(cc.verify, "blob", "stubx") - self.assertFalse(result) - self.assertAlmostEqual(elapsed, min_verify_time, delta=delta) - - #ensure taking longer emits a warning. - TimedHash.delay = max_delay - with catch_warnings(record=True) as wlog: - elapsed, result = timecall(cc.verify, "blob", "stubx") - self.assertFalse(result) - self.assertAlmostEqual(elapsed, max_delay, delta=delta) - self.consumeWarningList(wlog, ".*verify exceeded min_verify_time") - - def test_25_verify_and_update(self): - "test verify_and_update()" - cc = CryptContext(**self.sample_policy_1) - - #create some hashes - h1 = cc.encrypt("password", scheme="des_crypt") - h2 = cc.encrypt("password", scheme="sha256_crypt") - - #check bad password, deprecated hash - ok, new_hash = cc.verify_and_update("wrongpass", h1) - self.assertFalse(ok) - self.assertIs(new_hash, None) - - #check bad password, good hash - ok, new_hash = cc.verify_and_update("wrongpass", h2) - self.assertFalse(ok) - self.assertIs(new_hash, None) - - #check right password, deprecated hash - ok, new_hash = cc.verify_and_update("password", h1) - self.assertTrue(ok) - self.assertTrue(cc.identify(new_hash), "sha256_crypt") - - #check right password, good hash - ok, new_hash = cc.verify_and_update("password", h2) - self.assertTrue(ok) - self.assertIs(new_hash, None) - - #========================================================= # border cases #========================================================= def test_30_nonstring_hash(self): @@ -1050,64 +681,11 @@ class CryptContextTest(TestCase): ((), {}), ]: - self.assertRaises(TypeError, cc.identify, hash, **kwds) - self.assertRaises(TypeError, cc.genhash, 'stub', hash, **kwds) - self.assertRaises(TypeError, cc.verify, 'stub', hash, **kwds) - self.assertRaises(TypeError, cc.verify_and_update, 'stub', hash, **kwds) self.assertRaises(TypeError, cc.hash_needs_update, hash, **kwds) - # - # but genhash *should* accept None if default scheme lacks config string. - # cc2 = CryptContext(["mysql323"]) - self.assertRaises(TypeError, cc2.identify, None) - self.assertIsInstance(cc2.genhash("stub", None), str) - self.assertRaises(TypeError, cc2.verify, 'stub', None) - self.assertRaises(TypeError, cc2.verify_and_update, 'stub', None) self.assertRaises(TypeError, cc2.hash_needs_update, None) - - def test_31_nonstring_secret(self): - "test non-string password values cause error" - cc = CryptContext(["des_crypt"]) - hash = cc.encrypt("stub") - # - # test secret=None, or some other non-string causes TypeError - # - for secret, kwds in [ - (None, {}), - (None, {"scheme": "des_crypt"}), - (1, {}), - ((), {}), - ]: - self.assertRaises(TypeError, cc.encrypt, secret, **kwds) - self.assertRaises(TypeError, cc.genhash, secret, hash, **kwds) - self.assertRaises(TypeError, cc.verify, secret, hash, **kwds) - self.assertRaises(TypeError, cc.verify_and_update, secret, hash, **kwds) - - #========================================================= - # other - #========================================================= - def test_90_bcrypt_normhash(self): - "teset verify_and_update / hash_needs_update corrects bcrypt padding" - # see issue 25. - bcrypt = hash.bcrypt - - PASS1 = "loppux" - BAD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" - GOOD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" - ctx = CryptContext(["bcrypt"]) - - with catch_warnings(record=True) as wlog: - self.assertTrue(ctx.hash_needs_update(BAD1)) - self.assertFalse(ctx.hash_needs_update(GOOD1)) - - if bcrypt.has_backend(): - self.assertEqual(ctx.verify_and_update(PASS1,GOOD1), (True,None)) - self.assertEqual(ctx.verify_and_update("x",BAD1), (False,None)) - res = ctx.verify_and_update(PASS1, BAD1) - self.assertTrue(res[0] and res[1] and res[1] != BAD1) - #========================================================= #eoc #========================================================= |