summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-27 00:49:55 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-27 00:49:55 -0400
commit03964d1fce1720413498dbb537acc52ba3321566 (patch)
tree0ee97040ecc6c0c930e4feb04dc6c3406f9af079
parenta8c0ef90542ec5b17fe84c18ecbe7c439da4105d (diff)
downloadpasslib-03964d1fce1720413498dbb537acc52ba3321566.tar.gz
context change - shortened hash_needs_update() to needs_update()
- renamed internal update hooks as well - needs_update() now accepts an optional copy of the password. this is unused for now, but should pave the way for properly migrating crypt_blowfish $2x$ hashes in the next release.
-rw-r--r--passlib/context.py50
-rw-r--r--passlib/handlers/bcrypt.py7
-rw-r--r--passlib/handlers/des_crypt.py6
-rw-r--r--passlib/handlers/scram.py4
-rw-r--r--passlib/tests/test_context.py37
-rw-r--r--passlib/tests/test_handlers.py6
-rw-r--r--passlib/utils/handlers.py4
7 files changed, 64 insertions, 50 deletions
diff --git a/passlib/context.py b/passlib/context.py
index b0b4d12..3d14238 100644
--- a/passlib/context.py
+++ b/passlib/context.py
@@ -630,8 +630,8 @@ class _CryptRecord(object):
# verify() attrs
_min_verify_time = None
- # hash_needs_update() attrs
- _is_deprecated_by_handler = None # optional callable used by bcrypt/scram
+ # needs_update() attrs
+ _needs_update = None # optional callable provided by handler
_has_rounds_introspection = False # if rounds can be extract from hash
# cloned directly from handler, not affected by config options.
@@ -658,7 +658,7 @@ class _CryptRecord(object):
# init wrappers for handler methods we modify args to
self._init_encrypt_and_genconfig()
self._init_verify(min_verify_time)
- self._init_hash_needs_update()
+ self._init_needs_update()
# these aren't wrapped by _CryptRecord, copy them directly from handler.
self.identify = handler.identify
@@ -931,44 +931,44 @@ class _CryptRecord(object):
return False
#================================================================
- # hash_needs_update()
+ # needs_update()
#================================================================
- def _init_hash_needs_update(self):
- """initialize state for hash_needs_update()"""
+ def _init_needs_update(self):
+ """initialize state for needs_update()"""
# if handler has been deprecated, replace wrapper and skip other checks
if self.deprecated:
- self.hash_needs_update = lambda hash: True
+ self.needs_update = lambda hash, secret: True
return
# let handler detect hashes with configurations that don't match
# current settings. currently do this by calling
- # ``handler._deprecation_detector(**settings)``, which if defined
- # should return None or a callable ``is_deprecated(hash)->bool``.
+ # ``handler._bind_needs_update(**settings)``, which if defined
+ # should return None or a callable ``needs_update(hash,secret)->bool``.
#
# NOTE: this interface is still private, because it was hacked in
# for the sake of bcrypt & scram, and is subject to change.
handler = self.handler
- const = getattr(handler, "_deprecation_detector", None)
+ const = getattr(handler, "_bind_needs_update", None)
if const:
- self._is_deprecated_by_handler = const(**self.settings)
+ self._needs_update = const(**self.settings)
# XXX: what about a "min_salt_size" deprecator?
# set flag if we can extract rounds from hash, allowing
- # hash_needs_update() to check for rounds that are outside of
+ # needs_update() to check for rounds that are outside of
# the configured range.
if self._has_rounds_bounds and hasattr(handler, "from_string"):
self._has_rounds_introspection = True
- def hash_needs_update(self, hash):
+ def needs_update(self, hash, secret):
# init replaces this method entirely for this case.
### check if handler has been deprecated
##if self.deprecated:
## return True
# check handler's detector if it provided one.
- check = self._is_deprecated_by_handler
- if check and check(hash):
+ check = self._needs_update
+ if check and check(hash, secret):
return True
# if we can parse rounds parameter, check if it's w/in bounds.
@@ -1078,7 +1078,7 @@ class CryptContext(object):
Applications which want to detect and re-encrypt deprecated
hashes will want to use one of the following methods:
- .. automethod:: hash_needs_update
+ .. automethod:: needs_update
.. automethod:: verify_and_update
Additionally, the main interface offers a few helper methods,
@@ -2215,8 +2215,7 @@ class CryptContext(object):
# Each record object stores the options used
# by the specific (scheme,category) combination it manages.
- # XXX: would a better name be needs_update/is_deprecated?
- def hash_needs_update(self, hash, scheme=None, category=None):
+ def needs_update(self, hash, scheme=None, category=None, secret=None):
"""check if hash is allowed by current policy, or if secret should be re-encrypted.
the core of CryptContext's support for hash migration:
@@ -2230,11 +2229,20 @@ class CryptContext(object):
:arg hash: existing hash string
:param scheme: optionally identify specific scheme to check against.
:param category: optional user category
+ :param secret: optional copy of associated password
:returns: True/False
"""
record = self._get_or_identify_record(hash, scheme, category)
- return record.hash_needs_update(hash)
+ return record.needs_update(hash, secret)
+
+ def hash_needs_update(self, hash, scheme=None, category=None):
+ """legacy alias for :meth:`needs_update`.
+
+ .. deprecated:: 1.6
+ use :meth:`needs_update` instead.
+ """
+ return self.needs_update(hash, scheme, category)
def genconfig(self, scheme=None, category=None, **settings):
"""Call genconfig() for specified handler
@@ -2362,7 +2370,7 @@ class CryptContext(object):
This is a convenience method for a common situation in most applications:
When a user logs in, they must :meth:`verify` if the password matches;
if successful, check if the hash algorithm
- has been deprecated (:meth:`hash_needs_update`); and if so,
+ has been deprecated (:meth:`needs_update`); and if so,
re-:meth:`encrypt` the secret.
This method takes care of calling all of these 3 methods,
returning a simple tuple for the application to use.
@@ -2400,7 +2408,7 @@ class CryptContext(object):
record = self._get_or_identify_record(hash, scheme, category)
if not record.verify(secret, hash, **kwds):
return False, None
- elif record.hash_needs_update(hash):
+ elif record.needs_update(hash, secret):
# NOTE: we re-encrypt with default scheme, not current one.
return True, self.encrypt(secret, None, category, **kwds)
else:
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index 4d12091..5df66e3 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -159,16 +159,17 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
#=========================================================
@classmethod
- def _deprecation_detector(cls, **settings):
- return cls._hash_needs_update
+ def _bind_needs_update(cls, **settings):
+ return cls._needs_update
@classmethod
- def _hash_needs_update(cls, hash):
+ def _needs_update(cls, hash, secret):
if isinstance(hash, bytes):
hash = hash.decode("ascii")
# check for incorrect padding bits (passlib issue 25)
if hash.startswith(IDENT_2A) and hash[28] not in bcrypt64._padinfo2[1]:
return True
+ # TODO: try to detect incorrect $2x$ hashes using *secret*
return False
@classmethod
diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py
index 4a19532..68ab3dd 100644
--- a/passlib/handlers/des_crypt.py
+++ b/passlib/handlers/des_crypt.py
@@ -278,11 +278,11 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
return rounds
@classmethod
- def _deprecation_detector(cls, **settings):
- return cls._hash_needs_update
+ def _bind_needs_update(cls, **settings):
+ return cls._needs_update
@classmethod
- def _hash_needs_update(cls, hash):
+ def _needs_update(cls, hash, secret):
# mark bsdi_crypt hashes as deprecated if they have even rounds.
assert cls.identify(hash)
if isinstance(hash, unicode):
diff --git a/passlib/handlers/scram.py b/passlib/handlers/scram.py
index 036d7c2..ef73cf4 100644
--- a/passlib/handlers/scram.py
+++ b/passlib/handlers/scram.py
@@ -323,12 +323,12 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
#=========================================================
@classmethod
- def _deprecation_detector(cls, **settings):
+ def _bind_needs_update(cls, **settings):
"generate a deprecation detector for CryptContext to use"
# generate deprecation hook which marks hashes as deprecated
# if they don't support a superset of current algs.
algs = frozenset(cls(use_defaults=True, **settings).algs)
- def detector(hash):
+ def detector(hash, secret):
return not algs.issubset(cls.from_string(hash).algs)
return detector
diff --git a/passlib/tests/test_context.py b/passlib/tests/test_context.py
index 28ce208..2abbccf 100644
--- a/passlib/tests/test_context.py
+++ b/passlib/tests/test_context.py
@@ -941,21 +941,23 @@ sha512_crypt__min_rounds = 45000
# throws error without schemes
self.assertRaises(KeyError, CryptContext().verify, 'secret', 'hash')
- def test_46_hash_needs_update(self):
- "test hash_needs_update() method"
+ def test_46_needs_update(self):
+ "test needs_update() method"
cc = CryptContext(**self.sample_4_dict)
#check deprecated scheme
- self.assertTrue(cc.hash_needs_update('9XXD4trGYeGJA'))
- self.assertFalse(cc.hash_needs_update('$1$J8HC2RCr$HcmM.7NxB2weSvlw2FgzU0'))
+ self.assertTrue(cc.needs_update('9XXD4trGYeGJA'))
+ self.assertFalse(cc.needs_update('$1$J8HC2RCr$HcmM.7NxB2weSvlw2FgzU0'))
#check min rounds
- self.assertTrue(cc.hash_needs_update('$5$rounds=1999$jD81UCoo.zI.UETs$Y7qSTQ6mTiU9qZB4fRr43wRgQq4V.5AAf7F97Pzxey/'))
- self.assertFalse(cc.hash_needs_update('$5$rounds=2000$228SSRje04cnNCaQ$YGV4RYu.5sNiBvorQDlO0WWQjyJVGKBcJXz3OtyQ2u8'))
+ self.assertTrue(cc.needs_update('$5$rounds=1999$jD81UCoo.zI.UETs$Y7qSTQ6mTiU9qZB4fRr43wRgQq4V.5AAf7F97Pzxey/'))
+ self.assertFalse(cc.needs_update('$5$rounds=2000$228SSRje04cnNCaQ$YGV4RYu.5sNiBvorQDlO0WWQjyJVGKBcJXz3OtyQ2u8'))
#check max rounds
- self.assertFalse(cc.hash_needs_update('$5$rounds=3000$fS9iazEwTKi7QPW4$VasgBC8FqlOvD7x2HhABaMXCTh9jwHclPA9j5YQdns.'))
- self.assertTrue(cc.hash_needs_update('$5$rounds=3001$QlFHHifXvpFX4PLs$/0ekt7lSs/lOikSerQ0M/1porEHxYq7W/2hdFpxA3fA'))
+ self.assertFalse(cc.needs_update('$5$rounds=3000$fS9iazEwTKi7QPW4$VasgBC8FqlOvD7x2HhABaMXCTh9jwHclPA9j5YQdns.'))
+ self.assertTrue(cc.needs_update('$5$rounds=3001$QlFHHifXvpFX4PLs$/0ekt7lSs/lOikSerQ0M/1porEHxYq7W/2hdFpxA3fA'))
+
+ # TODO: check with secret passed in, that _bind_needs_update() is invoked correctly.
#--------------------------------------------------------------
# border cases
@@ -964,10 +966,10 @@ sha512_crypt__min_rounds = 45000
# rejects non-string hashes
cc = CryptContext(["des_crypt"])
for hash, kwds in self.nonstring_vectors:
- self.assertRaises(TypeError, cc.hash_needs_update, hash, **kwds)
+ self.assertRaises(TypeError, cc.needs_update, hash, **kwds)
# throws error without schemes
- self.assertRaises(KeyError, CryptContext().hash_needs_update, 'hash')
+ self.assertRaises(KeyError, CryptContext().needs_update, 'hash')
def test_47_verify_and_update(self):
"test verify_and_update()"
@@ -1322,8 +1324,11 @@ sha512_crypt__min_rounds = 45000
self.assertFalse(ctx.verify(pu, ctx.encrypt(pn, scheme="md5_crypt")))
self.assertTrue(ctx.verify(pu, ctx.encrypt(pn, scheme="sha256_crypt")))
+ #=========================================================
+ # handler deprecation detectors
+ #=========================================================
def test_62_bcrypt_update(self):
- "test verify_and_update / hash_needs_update corrects bcrypt padding"
+ "test verify_and_update / needs_update corrects bcrypt padding"
# see issue 25.
bcrypt = hash.bcrypt
@@ -1333,8 +1338,8 @@ sha512_crypt__min_rounds = 45000
ctx = CryptContext(["bcrypt"])
with catch_warnings(record=True) as wlog:
- self.assertTrue(ctx.hash_needs_update(BAD1))
- self.assertFalse(ctx.hash_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))
@@ -1344,14 +1349,14 @@ sha512_crypt__min_rounds = 45000
self.assertTrue(new_hash and new_hash != BAD1)
def test_63_bsdi_crypt_update(self):
- "test verify_and_update / hash_needs_update correct bsdi even rounds"
+ "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'])
- self.assertTrue(ctx.hash_needs_update(even_hash))
- self.assertFalse(ctx.hash_needs_update(odd_hash))
+ self.assertTrue(ctx.needs_update(even_hash))
+ self.assertFalse(ctx.needs_update(odd_hash))
self.assertEqual(ctx.verify_and_update(secret, odd_hash), (True,None))
self.assertEqual(ctx.verify_and_update("x", even_hash), (False,None))
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index 503b349..4e7c040 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -2046,13 +2046,13 @@ class scram_test(HandlerCase):
h = c1.encrypt("dummy")
self.assertEqual(handler.extract_digest_algs(h), ["md5", "sha-1"])
- self.assertFalse(c1.hash_needs_update(h))
+ self.assertFalse(c1.needs_update(h))
c2 = c1.copy(scram__algs="sha1")
- self.assertFalse(c2.hash_needs_update(h))
+ self.assertFalse(c2.needs_update(h))
c2 = c1.copy(scram__algs="sha1,sha256")
- self.assertTrue(c2.hash_needs_update(h))
+ self.assertTrue(c2.needs_update(h))
def test_96_full_verify(self):
"test verify(full=True) flag"
diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py
index eb2b7d3..76de5bc 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/handlers.py
@@ -563,13 +563,13 @@ class GenericHandler(object):
#=========================================================
##@classmethod
- ##def _deprecation_detector(cls, **settings):
+ ##def _bind_needs_update(cls, **settings):
## """return helper to detect deprecated hashes.
##
## if this method is defined, the CryptContext constructor
## will invoke it with the settings specified for the context.
## this method should return None or a callable
- ## with the signature ``func(hash)->bool``.
+ ## with the signature ``func(hash,secret)->bool``.
##
## this function should return true if the hash
## should be re-encrypted, whether due to internal