diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-02-26 10:54:14 -0500 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-02-26 10:54:14 -0500 |
| commit | 6c9c467b5448e8b6fea751bdf2a0628c6f2c99ea (patch) | |
| tree | 14a182e9c6eab586eb523952641295b807ff5d42 /passlib/tests/utils.py | |
| parent | 962ec21abda828138a3c8efed5918ad2f14850b8 (diff) | |
| download | passlib-6c9c467b5448e8b6fea751bdf2a0628c6f2c99ea.tar.gz | |
HandlerCase
===========
* refactored common handler test case (HandlerCase)
* removed some needless tests
* added support for genhash / genconfig
* bugfixes to des_crypt & nthash that above changes revealed
Diffstat (limited to 'passlib/tests/utils.py')
| -rw-r--r-- | passlib/tests/utils.py | 312 |
1 files changed, 173 insertions, 139 deletions
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py index 19d8891..cad2194 100644 --- a/passlib/tests/utils.py +++ b/passlib/tests/utils.py @@ -7,6 +7,11 @@ import logging; log = logging.getLogger(__name__) import re import os import unittest +import warnings +try: + from warnings import catch_warnings +except ImportError: #wasn't added until py26 + catch_warnings = None #site from nose.plugins.skip import SkipTest #pkg @@ -203,7 +208,7 @@ class HandlerCase(TestCase): #attrs to be filled in by subclass for testing specific handler #========================================================= - #specify handler object here + #specify handler object here (required) handler = None #this option is available for hashes which can't handle unicode @@ -214,39 +219,29 @@ class HandlerCase(TestCase): secret_chars = -1 #list of (secret,hash) pairs which handler should verify as matching - known_correct = [] + known_correct_hashes = [] - #list of (secret,hash) pairs which handler should verify as NOT matching - known_incorrect = [] + #list of (config, secret, hash) triples which handler should genhash & verify + known_correct_configs = [] - # list of handler's hashes with crucial invalidating typos, that handler shouldn't identify as belonging to it - known_invalid = [] + # hashes so malformed they aren't even identified properly + known_unidentified_hashes = [] - # list of handler's hashes that it *will* identify as it's own, but genhash will raise error due to invalid internal requirements - known_identified_invalid = [] + # hashes which are malformed - they should identify() as True, but cause error when passed to genhash/verify + known_malformed_hashes = [] - #list of (name, hash) pairs for other algorithm's hashes, that handler shouldn't identify as belonging to it + #list of (handler name, hash) pairs for other algorithm's hashes, that handler shouldn't identify as belonging to it #this list should generally be sufficient (if handler name in list, that entry will be skipped) - known_other = [ + known_other_hashes = [ ('des_crypt', '6f8c114b58f2c'), ('md5_crypt', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'), ('sha512_crypt', "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc" "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1"), ] - #list of various secrets all algs are tested with to make sure they work - standard_secrets = [ - '', - ' ', - 'my socrates note', - 'Compl3X AlphaNu3meric', - '4lpHa N|_|M3r1K W/ Cur51|\\|g: #$%(*)(*%#', - 'Really Long Password (tm), which is all the rage nowadays. Maybe some Shakespeare?', - ] - - unicode_secrets = [ - u'test with unic\u00D6de', - ] + #========================================================= + # + #========================================================= #optional prefix to prepend to name of test method as it's called, #useful when multiple handler test classes being run. @@ -262,11 +257,6 @@ class HandlerCase(TestCase): #alg interface helpers - allows subclass to overide how # default tests invoke the handler (eg for context_kwds) #========================================================= - def do_concat(self, secret, prefix): - "concatenate prefix onto secret" - #NOTE: this is subclassable mainly for some algorithms - #which accept non-strings in secret - return prefix + secret def do_encrypt(self, secret, **kwds): "call handler's encrypt method with specified options" @@ -280,11 +270,25 @@ class HandlerCase(TestCase): "call handler's identify method" return self.handler.identify(hash) + def do_genconfig(self, **kwds): + "call handler's genconfig method with specified options" + return self.handler.genconfig(**kwds) + + def do_genhash(self, secret, config): + "call handler's genhash method with specified options" + return self.handler.genhash(secret, config) + + def create_mismatch(self, secret): + "return other secret which won't match" + #NOTE: this is subclassable mainly for some algorithms + #which accept non-strings in secret + return 'x' + secret + #========================================================= #attributes #========================================================= - def test_00_attributes(self): - "test handler attributes are all defined" + def test_00_required_attributes(self): + "test required handler attributes are defined" handler = self.handler def ga(name): return getattr(handler, name, None) @@ -294,7 +298,17 @@ class HandlerCase(TestCase): self.assert_(name.lower() == name, "name not lower-case:") self.assert_(re.match("^[a-z0-9_]+$", name), "name must be alphanum + underscore: %r" % (name,)) - def test_01_base_handler(self): + settings = ga("setting_kwds") + self.assert_(settings is not None, "setting_kwds must be defined:") + self.assertIsInstance(settings, tuple, "setting_kwds must be a tuple:") + + context = ga("context_kwds") + self.assert_(context is not None, "context_kwds must be defined:") + self.assertIsInstance(context, tuple, "context_kwds must be a tuple:") + + #TODO: check optional rounds attributes & salt attributes + + def test_04_base_handler(self): "check configuration of BaseHash-derived classes" h = self.handler if not isinstance(h, type) or not issubclass(h, BaseHash): @@ -302,139 +316,174 @@ class HandlerCase(TestCase): h.validate_class() #should raise AssertionError if something's wrong. #========================================================= - #identify + #identify() #========================================================= - def test_10_identify_other(self): - "test identify() against other schemes' hashes" - for name, hash in self.known_other: - self.assertEqual(self.do_identify(hash), name == self.handler.name) - - def test_11_identify_positive(self): + def test_10_identify_hash(self): "test identify() against scheme's own hashes" - for secret, hash in self.known_correct: - self.assertEqual(self.do_identify(hash), True) + for secret, hash in self.known_correct_hashes: + self.assertEqual(self.do_identify(hash), True, "hash=%r:" % (hash,)) - for secret, hash in self.known_incorrect: - self.assertEqual(self.do_identify(hash), True) + for config, secret, hash in self.known_correct_configs: + self.assertEqual(self.do_identify(hash), True, "hash=%r:" % (hash,)) - for hash in self.known_identified_invalid: - self.assertEqual(self.do_identify(hash), True) + def test_11_identify_config(self): + "test identify() against scheme's own config strings" + if not self.known_correct_configs: + raise SkipTest + for config, secret, hash in self.known_correct_configs: + self.assertEqual(self.do_identify(config), True, "config=%r:" % (config,)) - def test_12_identify_invalid(self): - "test identify() against malformed instances of scheme's own hashes" - if not self.known_invalid: + def test_12_identify_unidentified(self): + "test identify() against scheme's own hashes that are mangled beyond identification" + if not self.known_unidentified_hashes: raise SkipTest - for hash in self.known_invalid: + for hash in self.known_unidentified_hashes: self.assertEqual(self.do_identify(hash), False, "hash=%r:" % (hash,)) - def test_13_identify_none(self): + def test_13_identify_malformed(self): + "test identify() against scheme's own hashes that are mangled but identifiable" + if not self.known_malformed_hashes: + raise SkipTest + for hash in self.known_malformed_hashes: + self.assertEqual(self.do_identify(hash), True, "hash=%r:" % (hash,)) + + def test_14_identify_other(self): + "test identify() against other schemes' hashes" + for name, hash in self.known_other_hashes: + self.assertEqual(self.do_identify(hash), name == self.handler.name, "scheme=%r, hash=%r:" % (name, hash)) + + def test_15_identify_none(self): "test identify() against None / empty string" self.assertEqual(self.do_identify(None), False) self.assertEqual(self.do_identify(''), False) #========================================================= - #verify + #verify() #========================================================= def test_20_verify_positive(self): "test verify() against known-correct secret/hash pairs" - self.assert_(self.known_correct, "test must define known_correct hashes") - for secret, hash in self.known_correct: - self.assertEqual(self.do_verify(secret, hash), True, "known correct hash (secret=%r, hash=%r):" % (secret,hash)) + self.assert_(self.known_correct_hashes or self.known_correct_configs, + "test must define at least one of known_correct_hashes or known_correct_configs") - def test_21_verify_negative(self): - "test verify() against known-incorrect secret/hash pairs" - if not self.known_incorrect: - raise SkipTest - for secret, hash in self.known_incorrect: - self.assertEqual(self.do_verify(secret, hash), False) + for secret, hash in self.known_correct_hashes: + self.assertEqual(self.do_verify(secret, hash), True, + "known correct hash (secret=%r, hash=%r):" % (secret,hash)) - #XXX: is this needed if known_incorrect is defined? - def test_22_verify_derived_negative(self): - "test verify() against derived incorrect secret/hash pairs" - for secret, hash in self.known_correct: - self.assertEqual(self.do_verify(self.do_concat(secret,'x'), hash), False) + for config, secret, hash in self.known_correct_configs: + self.assertEqual(self.do_verify(secret, hash), True, + "known correct hash (secret=%r, hash=%r):" % (secret,hash)) - def test_23_verify_other(self): + def test_21_verify_other(self): "test verify() throws error against other algorithm's hashes" - for name, hash in self.known_other: + for name, hash in self.known_other_hashes: if name == self.handler.name: continue - self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="verify other %r %r:" % (name, hash)) + self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="scheme=%r, hash=%r:" % (name, hash)) - def test_24_verify_invalid(self): + def test_22_verify_invalid(self): "test verify() throws error against known-invalid hashes" - if not self.known_invalid and not self.known_identified_invalid: + if not self.known_unidentified_hashes and not self.known_malformed_hashes: raise SkipTest - for hash in self.known_invalid + self.known_identified_invalid: - self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="verify invalid %r:" % (hash,)) + for hash in self.known_unidentified_hashes: + self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="hash=%r:" % (hash,)) + for hash in self.known_malformed_hashes: + self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="hash=%r:" % (hash,)) - def test_25_verify_none(self): + def test_23_verify_none(self): "test verify() throws error against hash=None/empty string" #find valid hash so that doesn't mask error - self.assertRaises(ValueError, self.do_verify, 'stub', None, __msg__="verify None:") - self.assertRaises(ValueError, self.do_verify, 'stub', '', __msg__="verify empty:") + self.assertRaises(ValueError, self.do_verify, 'stub', None, __msg__="hash=None:") + self.assertRaises(ValueError, self.do_verify, 'stub', '', __msg__="hash='':") + + #========================================================= + #genconfig() + #========================================================= + #NOTE: no specific genconfig tests yet, but testing in other cases + + #========================================================= + #genhash() + #========================================================= + filter_known_config_warnings = None + + def test_40_genhash_config(self): + "test genhash() against known config strings" + if not self.known_correct_configs: + raise SkipTest + fk = self.filter_known_config_warnings + if fk: + if catch_warnings: + ctx = catch_warnings() + ctx.__enter__() + fk() + for config, secret, hash in self.known_correct_configs: + result = self.do_genhash(secret, config) + self.assertEquals(result, hash, "config=%r,secret=%r:" % (config,secret)) + if fk and catch_warnings: + ctx.__exit__(None,None,None) + + def test_41_genhash_hash(self): + "test genhash() against known hash strings" + if not self.known_correct_hashes: + raise SkipTest + handler = self.handler + for secret, hash in self.known_correct_hashes: + result = self.do_genhash(secret, hash) + self.assertEquals(result, hash, "secret=%r:" % (secret,)) + + def test_42_genhash_genconfig(self): + "test genhash() against genconfig() output" + handler = self.handler + config = handler.genconfig() + hash = self.do_genhash("stub", config) + self.assert_(handler.identify(hash)) #========================================================= - #encrypt + #encrypt() #========================================================= + def test_50_encrypt_plain(self): + "test plain encrypt()" + if self.supports_unicode: + secret = u"unic\u00D6de" + else: + secret = "too many secrets" + result = self.do_encrypt(secret) + self.assert_(self.do_identify(result)) + self.assert_(self.do_verify(secret, result)) + + def test_51_encrypt_none(self): + "test encrypt() refused secret=None" + self.assertRaises(TypeError, self.do_encrypt, None) - #--------------------------------------------------------- - #test encryption against various secrets - #--------------------------------------------------------- - def test_30_encrypt_standard(self): - "test encrypt() against standard secrets" - for secret in self.standard_secrets: - self.check_encrypt(secret) - - def test_31_encrypt_unicode(self): - "test encrypt() against unicode secrets" - if not self.supports_unicode: + #========================================================= + #test salt generation + #========================================================= + def test_60_genconfig_salt(self): + "test genconfig() generates new salts" + if 'salt' not in self.handler.setting_kwds: raise SkipTest - for secret in self.unicode_secrets: - self.check_encrypt(secret) - - #this is probably excessive - ##def test_32_encrypt_positive(self): - ## "test encrypt() against known-correct secret/hash pairs" - ## for secret, hash in self.known_correct: - ## self.check_encrypt(secret) - - def check_encrypt(self, secret): - "check encrypt() behavior for a given secret" - #hash the secret - hash = self.do_encrypt(secret) - - #test identification - self.assertEqual(self.do_identify(hash), True, "identify hash %r from secret %r:" % (hash, secret)) - - #test positive verification - self.assertEqual(self.do_verify(secret, hash), True, "verify hash %r from secret %r:" % (hash, secret)) - - #test negative verification - for other in ['', 'test', self.do_concat(secret,'x')]: - if other != secret: - self.assertEqual(self.do_verify(other, hash), False, - "hash collision: %r and %r => %r" % (secret, other, hash)) - - #--------------------------------------------------------- - #test salt handling - #--------------------------------------------------------- - def test_33_encrypt_gensalt(self): - "test encrypt() generates new salt each time" + c1 = self.do_genconfig() + c2 = self.do_genconfig() + self.assertNotEquals(c1,c2) + + def test_61_encrypt_salt(self): + "test encrypt() generates new salts" if 'salt' not in self.handler.setting_kwds: raise SkipTest - for secret, hash in self.known_correct: - hash2 = self.do_encrypt(secret) - self.assertNotEqual(hash, hash2) + if self.known_correct_hashes: + secret, hash = self.known_correct_hashes[0] + else: + _, secret, hash = self.known_correct_configs[0] + hash2 = self.do_encrypt(secret) + self.assertNotEquals(hash,hash2) #TODO: test too-short user-provided salts #TODO: test too-long user-provided salts #TODO: test invalid char in user-provided salts - #--------------------------------------------------------- - #test secret handling - #--------------------------------------------------------- - def test_37_secret_chars(self): + #========================================================= + #test max password size + #========================================================= + def test_70_secret_chars(self): "test secret_chars limit" sc = self.secret_chars @@ -472,21 +521,6 @@ class HandlerCase(TestCase): hash = self.do_encrypt(secret) self.assert_(not self.do_verify(secret[:-1] + alt, hash)) - def test_38_encrypt_none(self): - "test encrypt() refused secret=None" - self.assertRaises(TypeError, self.do_encrypt, None) - - #========================================================= - # - #========================================================= - - #TODO: check genhash works - #TODO: check genconfig works - - #TODO: check parse method works - #TODO: check render method works - #TODO: check default/min/max_rounds valid if present - #========================================================= #eoc #========================================================= |
