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 | |
| 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')
| -rw-r--r-- | passlib/tests/test_drivers.py | 131 | ||||
| -rw-r--r-- | passlib/tests/test_frontend.py | 15 | ||||
| -rw-r--r-- | passlib/tests/test_utils_drivers.py | 4 | ||||
| -rw-r--r-- | passlib/tests/utils.py | 312 |
4 files changed, 226 insertions, 236 deletions
diff --git a/passlib/tests/test_drivers.py b/passlib/tests/test_drivers.py index 75ca8be..bf577a8 100644 --- a/passlib/tests/test_drivers.py +++ b/passlib/tests/test_drivers.py @@ -6,10 +6,6 @@ from __future__ import with_statement #core import hashlib import logging; log = logging.getLogger(__name__) -try: - from warnings import catch_warnings -except ImportError: #wasn't added until py26 - catch_warnings = None import warnings #site #pkg @@ -24,11 +20,11 @@ class AprMd5CryptTest(HandlerCase): handler = apr_md5_crypt #values taken from http://httpd.apache.org/docs/2.2/misc/password_encryptions.html - known_correct = ( + known_correct_hashes = ( ('myPassword', '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA/'), ) - known_identified_invalid = [ + known_malformed_hashes = [ #bad char in otherwise correct hash '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA!' ] @@ -42,7 +38,7 @@ class BCryptTest(HandlerCase): handler = bcrypt secret_chars = 72 - known_correct = ( + known_correct_hashes = ( #selected bcrypt test vectors ('', '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'), ('a', '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'), @@ -51,12 +47,12 @@ class BCryptTest(HandlerCase): ('~!@#$%^&*() ~!@#$%^&*()PNBFRD', '$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'), ) - known_invalid = [ + known_unidentified_hashes = [ #unsupported minor version "$2b$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", ] - known_identified_invalid = [ + known_malformed_hashes = [ #bad char in otherwise correct hash "$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", #rounds not zero-padded (pybcrypt rejects this, therefore so do we) @@ -79,13 +75,13 @@ class BigCryptTest(HandlerCase): #TODO: find an authortative source of test vectors, #these were found in docs and messages on the web. - known_correct = [ + known_correct_hashes = [ ("passphrase", "qiyh4XPJGsOZ2MEAyLkfWqeQ"), ("This is very long passwd", "f8.SVpL2fvwjkAnxn8/rgTkwvrif6bjYB5c"), ] #omit des_crypt from known other, it looks like bigcrypt - known_other = filter(lambda row: row[0] != "des_crypt", HandlerCase.known_other) + known_other_hashes = filter(lambda row: row[0] != "des_crypt", HandlerCase.known_other_hashes) #========================================================= #bsdi crypt @@ -95,14 +91,14 @@ from passlib.drivers.des_crypt import bsdi_crypt class BSDiCryptTest(HandlerCase): "test BSDiCrypt algorithm" handler = bsdi_crypt - known_correct = [ + known_correct_hashes = [ (" ", "_K1..crsmZxOLzfJH8iw"), ("my", '_KR/.crsmykRplHbAvwA'), #<- to detect old 12-bit rounds bug ("my socra", "_K1..crsmf/9NzZr1fLM"), ("my socrates", '_K1..crsmOv1rbde9A9o'), ("my socrates note", "_K1..crsm/2qeAhdISMA"), ] - known_invalid = [ + known_unidentified_hashes = [ #bad char in otherwise correctly formatted hash "_K1.!crsmZxOLzfJH8iw" ] @@ -119,7 +115,7 @@ class Crypt16Test(HandlerCase): #TODO: find an authortative source of test vectors #instead of just msgs around the web # (eg http://seclists.org/bugtraq/1999/Mar/76) - known_correct = [ + known_correct_hashes = [ ("passphrase", "qi8H8R7OM4xMUNMPuRAZxlY."), ("printf", "aaCjFz4Sh8Eg2QSqAReePlq6"), ("printf", "AA/xje2RyeiSU0iBY3PDwjYo"), @@ -140,7 +136,7 @@ class DesCryptTest(HandlerCase): #TODO: test - known_correct = ( + known_correct_hashes = ( #secret, example hash which matches secret ('', 'OgAwTx2l6NADI'), (' ', '/Hk.VPuwQTXbc'), @@ -150,7 +146,7 @@ class DesCryptTest(HandlerCase): ('AlOtBsOl', 'cEpWz5IUCShqM'), (u'hell\u00D6', 'saykDgk3BPZ9E'), ) - known_invalid = [ + known_unidentified_hashes = [ #bad char in otherwise correctly formatted hash '!gAwTx2l6NADI', ] @@ -164,7 +160,7 @@ from passlib.drivers.md5_crypt import md5_crypt class Md5CryptTest(HandlerCase): handler = md5_crypt - known_correct = ( + known_correct_hashes = ( ('', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'), (' ', '$1$m/5ee7ol$bZn0kIBFipq39e.KDXX8I0'), ('test', '$1$ec6XvcoW$ghEtNK2U1MC5l.Dwgi3020'), @@ -173,7 +169,7 @@ class Md5CryptTest(HandlerCase): ('test', '$1$SuMrG47N$ymvzYjr7QcEQjaK5m1PGx1'), ) - known_identified_invalid = [ + known_malformed_hashes = [ #bad char in otherwise correct hash '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o!', ] @@ -188,13 +184,10 @@ from passlib.drivers.mysql import mysql323, mysql41 class Mysql323Test(HandlerCase): handler = mysql323 - #remove single space from secrets, since mysql-323 ignores all whitespace (?!) - standard_secrets = [ x for x in HandlerCase.standard_secrets if x != ' ' ] - - known_correct = ( + known_correct_hashes = ( ('mypass', '6f8c114b58f2ce9e'), ) - known_invalid = [ + known_unidentified_hashes = [ #bad char in otherwise correct hash '6z8c114b58f2ce9e', ] @@ -207,10 +200,10 @@ class Mysql323Test(HandlerCase): class Mysql41Test(HandlerCase): handler = mysql41 - known_correct = ( + known_correct_hashes = ( ('mypass', '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'), ) - known_invalid = [ + known_unidentified_hashes = [ #bad char in otherwise correct hash '*6Z8989366EAF75BB670AD8EA7A7FC1176A95CEF4', ] @@ -223,12 +216,12 @@ from passlib.drivers.nthash import nthash class NTHashTest(HandlerCase): handler = nthash - known_correct = ( + known_correct_hashes = ( ('passphrase', '$3$$7f8fe03093cc84b267b109625f6bbf4b'), ('passphrase', '$NT$7f8fe03093cc84b267b109625f6bbf4b'), ) - known_identified_invalid = [ + known_malformed_hashes = [ #bad char in otherwise correct hash '$3$$7f8fe03093cc84b267b109625f6bbfxb', ] @@ -241,13 +234,13 @@ from passlib.drivers.phpass import phpass class PHPassTest(HandlerCase): handler = phpass - known_correct = ( + known_correct_hashes = ( ('', '$P$7JaFQsPzJSuenezefD/3jHgt5hVfNH0'), ('compL3X!', '$P$FiS0N5L672xzQx1rt1vgdJQRYKnQM9/'), ('test12345', '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0'), #from the source ) - known_identified_invalid = [ + known_malformed_hashes = [ #bad char in otherwise correct hash '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r!L0', ] @@ -261,13 +254,13 @@ from passlib.drivers.postgres import postgres_md5, postgres_plaintext class PostgresMD5CryptTest(HandlerCase): handler = postgres_md5 - known_correct = [ + known_correct_hashes = [ # ((secret,user),hash) (('mypass', 'postgres'), 'md55fba2ea04fd36069d2574ea71c8efe9d'), (('mypass', 'root'), 'md540c31989b20437833f697e485811254b'), (("testpassword",'testuser'), 'md5d4fc5129cc2c25465a5370113ae9835f'), ] - known_invalid = [ + known_unidentified_hashes = [ #bad 'z' char in otherwise correct hash 'md54zc31989b20437833f697e485811254b', ] @@ -285,13 +278,12 @@ class PostgresMD5CryptTest(HandlerCase): self.assertRaises(TypeError, self.handler.encrypt, 'mypass') self.assertRaises(TypeError, self.handler.verify, 'mypass', 'md55fba2ea04fd36069d2574ea71c8efe9d') - def do_concat(self, secret, prefix): + def create_mismatch(self, secret): if isinstance(secret, tuple): secret, user = secret - secret = prefix + secret - return secret, user + return 'x' + secret, user else: - return prefix + secret + return 'x' + secret def do_encrypt(self, secret, **kwds): if isinstance(secret, tuple): @@ -309,6 +301,13 @@ class PostgresMD5CryptTest(HandlerCase): user = 'default' return self.handler.verify(secret, hash, user=user) + def do_genhash(self, secret, config): + if isinstance(secret, tuple): + secret, user = secret + else: + user = 'default' + return self.handler.genhash(secret, config, user=user) + #========================================================= # (netbsd's) sha1 crypt #========================================================= @@ -317,12 +316,12 @@ from passlib.drivers.sha1_crypt import sha1_crypt class SHA1CryptTest(HandlerCase): handler = sha1_crypt - known_correct = ( + known_correct_hashes = ( ("password", "$sha1$19703$iVdJqfSE$v4qYKl1zqYThwpjJAoKX6UvlHq/a"), ("password", "$sha1$21773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH"), ) - known_identified_invalid = [ + known_malformed_hashes = [ #bad char in otherwise correct hash '$sha1$21773$u!7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH', ] @@ -336,7 +335,7 @@ class SHA256CryptTest(HandlerCase): handler = sha256_crypt supports_unicode = True - known_correct = [ + known_correct_hashes = [ ('', '$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'), (' ', '$5$rounds=10376$I5lNtXtRmf.OoMd8$Ko3AI1VvTANdyKhBPavaRjJzNpSatKU6QVN9uwS9MH.'), ('test', '$5$rounds=11858$WH1ABM5sKhxbkgCK$aTQsjPkz0rBsH3lQlJxw9HDTDXPKBxC0LlVeV69P.t1'), @@ -345,7 +344,7 @@ class SHA256CryptTest(HandlerCase): (u'with unic\u00D6de', '$5$rounds=1000$IbG0EuGQXw5EkMdP$LQ5AfPf13KufFsKtmazqnzSGZ4pxtUNw3woQ.ELRDF4'), ] - known_identified_invalid = [ + known_malformed_hashes = [ #bad char in otherwise correct hash '$5$rounds=10428$uy/:jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMeZGsGx2aBvxTvDFI613c3', @@ -354,7 +353,7 @@ class SHA256CryptTest(HandlerCase): ] #NOTE: these test cases taken from official specification at http://www.akkadia.org/drepper/SHA-crypt.txt - cases256 = [ + known_correct_configs = [ #config, secret, result ( "$5$saltstring", "Hello world!", "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5" ), @@ -380,28 +379,9 @@ class SHA256CryptTest(HandlerCase): "2bIC" ), ] - def test_spec_vectors(self): - "verify sha256-crypt passes specification test vectors" - handler = self.handler - - #NOTE: the 'roundstoolow' test vector is known to raise a warning, which we silence here - if catch_warnings: - ctx = catch_warnings() - ctx.__enter__() + def filter_known_config_warnings(self): warnings.filterwarnings("ignore", "sha256_crypt does not allow less than 1000 rounds: 10", UserWarning) - for config, secret, hash in self.cases256: - #make sure we got expected result back - result = handler.genhash(secret, config) - self.assertEqual(result, hash, "hash=%r secret=%r:" % (hash, secret)) - - #make sure parser truncated salts - info = handler.from_string(config) - self.assert_(len(info.salt) <= 16, "hash=%r secret=%r:" % (hash, secret)) - - if catch_warnings: - ctx.__exit__(None,None,None) - BuiltinSHA256CryptTest = create_backend_case(SHA256CryptTest, "builtin") #========================================================= @@ -413,7 +393,7 @@ class SHA512CryptTest(HandlerCase): handler = sha512_crypt supports_unicode = True - known_correct = [ + known_correct_hashes = [ ('', '$6$rounds=11021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1'), (' ', '$6$rounds=11104$ED9SA4qGmd57Fq2m$q/.PqACDM/JpAHKmr86nkPzzuR5.YpYa8ZJJvI8Zd89ZPUYTJExsFEIuTYbM7gAGcQtTkCEhBKmp1S1QZwaXx0'), ('test', '$6$rounds=11531$G/gkPn17kHYo0gTF$Kq.uZBHlSBXyzsOJXtxJruOOH4yc0Is13uY7yK0PvAvXxbvc1w8DO1RzREMhKsc82K/Jh8OquV8FZUlreYPJk1'), @@ -421,7 +401,7 @@ class SHA512CryptTest(HandlerCase): ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$6$rounds=11065$5KXQoE1bztkY5IZr$Jf6krQSUKKOlKca4hSW07MSerFFzVIZt/N3rOTsUgKqp7cUdHrwV8MoIVNCk9q9WL3ZRMsdbwNXpVk0gVxKtz1'), ] - known_identified_invalid = [ + known_malformed_hashes = [ #zero-padded rounds '$6$rounds=011021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1', #bad char in otherwise correct hash @@ -429,7 +409,7 @@ class SHA512CryptTest(HandlerCase): ] #NOTE: these test cases taken from official specification at http://www.akkadia.org/drepper/SHA-crypt.txt - cases512 = [ + known_correct_configs = [ #config, secret, result ("$6$saltstring", "Hello world!", "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu" @@ -463,28 +443,9 @@ class SHA512CryptTest(HandlerCase): "hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." ), ] - def test_spec_vectors(self): - "verify sha512-crypt passes specification test vectors" - handler = self.handler - - #NOTE: the 'roundstoolow' test vector is known to raise a warning, which we silence here - if catch_warnings: - ctx = catch_warnings() - ctx.__enter__() + def filter_known_config_warnings(self): warnings.filterwarnings("ignore", "sha512_crypt does not allow less than 1000 rounds: 10", UserWarning) - for config, secret, hash in self.cases512: - #make sure we got expected result back - result = handler.genhash(secret, config) - self.assertEqual(result, hash, "hash=%r secret=%r:" % (hash, secret)) - - #make sure parser truncated salts - info = handler.from_string(config) - self.assert_(len(info.salt) <= 16, "hash=%r secret=%r:" % (hash, secret)) - - if catch_warnings: - ctx.__exit__(None,None,None) - BuiltinSHA512CryptTest = create_backend_case(SHA512CryptTest, "builtin") #========================================================= @@ -495,12 +456,12 @@ from passlib.drivers.sun_md5_crypt import sun_md5_crypt class SunMD5CryptTest(HandlerCase): handler = sun_md5_crypt - known_correct = [ + known_correct_hashes = [ #sample hash found at http://compgroups.net/comp.unix.solaris/password-file-in-linux-and-solaris-8-9 ("passwd", "$md5$RPgLF6IJ$WTvAlUJ7MqH5xak2FMEwS/"), ] - known_identified_invalid = [ + known_malformed_hashes = [ #bad char in otherwise correct hash "$md5$RPgL!6IJ$WTvAlUJ7MqH5xak2FMEwS/" ] diff --git a/passlib/tests/test_frontend.py b/passlib/tests/test_frontend.py index 5fd54bb..8297d75 100644 --- a/passlib/tests/test_frontend.py +++ b/passlib/tests/test_frontend.py @@ -42,16 +42,14 @@ class QuickAccessTest(TestCase): identify = mod.identify for cc in self.crypt_cases: name = cc.handler.name - for _, hash in cc.known_correct: + for _, hash in cc.known_correct_hashes: self.assertEqual(identify(hash), name) - for _, hash in cc.known_incorrect: - self.assertEqual(identify(hash), name) - for other, hash in cc.known_other: + for other, hash in cc.known_other_hashes: if other == name: self.assertEqual(identify(hash), name) else: self.assertNotEqual(identify(hash), name) - for hash in cc.known_invalid: + for hash in cc.known_unidentified_hashes: self.assertEqual(identify(hash), None) def test_01_verify(self): @@ -59,13 +57,10 @@ class QuickAccessTest(TestCase): verify = mod.verify for cc in self.crypt_cases: name = cc.handler.name - for secret, hash in cc.known_correct[:3]: + for secret, hash in cc.known_correct_hashes[:3]: self.assert_(verify(secret, hash)) self.assert_(verify(secret, hash, alg=name)) - for secret, hash in cc.known_incorrect[:3]: - self.assert_(not verify(secret, hash)) - self.assert_(not verify(secret, hash, alg=name)) - for hash in cc.known_invalid[:3]: + for hash in cc.known_unidentified_hashes[:3]: #context should raise ValueError because can't be identified self.assertRaises(ValueError, verify, secret, hash) diff --git a/passlib/tests/test_utils_drivers.py b/passlib/tests/test_utils_drivers.py index 1e96df0..9ac7db5 100644 --- a/passlib/tests/test_utils_drivers.py +++ b/passlib/tests/test_utils_drivers.py @@ -78,12 +78,12 @@ class SaltedHash(ExtHash): class UnsaltedHashTest(HandlerCase): handler = UnsaltedHash - known_correct = [] + known_correct_hashes = [] class SaltedHashTest(HandlerCase): handler = SaltedHash - known_correct = [] + known_correct_hashes = [] #========================================================= # 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 #========================================================= |
