1"""tests for passlib.context 2 3this file is a clone of the 1.5 test_context.py, 4containing the tests using the legacy CryptPolicy api. 5it's being preserved here to ensure the old api doesn't break 6(until Passlib 1.8, when this and the legacy api will be removed). 7""" 8#============================================================================= 9# imports 10#============================================================================= 11from __future__ import with_statement 12# core 13from logging import getLogger 14import os 15import warnings 16# site 17try: 18 from pkg_resources import resource_filename 19except ImportError: 20 resource_filename = None 21# pkg 22from passlib import hash 23from passlib.context import CryptContext, CryptPolicy, LazyCryptContext 24from passlib.utils import to_bytes, to_unicode 25import passlib.utils.handlers as uh 26from passlib.tests.utils import TestCase, set_file 27from passlib.registry import (register_crypt_handler_path, 28 _has_crypt_handler as has_crypt_handler, 29 _unload_handler_name as unload_handler_name, 30 ) 31# module 32log = getLogger(__name__) 33 34#============================================================================= 35# 36#============================================================================= 37class CryptPolicyTest(TestCase): 38 """test CryptPolicy object""" 39 40 # TODO: need to test user categories w/in all this 41 42 descriptionPrefix = "CryptPolicy" 43 44 #=================================================================== 45 # sample crypt policies used for testing 46 #=================================================================== 47 48 #--------------------------------------------------------------- 49 # sample 1 - average config file 50 #--------------------------------------------------------------- 51 # NOTE: copy of this is stored in file passlib/tests/sample_config_1s.cfg 52 sample_config_1s = """\ 53[passlib] 54schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt 55default = md5_crypt 56all.vary_rounds = 10%% 57bsdi_crypt.max_rounds = 30000 58bsdi_crypt.default_rounds = 25000 59sha512_crypt.max_rounds = 50000 60sha512_crypt.min_rounds = 40000 61""" 62 sample_config_1s_path = os.path.abspath(os.path.join( 63 os.path.dirname(__file__), "sample_config_1s.cfg")) 64 if not os.path.exists(sample_config_1s_path) and resource_filename: 65 # in case we're zipped up in an egg. 66 sample_config_1s_path = resource_filename("passlib.tests", 67 "sample_config_1s.cfg") 68 69 # make sure sample_config_1s uses \n linesep - tests rely on this 70 assert sample_config_1s.startswith("[passlib]\nschemes") 71 72 sample_config_1pd = dict( 73 schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], 74 default = "md5_crypt", 75 # NOTE: not maintaining backwards compat for rendering to "10%" 76 all__vary_rounds = 0.1, 77 bsdi_crypt__max_rounds = 30000, 78 bsdi_crypt__default_rounds = 25000, 79 sha512_crypt__max_rounds = 50000, 80 sha512_crypt__min_rounds = 40000, 81 ) 82 83 sample_config_1pid = { 84 "schemes": "des_crypt, md5_crypt, bsdi_crypt, sha512_crypt", 85 "default": "md5_crypt", 86 # NOTE: not maintaining backwards compat for rendering to "10%" 87 "all.vary_rounds": 0.1, 88 "bsdi_crypt.max_rounds": 30000, 89 "bsdi_crypt.default_rounds": 25000, 90 "sha512_crypt.max_rounds": 50000, 91 "sha512_crypt.min_rounds": 40000, 92 } 93 94 sample_config_1prd = dict( 95 schemes = [ hash.des_crypt, hash.md5_crypt, hash.bsdi_crypt, hash.sha512_crypt], 96 default = "md5_crypt", # NOTE: passlib <= 1.5 was handler obj. 97 # NOTE: not maintaining backwards compat for rendering to "10%" 98 all__vary_rounds = 0.1, 99 bsdi_crypt__max_rounds = 30000, 100 bsdi_crypt__default_rounds = 25000, 101 sha512_crypt__max_rounds = 50000, 102 sha512_crypt__min_rounds = 40000, 103 ) 104 105 #--------------------------------------------------------------- 106 # sample 2 - partial policy & result of overlay on sample 1 107 #--------------------------------------------------------------- 108 sample_config_2s = """\ 109[passlib] 110bsdi_crypt.min_rounds = 29000 111bsdi_crypt.max_rounds = 35000 112bsdi_crypt.default_rounds = 31000 113sha512_crypt.min_rounds = 45000 114""" 115 116 sample_config_2pd = dict( 117 # using this to test full replacement of existing options 118 bsdi_crypt__min_rounds = 29000, 119 bsdi_crypt__max_rounds = 35000, 120 bsdi_crypt__default_rounds = 31000, 121 # using this to test partial replacement of existing options 122 sha512_crypt__min_rounds=45000, 123 ) 124 125 sample_config_12pd = dict( 126 schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], 127 default = "md5_crypt", 128 # NOTE: not maintaining backwards compat for rendering to "10%" 129 all__vary_rounds = 0.1, 130 bsdi_crypt__min_rounds = 29000, 131 bsdi_crypt__max_rounds = 35000, 132 bsdi_crypt__default_rounds = 31000, 133 sha512_crypt__max_rounds = 50000, 134 sha512_crypt__min_rounds=45000, 135 ) 136 137 #--------------------------------------------------------------- 138 # sample 3 - just changing default 139 #--------------------------------------------------------------- 140 sample_config_3pd = dict( 141 default="sha512_crypt", 142 ) 143 144 sample_config_123pd = dict( 145 schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], 146 default = "sha512_crypt", 147 # NOTE: not maintaining backwards compat for rendering to "10%" 148 all__vary_rounds = 0.1, 149 bsdi_crypt__min_rounds = 29000, 150 bsdi_crypt__max_rounds = 35000, 151 bsdi_crypt__default_rounds = 31000, 152 sha512_crypt__max_rounds = 50000, 153 sha512_crypt__min_rounds=45000, 154 ) 155 156 #--------------------------------------------------------------- 157 # sample 4 - category specific 158 #--------------------------------------------------------------- 159 sample_config_4s = """ 160[passlib] 161schemes = sha512_crypt 162all.vary_rounds = 10%% 163default.sha512_crypt.max_rounds = 20000 164admin.all.vary_rounds = 5%% 165admin.sha512_crypt.max_rounds = 40000 166""" 167 168 sample_config_4pd = dict( 169 schemes = [ "sha512_crypt" ], 170 # NOTE: not maintaining backwards compat for rendering to "10%" 171 all__vary_rounds = 0.1, 172 sha512_crypt__max_rounds = 20000, 173 # NOTE: not maintaining backwards compat for rendering to "5%" 174 admin__all__vary_rounds = 0.05, 175 admin__sha512_crypt__max_rounds = 40000, 176 ) 177 178 #--------------------------------------------------------------- 179 # sample 5 - to_string & deprecation testing 180 #--------------------------------------------------------------- 181 sample_config_5s = sample_config_1s + """\ 182deprecated = des_crypt 183admin__context__deprecated = des_crypt, bsdi_crypt 184""" 185 186 sample_config_5pd = sample_config_1pd.copy() 187 sample_config_5pd.update( 188 deprecated = [ "des_crypt" ], 189 admin__context__deprecated = [ "des_crypt", "bsdi_crypt" ], 190 ) 191 192 sample_config_5pid = sample_config_1pid.copy() 193 sample_config_5pid.update({ 194 "deprecated": "des_crypt", 195 "admin.context.deprecated": "des_crypt, bsdi_crypt", 196 }) 197 198 sample_config_5prd = sample_config_1prd.copy() 199 sample_config_5prd.update({ 200 # XXX: should deprecated return the actual handlers in this case? 201 # would have to modify how policy stores info, for one. 202 "deprecated": ["des_crypt"], 203 "admin__context__deprecated": ["des_crypt", "bsdi_crypt"], 204 }) 205 206 #=================================================================== 207 # constructors 208 #=================================================================== 209 def setUp(self): 210 TestCase.setUp(self) 211 warnings.filterwarnings("ignore", 212 r"The CryptPolicy class has been deprecated") 213 warnings.filterwarnings("ignore", 214 r"the method.*hash_needs_update.*is deprecated") 215 warnings.filterwarnings("ignore", "The 'all' scheme is deprecated.*") 216 warnings.filterwarnings("ignore", "bsdi_crypt rounds should be odd") 217 218 def test_00_constructor(self): 219 """test CryptPolicy() constructor""" 220 policy = CryptPolicy(**self.sample_config_1pd) 221 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 222 223 policy = CryptPolicy(self.sample_config_1pd) 224 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 225 226 self.assertRaises(TypeError, CryptPolicy, {}, {}) 227 self.assertRaises(TypeError, CryptPolicy, {}, dummy=1) 228 229 # check key with too many separators is rejected 230 self.assertRaises(TypeError, CryptPolicy, 231 schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], 232 bad__key__bsdi_crypt__max_rounds = 30000, 233 ) 234 235 # check nameless handler rejected 236 class nameless(uh.StaticHandler): 237 name = None 238 self.assertRaises(ValueError, CryptPolicy, schemes=[nameless]) 239 240 # check scheme must be name or crypt handler 241 self.assertRaises(TypeError, CryptPolicy, schemes=[uh.StaticHandler]) 242 243 # check name conflicts are rejected 244 class dummy_1(uh.StaticHandler): 245 name = 'dummy_1' 246 self.assertRaises(KeyError, CryptPolicy, schemes=[dummy_1, dummy_1]) 247 248 # with unknown deprecated value 249 self.assertRaises(KeyError, CryptPolicy, 250 schemes=['des_crypt'], 251 deprecated=['md5_crypt']) 252 253 # with unknown default value 254 self.assertRaises(KeyError, CryptPolicy, 255 schemes=['des_crypt'], 256 default='md5_crypt') 257 258 def test_01_from_path_simple(self): 259 """test CryptPolicy.from_path() constructor""" 260 # NOTE: this is separate so it can also run under GAE 261 262 # test preset stored in existing file 263 path = self.sample_config_1s_path 264 policy = CryptPolicy.from_path(path) 265 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 266 267 # test if path missing 268 self.assertRaises(EnvironmentError, CryptPolicy.from_path, path + 'xxx') 269 270 def test_01_from_path(self): 271 """test CryptPolicy.from_path() constructor with encodings""" 272 path = self.mktemp() 273 274 # test "\n" linesep 275 set_file(path, self.sample_config_1s) 276 policy = CryptPolicy.from_path(path) 277 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 278 279 # test "\r\n" linesep 280 set_file(path, self.sample_config_1s.replace("\n","\r\n")) 281 policy = CryptPolicy.from_path(path) 282 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 283 284 # test with custom encoding 285 uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8") 286 set_file(path, uc2) 287 policy = CryptPolicy.from_path(path, encoding="utf-16") 288 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 289 290 def test_02_from_string(self): 291 """test CryptPolicy.from_string() constructor""" 292 # test "\n" linesep 293 policy = CryptPolicy.from_string(self.sample_config_1s) 294 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 295 296 # test "\r\n" linesep 297 policy = CryptPolicy.from_string( 298 self.sample_config_1s.replace("\n","\r\n")) 299 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 300 301 # test with unicode 302 data = to_unicode(self.sample_config_1s) 303 policy = CryptPolicy.from_string(data) 304 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 305 306 # test with non-ascii-compatible encoding 307 uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8") 308 policy = CryptPolicy.from_string(uc2, encoding="utf-16") 309 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 310 311 # test category specific options 312 policy = CryptPolicy.from_string(self.sample_config_4s) 313 self.assertEqual(policy.to_dict(), self.sample_config_4pd) 314 315 def test_03_from_source(self): 316 """test CryptPolicy.from_source() constructor""" 317 # pass it a path 318 policy = CryptPolicy.from_source(self.sample_config_1s_path) 319 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 320 321 # pass it a string 322 policy = CryptPolicy.from_source(self.sample_config_1s) 323 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 324 325 # pass it a dict (NOTE: make a copy to detect in-place modifications) 326 policy = CryptPolicy.from_source(self.sample_config_1pd.copy()) 327 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 328 329 # pass it existing policy 330 p2 = CryptPolicy.from_source(policy) 331 self.assertIs(policy, p2) 332 333 # pass it something wrong 334 self.assertRaises(TypeError, CryptPolicy.from_source, 1) 335 self.assertRaises(TypeError, CryptPolicy.from_source, []) 336 337 def test_04_from_sources(self): 338 """test CryptPolicy.from_sources() constructor""" 339 340 # pass it empty list 341 self.assertRaises(ValueError, CryptPolicy.from_sources, []) 342 343 # pass it one-element list 344 policy = CryptPolicy.from_sources([self.sample_config_1s]) 345 self.assertEqual(policy.to_dict(), self.sample_config_1pd) 346 347 # pass multiple sources 348 policy = CryptPolicy.from_sources( 349 [ 350 self.sample_config_1s_path, 351 self.sample_config_2s, 352 self.sample_config_3pd, 353 ]) 354 self.assertEqual(policy.to_dict(), self.sample_config_123pd) 355 356 def test_05_replace(self): 357 """test CryptPolicy.replace() constructor""" 358 359 p1 = CryptPolicy(**self.sample_config_1pd) 360 361 # check overlaying sample 2 362 p2 = p1.replace(**self.sample_config_2pd) 363 self.assertEqual(p2.to_dict(), self.sample_config_12pd) 364 365 # check repeating overlay makes no change 366 p2b = p2.replace(**self.sample_config_2pd) 367 self.assertEqual(p2b.to_dict(), self.sample_config_12pd) 368 369 # check overlaying sample 3 370 p3 = p2.replace(self.sample_config_3pd) 371 self.assertEqual(p3.to_dict(), self.sample_config_123pd) 372 373 def test_06_forbidden(self): 374 """test CryptPolicy() forbidden kwds""" 375 376 # salt not allowed to be set 377 self.assertRaises(KeyError, CryptPolicy, 378 schemes=["des_crypt"], 379 des_crypt__salt="xx", 380 ) 381 self.assertRaises(KeyError, CryptPolicy, 382 schemes=["des_crypt"], 383 all__salt="xx", 384 ) 385 386 # schemes not allowed for category 387 self.assertRaises(KeyError, CryptPolicy, 388 schemes=["des_crypt"], 389 user__context__schemes=["md5_crypt"], 390 ) 391 392 #=================================================================== 393 # reading 394 #=================================================================== 395 def test_10_has_schemes(self): 396 """test has_schemes() method""" 397 398 p1 = CryptPolicy(**self.sample_config_1pd) 399 self.assertTrue(p1.has_schemes()) 400 401 p3 = CryptPolicy(**self.sample_config_3pd) 402 self.assertTrue(not p3.has_schemes()) 403 404 def test_11_iter_handlers(self): 405 """test iter_handlers() method""" 406 407 p1 = CryptPolicy(**self.sample_config_1pd) 408 s = self.sample_config_1prd['schemes'] 409 self.assertEqual(list(p1.iter_handlers()), s) 410 411 p3 = CryptPolicy(**self.sample_config_3pd) 412 self.assertEqual(list(p3.iter_handlers()), []) 413 414 def test_12_get_handler(self): 415 """test get_handler() method""" 416 417 p1 = CryptPolicy(**self.sample_config_1pd) 418 419 # check by name 420 self.assertIs(p1.get_handler("bsdi_crypt"), hash.bsdi_crypt) 421 422 # check by missing name 423 self.assertIs(p1.get_handler("sha256_crypt"), None) 424 self.assertRaises(KeyError, p1.get_handler, "sha256_crypt", required=True) 425 426 # check default 427 self.assertIs(p1.get_handler(), hash.md5_crypt) 428 429 def test_13_get_options(self): 430 """test get_options() method""" 431 432 p12 = CryptPolicy(**self.sample_config_12pd) 433 434 self.assertEqual(p12.get_options("bsdi_crypt"),dict( 435 # NOTE: not maintaining backwards compat for rendering to "10%" 436 vary_rounds = 0.1, 437 min_rounds = 29000, 438 max_rounds = 35000, 439 default_rounds = 31000, 440 )) 441 442 self.assertEqual(p12.get_options("sha512_crypt"),dict( 443 # NOTE: not maintaining backwards compat for rendering to "10%" 444 vary_rounds = 0.1, 445 min_rounds = 45000, 446 max_rounds = 50000, 447 )) 448 449 p4 = CryptPolicy.from_string(self.sample_config_4s) 450 self.assertEqual(p4.get_options("sha512_crypt"), dict( 451 # NOTE: not maintaining backwards compat for rendering to "10%" 452 vary_rounds=0.1, 453 max_rounds=20000, 454 )) 455 456 self.assertEqual(p4.get_options("sha512_crypt", "user"), dict( 457 # NOTE: not maintaining backwards compat for rendering to "10%" 458 vary_rounds=0.1, 459 max_rounds=20000, 460 )) 461 462 self.assertEqual(p4.get_options("sha512_crypt", "admin"), dict( 463 # NOTE: not maintaining backwards compat for rendering to "5%" 464 vary_rounds=0.05, 465 max_rounds=40000, 466 )) 467 468 def test_14_handler_is_deprecated(self): 469 """test handler_is_deprecated() method""" 470 pa = CryptPolicy(**self.sample_config_1pd) 471 pb = CryptPolicy(**self.sample_config_5pd) 472 473 self.assertFalse(pa.handler_is_deprecated("des_crypt")) 474 self.assertFalse(pa.handler_is_deprecated(hash.bsdi_crypt)) 475 self.assertFalse(pa.handler_is_deprecated("sha512_crypt")) 476 477 self.assertTrue(pb.handler_is_deprecated("des_crypt")) 478 self.assertFalse(pb.handler_is_deprecated(hash.bsdi_crypt)) 479 self.assertFalse(pb.handler_is_deprecated("sha512_crypt")) 480 481 # check categories as well 482 self.assertTrue(pb.handler_is_deprecated("des_crypt", "user")) 483 self.assertFalse(pb.handler_is_deprecated("bsdi_crypt", "user")) 484 self.assertTrue(pb.handler_is_deprecated("des_crypt", "admin")) 485 self.assertTrue(pb.handler_is_deprecated("bsdi_crypt", "admin")) 486 487 # check deprecation is overridden per category 488 pc = CryptPolicy( 489 schemes=["md5_crypt", "des_crypt"], 490 deprecated=["md5_crypt"], 491 user__context__deprecated=["des_crypt"], 492 ) 493 self.assertTrue(pc.handler_is_deprecated("md5_crypt")) 494 self.assertFalse(pc.handler_is_deprecated("des_crypt")) 495 self.assertFalse(pc.handler_is_deprecated("md5_crypt", "user")) 496 self.assertTrue(pc.handler_is_deprecated("des_crypt", "user")) 497 498 def test_15_min_verify_time(self): 499 """test get_min_verify_time() method""" 500 # silence deprecation warnings for min verify time 501 warnings.filterwarnings("ignore", category=DeprecationWarning) 502 503 pa = CryptPolicy() 504 self.assertEqual(pa.get_min_verify_time(), 0) 505 self.assertEqual(pa.get_min_verify_time('admin'), 0) 506 507 pb = pa.replace(min_verify_time=.1) 508 self.assertEqual(pb.get_min_verify_time(), 0) 509 self.assertEqual(pb.get_min_verify_time('admin'), 0) 510 511 #=================================================================== 512 # serialization 513 #=================================================================== 514 def test_20_iter_config(self): 515 """test iter_config() method""" 516 p5 = CryptPolicy(**self.sample_config_5pd) 517 self.assertEqual(dict(p5.iter_config()), self.sample_config_5pd) 518 self.assertEqual(dict(p5.iter_config(resolve=True)), self.sample_config_5prd) 519 self.assertEqual(dict(p5.iter_config(ini=True)), self.sample_config_5pid) 520 521 def test_21_to_dict(self): 522 """test to_dict() method""" 523 p5 = CryptPolicy(**self.sample_config_5pd) 524 self.assertEqual(p5.to_dict(), self.sample_config_5pd) 525 self.assertEqual(p5.to_dict(resolve=True), self.sample_config_5prd) 526 527 def test_22_to_string(self): 528 """test to_string() method""" 529 pa = CryptPolicy(**self.sample_config_5pd) 530 s = pa.to_string() # NOTE: can't compare string directly, ordering etc may not match 531 pb = CryptPolicy.from_string(s) 532 self.assertEqual(pb.to_dict(), self.sample_config_5pd) 533 534 s = pa.to_string(encoding="latin-1") 535 self.assertIsInstance(s, bytes) 536 537 #=================================================================== 538 # 539 #=================================================================== 540 541#============================================================================= 542# CryptContext 543#============================================================================= 544class CryptContextTest(TestCase): 545 """test CryptContext class""" 546 descriptionPrefix = "CryptContext" 547 548 def setUp(self): 549 TestCase.setUp(self) 550 warnings.filterwarnings("ignore", 551 r"CryptContext\(\)\.replace\(\) has been deprecated.*") 552 warnings.filterwarnings("ignore", 553 r"The CryptContext ``policy`` keyword has been deprecated.*") 554 warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*") 555 warnings.filterwarnings("ignore", 556 r"the method.*hash_needs_update.*is deprecated") 557 558 #=================================================================== 559 # constructor 560 #=================================================================== 561 def test_00_constructor(self): 562 """test constructor""" 563 # create crypt context using handlers 564 cc = CryptContext([hash.md5_crypt, hash.bsdi_crypt, hash.des_crypt]) 565 c,b,a = cc.policy.iter_handlers() 566 self.assertIs(a, hash.des_crypt) 567 self.assertIs(b, hash.bsdi_crypt) 568 self.assertIs(c, hash.md5_crypt) 569 570 # create context using names 571 cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"]) 572 c,b,a = cc.policy.iter_handlers() 573 self.assertIs(a, hash.des_crypt) 574 self.assertIs(b, hash.bsdi_crypt) 575 self.assertIs(c, hash.md5_crypt) 576 577 # policy kwd 578 policy = cc.policy 579 cc = CryptContext(policy=policy) 580 self.assertEqual(cc.to_dict(), policy.to_dict()) 581 582 cc = CryptContext(policy=policy, default="bsdi_crypt") 583 self.assertNotEqual(cc.to_dict(), policy.to_dict()) 584 self.assertEqual(cc.to_dict(), dict(schemes=["md5_crypt","bsdi_crypt","des_crypt"], 585 default="bsdi_crypt")) 586 587 self.assertRaises(TypeError, setattr, cc, 'policy', None) 588 self.assertRaises(TypeError, CryptContext, policy='x') 589 590 def test_01_replace(self): 591 """test replace()""" 592 593 cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"]) 594 self.assertIs(cc.policy.get_handler(), hash.md5_crypt) 595 596 cc2 = cc.replace() 597 self.assertIsNot(cc2, cc) 598 # NOTE: was not able to maintain backward compatibility with this... 599 ##self.assertIs(cc2.policy, cc.policy) 600 601 cc3 = cc.replace(default="bsdi_crypt") 602 self.assertIsNot(cc3, cc) 603 # NOTE: was not able to maintain backward compatibility with this... 604 ##self.assertIs(cc3.policy, cc.policy) 605 self.assertIs(cc3.policy.get_handler(), hash.bsdi_crypt) 606 607 def test_02_no_handlers(self): 608 """test no handlers""" 609 610 # check constructor... 611 cc = CryptContext() 612 self.assertRaises(KeyError, cc.identify, 'hash', required=True) 613 self.assertRaises(KeyError, cc.hash, 'secret') 614 self.assertRaises(KeyError, cc.verify, 'secret', 'hash') 615 616 # check updating policy after the fact... 617 cc = CryptContext(['md5_crypt']) 618 p = CryptPolicy(schemes=[]) 619 cc.policy = p 620 621 self.assertRaises(KeyError, cc.identify, 'hash', required=True) 622 self.assertRaises(KeyError, cc.hash, 'secret') 623 self.assertRaises(KeyError, cc.verify, 'secret', 'hash') 624 625 #=================================================================== 626 # policy adaptation 627 #=================================================================== 628 sample_policy_1 = dict( 629 schemes = [ "des_crypt", "md5_crypt", "phpass", "bsdi_crypt", 630 "sha256_crypt"], 631 deprecated = [ "des_crypt", ], 632 default = "sha256_crypt", 633 bsdi_crypt__max_rounds = 30, 634 bsdi_crypt__default_rounds = 25, 635 bsdi_crypt__vary_rounds = 0, 636 sha256_crypt__max_rounds = 3000, 637 sha256_crypt__min_rounds = 2000, 638 sha256_crypt__default_rounds = 3000, 639 phpass__ident = "H", 640 phpass__default_rounds = 7, 641 ) 642 643 def test_12_hash_needs_update(self): 644 """test hash_needs_update() method""" 645 cc = CryptContext(**self.sample_policy_1) 646 647 # check deprecated scheme 648 self.assertTrue(cc.hash_needs_update('9XXD4trGYeGJA')) 649 self.assertFalse(cc.hash_needs_update('$1$J8HC2RCr$HcmM.7NxB2weSvlw2FgzU0')) 650 651 # check min rounds 652 self.assertTrue(cc.hash_needs_update('$5$rounds=1999$jD81UCoo.zI.UETs$Y7qSTQ6mTiU9qZB4fRr43wRgQq4V.5AAf7F97Pzxey/')) 653 self.assertFalse(cc.hash_needs_update('$5$rounds=2000$228SSRje04cnNCaQ$YGV4RYu.5sNiBvorQDlO0WWQjyJVGKBcJXz3OtyQ2u8')) 654 655 # check max rounds 656 self.assertFalse(cc.hash_needs_update('$5$rounds=3000$fS9iazEwTKi7QPW4$VasgBC8FqlOvD7x2HhABaMXCTh9jwHclPA9j5YQdns.')) 657 self.assertTrue(cc.hash_needs_update('$5$rounds=3001$QlFHHifXvpFX4PLs$/0ekt7lSs/lOikSerQ0M/1porEHxYq7W/2hdFpxA3fA')) 658 659 #=================================================================== 660 # border cases 661 #=================================================================== 662 def test_30_nonstring_hash(self): 663 """test non-string hash values cause error""" 664 warnings.filterwarnings("ignore", ".*needs_update.*'scheme' keyword is deprecated.*") 665 666 # 667 # test hash=None or some other non-string causes TypeError 668 # and that explicit-scheme code path behaves the same. 669 # 670 cc = CryptContext(["des_crypt"]) 671 for hash, kwds in [ 672 (None, {}), 673 # NOTE: 'scheme' kwd is deprecated... 674 (None, {"scheme": "des_crypt"}), 675 (1, {}), 676 ((), {}), 677 ]: 678 679 self.assertRaises(TypeError, cc.hash_needs_update, hash, **kwds) 680 681 cc2 = CryptContext(["mysql323"]) 682 self.assertRaises(TypeError, cc2.hash_needs_update, None) 683 684 #=================================================================== 685 # eoc 686 #=================================================================== 687 688#============================================================================= 689# LazyCryptContext 690#============================================================================= 691class dummy_2(uh.StaticHandler): 692 name = "dummy_2" 693 694class LazyCryptContextTest(TestCase): 695 descriptionPrefix = "LazyCryptContext" 696 697 def setUp(self): 698 TestCase.setUp(self) 699 700 # make sure this isn't registered before OR after 701 unload_handler_name("dummy_2") 702 self.addCleanup(unload_handler_name, "dummy_2") 703 704 # silence some warnings 705 warnings.filterwarnings("ignore", 706 r"CryptContext\(\)\.replace\(\) has been deprecated") 707 warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*") 708 709 def test_kwd_constructor(self): 710 """test plain kwds""" 711 self.assertFalse(has_crypt_handler("dummy_2")) 712 register_crypt_handler_path("dummy_2", "passlib.tests.test_context") 713 714 cc = LazyCryptContext(iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"]) 715 716 self.assertFalse(has_crypt_handler("dummy_2", True)) 717 718 self.assertTrue(cc.policy.handler_is_deprecated("des_crypt")) 719 self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"]) 720 721 self.assertTrue(has_crypt_handler("dummy_2", True)) 722 723 def test_callable_constructor(self): 724 """test create_policy() hook, returning CryptPolicy""" 725 self.assertFalse(has_crypt_handler("dummy_2")) 726 register_crypt_handler_path("dummy_2", "passlib.tests.test_context") 727 728 def create_policy(flag=False): 729 self.assertTrue(flag) 730 return CryptPolicy(schemes=iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"]) 731 732 cc = LazyCryptContext(create_policy=create_policy, flag=True) 733 734 self.assertFalse(has_crypt_handler("dummy_2", True)) 735 736 self.assertTrue(cc.policy.handler_is_deprecated("des_crypt")) 737 self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"]) 738 739 self.assertTrue(has_crypt_handler("dummy_2", True)) 740 741#============================================================================= 742# eof 743#============================================================================= 744