summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-30 22:50:33 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-30 22:50:33 -0400
commitbaf77a212017f0accce893248b3c3fd5ce7abf5a (patch)
tree757f4df599124b96fc14f083bfa4eb005ad6bdef
parentceeccea33a8e4e425425e4ed30c0ed2c34b67bbe (diff)
downloadpasslib-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.py149
-rw-r--r--passlib/tests/test_context.py293
-rw-r--r--passlib/tests/test_context_deprecated.py470
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
#=========================================================