You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							744 lines
						
					
					
						
							29 KiB
						
					
					
				
			
		
		
	
	
							744 lines
						
					
					
						
							29 KiB
						
					
					
				"""tests for passlib.context
 | 
						|
 | 
						|
this file is a clone of the 1.5 test_context.py,
 | 
						|
containing the tests using the legacy CryptPolicy api.
 | 
						|
it's being preserved here to ensure the old api doesn't break
 | 
						|
(until Passlib 1.8, when this and the legacy api will be removed).
 | 
						|
"""
 | 
						|
#=============================================================================
 | 
						|
# imports
 | 
						|
#=============================================================================
 | 
						|
from __future__ import with_statement
 | 
						|
# core
 | 
						|
from logging import getLogger
 | 
						|
import os
 | 
						|
import warnings
 | 
						|
# site
 | 
						|
try:
 | 
						|
    from pkg_resources import resource_filename
 | 
						|
except ImportError:
 | 
						|
    resource_filename = None
 | 
						|
# pkg
 | 
						|
from passlib import hash
 | 
						|
from passlib.context import CryptContext, CryptPolicy, LazyCryptContext
 | 
						|
from passlib.utils import to_bytes, to_unicode
 | 
						|
import passlib.utils.handlers as uh
 | 
						|
from passlib.tests.utils import TestCase, set_file
 | 
						|
from passlib.registry import (register_crypt_handler_path,
 | 
						|
                        _has_crypt_handler as has_crypt_handler,
 | 
						|
                        _unload_handler_name as unload_handler_name,
 | 
						|
                        )
 | 
						|
# module
 | 
						|
log = getLogger(__name__)
 | 
						|
 | 
						|
#=============================================================================
 | 
						|
#
 | 
						|
#=============================================================================
 | 
						|
class CryptPolicyTest(TestCase):
 | 
						|
    """test CryptPolicy object"""
 | 
						|
 | 
						|
    # TODO: need to test user categories w/in all this
 | 
						|
 | 
						|
    descriptionPrefix = "CryptPolicy"
 | 
						|
 | 
						|
    #===================================================================
 | 
						|
    # sample crypt policies used for testing
 | 
						|
    #===================================================================
 | 
						|
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    # sample 1 - average config file
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    # NOTE: copy of this is stored in file passlib/tests/sample_config_1s.cfg
 | 
						|
    sample_config_1s = """\
 | 
						|
[passlib]
 | 
						|
schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt
 | 
						|
default = md5_crypt
 | 
						|
all.vary_rounds = 10%%
 | 
						|
bsdi_crypt.max_rounds = 30000
 | 
						|
bsdi_crypt.default_rounds = 25000
 | 
						|
sha512_crypt.max_rounds = 50000
 | 
						|
sha512_crypt.min_rounds = 40000
 | 
						|
"""
 | 
						|
    sample_config_1s_path = os.path.abspath(os.path.join(
 | 
						|
        os.path.dirname(__file__), "sample_config_1s.cfg"))
 | 
						|
    if not os.path.exists(sample_config_1s_path) and resource_filename:
 | 
						|
        # in case we're zipped up in an egg.
 | 
						|
        sample_config_1s_path = resource_filename("passlib.tests",
 | 
						|
                                                  "sample_config_1s.cfg")
 | 
						|
 | 
						|
    # make sure sample_config_1s uses \n linesep - tests rely on this
 | 
						|
    assert sample_config_1s.startswith("[passlib]\nschemes")
 | 
						|
 | 
						|
    sample_config_1pd = dict(
 | 
						|
        schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
 | 
						|
        default = "md5_crypt",
 | 
						|
        # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
        all__vary_rounds = 0.1,
 | 
						|
        bsdi_crypt__max_rounds = 30000,
 | 
						|
        bsdi_crypt__default_rounds = 25000,
 | 
						|
        sha512_crypt__max_rounds = 50000,
 | 
						|
        sha512_crypt__min_rounds = 40000,
 | 
						|
    )
 | 
						|
 | 
						|
    sample_config_1pid = {
 | 
						|
        "schemes": "des_crypt, md5_crypt, bsdi_crypt, sha512_crypt",
 | 
						|
        "default": "md5_crypt",
 | 
						|
        # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
        "all.vary_rounds": 0.1,
 | 
						|
        "bsdi_crypt.max_rounds": 30000,
 | 
						|
        "bsdi_crypt.default_rounds": 25000,
 | 
						|
        "sha512_crypt.max_rounds": 50000,
 | 
						|
        "sha512_crypt.min_rounds": 40000,
 | 
						|
    }
 | 
						|
 | 
						|
    sample_config_1prd = dict(
 | 
						|
        schemes = [ hash.des_crypt, hash.md5_crypt, hash.bsdi_crypt, hash.sha512_crypt],
 | 
						|
        default = "md5_crypt", # NOTE: passlib <= 1.5 was handler obj.
 | 
						|
        # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
        all__vary_rounds = 0.1,
 | 
						|
        bsdi_crypt__max_rounds = 30000,
 | 
						|
        bsdi_crypt__default_rounds = 25000,
 | 
						|
        sha512_crypt__max_rounds = 50000,
 | 
						|
        sha512_crypt__min_rounds = 40000,
 | 
						|
    )
 | 
						|
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    # sample 2 - partial policy & result of overlay on sample 1
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    sample_config_2s = """\
 | 
						|
[passlib]
 | 
						|
bsdi_crypt.min_rounds = 29000
 | 
						|
bsdi_crypt.max_rounds = 35000
 | 
						|
bsdi_crypt.default_rounds = 31000
 | 
						|
sha512_crypt.min_rounds = 45000
 | 
						|
"""
 | 
						|
 | 
						|
    sample_config_2pd = dict(
 | 
						|
        # using this to test full replacement of existing options
 | 
						|
        bsdi_crypt__min_rounds = 29000,
 | 
						|
        bsdi_crypt__max_rounds = 35000,
 | 
						|
        bsdi_crypt__default_rounds = 31000,
 | 
						|
        # using this to test partial replacement of existing options
 | 
						|
        sha512_crypt__min_rounds=45000,
 | 
						|
    )
 | 
						|
 | 
						|
    sample_config_12pd = dict(
 | 
						|
        schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
 | 
						|
        default = "md5_crypt",
 | 
						|
        # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
        all__vary_rounds = 0.1,
 | 
						|
        bsdi_crypt__min_rounds = 29000,
 | 
						|
        bsdi_crypt__max_rounds = 35000,
 | 
						|
        bsdi_crypt__default_rounds = 31000,
 | 
						|
        sha512_crypt__max_rounds = 50000,
 | 
						|
        sha512_crypt__min_rounds=45000,
 | 
						|
    )
 | 
						|
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    # sample 3 - just changing default
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    sample_config_3pd = dict(
 | 
						|
        default="sha512_crypt",
 | 
						|
    )
 | 
						|
 | 
						|
    sample_config_123pd = dict(
 | 
						|
        schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
 | 
						|
        default = "sha512_crypt",
 | 
						|
        # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
        all__vary_rounds = 0.1,
 | 
						|
        bsdi_crypt__min_rounds = 29000,
 | 
						|
        bsdi_crypt__max_rounds = 35000,
 | 
						|
        bsdi_crypt__default_rounds = 31000,
 | 
						|
        sha512_crypt__max_rounds = 50000,
 | 
						|
        sha512_crypt__min_rounds=45000,
 | 
						|
    )
 | 
						|
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    # sample 4 - category specific
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    sample_config_4s = """
 | 
						|
[passlib]
 | 
						|
schemes = sha512_crypt
 | 
						|
all.vary_rounds = 10%%
 | 
						|
default.sha512_crypt.max_rounds = 20000
 | 
						|
admin.all.vary_rounds = 5%%
 | 
						|
admin.sha512_crypt.max_rounds = 40000
 | 
						|
"""
 | 
						|
 | 
						|
    sample_config_4pd = dict(
 | 
						|
        schemes = [ "sha512_crypt" ],
 | 
						|
        # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
        all__vary_rounds = 0.1,
 | 
						|
        sha512_crypt__max_rounds = 20000,
 | 
						|
        # NOTE: not maintaining backwards compat for rendering to "5%"
 | 
						|
        admin__all__vary_rounds = 0.05,
 | 
						|
        admin__sha512_crypt__max_rounds = 40000,
 | 
						|
        )
 | 
						|
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    # sample 5 - to_string & deprecation testing
 | 
						|
    #---------------------------------------------------------------
 | 
						|
    sample_config_5s = sample_config_1s + """\
 | 
						|
deprecated = des_crypt
 | 
						|
admin__context__deprecated = des_crypt, bsdi_crypt
 | 
						|
"""
 | 
						|
 | 
						|
    sample_config_5pd = sample_config_1pd.copy()
 | 
						|
    sample_config_5pd.update(
 | 
						|
        deprecated = [ "des_crypt" ],
 | 
						|
        admin__context__deprecated = [ "des_crypt", "bsdi_crypt" ],
 | 
						|
    )
 | 
						|
 | 
						|
    sample_config_5pid = sample_config_1pid.copy()
 | 
						|
    sample_config_5pid.update({
 | 
						|
        "deprecated": "des_crypt",
 | 
						|
        "admin.context.deprecated": "des_crypt, bsdi_crypt",
 | 
						|
    })
 | 
						|
 | 
						|
    sample_config_5prd = sample_config_1prd.copy()
 | 
						|
    sample_config_5prd.update({
 | 
						|
        # XXX: should deprecated return the actual handlers in this case?
 | 
						|
        #      would have to modify how policy stores info, for one.
 | 
						|
        "deprecated": ["des_crypt"],
 | 
						|
        "admin__context__deprecated": ["des_crypt", "bsdi_crypt"],
 | 
						|
    })
 | 
						|
 | 
						|
    #===================================================================
 | 
						|
    # constructors
 | 
						|
    #===================================================================
 | 
						|
    def setUp(self):
 | 
						|
        TestCase.setUp(self)
 | 
						|
        warnings.filterwarnings("ignore",
 | 
						|
                                r"The CryptPolicy class has been deprecated")
 | 
						|
        warnings.filterwarnings("ignore",
 | 
						|
                                r"the method.*hash_needs_update.*is deprecated")
 | 
						|
        warnings.filterwarnings("ignore", "The 'all' scheme is deprecated.*")
 | 
						|
        warnings.filterwarnings("ignore", "bsdi_crypt rounds should be odd")
 | 
						|
 | 
						|
    def test_00_constructor(self):
 | 
						|
        """test CryptPolicy() constructor"""
 | 
						|
        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"],
 | 
						|
            bad__key__bsdi_crypt__max_rounds = 30000,
 | 
						|
            )
 | 
						|
 | 
						|
        # check nameless handler rejected
 | 
						|
        class nameless(uh.StaticHandler):
 | 
						|
            name = None
 | 
						|
        self.assertRaises(ValueError, CryptPolicy, schemes=[nameless])
 | 
						|
 | 
						|
        # check scheme must be name or crypt handler
 | 
						|
        self.assertRaises(TypeError, CryptPolicy, schemes=[uh.StaticHandler])
 | 
						|
 | 
						|
        # check name conflicts are rejected
 | 
						|
        class dummy_1(uh.StaticHandler):
 | 
						|
            name = 'dummy_1'
 | 
						|
        self.assertRaises(KeyError, CryptPolicy, schemes=[dummy_1, dummy_1])
 | 
						|
 | 
						|
        # with unknown deprecated value
 | 
						|
        self.assertRaises(KeyError, CryptPolicy,
 | 
						|
                          schemes=['des_crypt'],
 | 
						|
                          deprecated=['md5_crypt'])
 | 
						|
 | 
						|
        # with unknown default value
 | 
						|
        self.assertRaises(KeyError, CryptPolicy,
 | 
						|
                          schemes=['des_crypt'],
 | 
						|
                          default='md5_crypt')
 | 
						|
 | 
						|
    def test_01_from_path_simple(self):
 | 
						|
        """test CryptPolicy.from_path() constructor"""
 | 
						|
        # NOTE: this is separate so it can also run under GAE
 | 
						|
 | 
						|
        # test preset stored in existing file
 | 
						|
        path = self.sample_config_1s_path
 | 
						|
        policy = CryptPolicy.from_path(path)
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # test if path missing
 | 
						|
        self.assertRaises(EnvironmentError, CryptPolicy.from_path, path + 'xxx')
 | 
						|
 | 
						|
    def test_01_from_path(self):
 | 
						|
        """test CryptPolicy.from_path() constructor with encodings"""
 | 
						|
        path = self.mktemp()
 | 
						|
 | 
						|
        # test "\n" linesep
 | 
						|
        set_file(path, self.sample_config_1s)
 | 
						|
        policy = CryptPolicy.from_path(path)
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # test "\r\n" linesep
 | 
						|
        set_file(path, self.sample_config_1s.replace("\n","\r\n"))
 | 
						|
        policy = CryptPolicy.from_path(path)
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # test with custom encoding
 | 
						|
        uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8")
 | 
						|
        set_file(path, uc2)
 | 
						|
        policy = CryptPolicy.from_path(path, encoding="utf-16")
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
    def test_02_from_string(self):
 | 
						|
        """test CryptPolicy.from_string() constructor"""
 | 
						|
        # test "\n" linesep
 | 
						|
        policy = CryptPolicy.from_string(self.sample_config_1s)
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # test "\r\n" linesep
 | 
						|
        policy = CryptPolicy.from_string(
 | 
						|
            self.sample_config_1s.replace("\n","\r\n"))
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # test with unicode
 | 
						|
        data = to_unicode(self.sample_config_1s)
 | 
						|
        policy = CryptPolicy.from_string(data)
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # test with non-ascii-compatible encoding
 | 
						|
        uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8")
 | 
						|
        policy = CryptPolicy.from_string(uc2, encoding="utf-16")
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # test category specific options
 | 
						|
        policy = CryptPolicy.from_string(self.sample_config_4s)
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_4pd)
 | 
						|
 | 
						|
    def test_03_from_source(self):
 | 
						|
        """test CryptPolicy.from_source() constructor"""
 | 
						|
        # pass it a path
 | 
						|
        policy = CryptPolicy.from_source(self.sample_config_1s_path)
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # pass it a string
 | 
						|
        policy = CryptPolicy.from_source(self.sample_config_1s)
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # pass it a dict (NOTE: make a copy to detect in-place modifications)
 | 
						|
        policy = CryptPolicy.from_source(self.sample_config_1pd.copy())
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # pass it existing policy
 | 
						|
        p2 = CryptPolicy.from_source(policy)
 | 
						|
        self.assertIs(policy, p2)
 | 
						|
 | 
						|
        # pass it something wrong
 | 
						|
        self.assertRaises(TypeError, CryptPolicy.from_source, 1)
 | 
						|
        self.assertRaises(TypeError, CryptPolicy.from_source, [])
 | 
						|
 | 
						|
    def test_04_from_sources(self):
 | 
						|
        """test CryptPolicy.from_sources() constructor"""
 | 
						|
 | 
						|
        # pass it empty list
 | 
						|
        self.assertRaises(ValueError, CryptPolicy.from_sources, [])
 | 
						|
 | 
						|
        # pass it one-element list
 | 
						|
        policy = CryptPolicy.from_sources([self.sample_config_1s])
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
 | 
						|
 | 
						|
        # pass multiple sources
 | 
						|
        policy = CryptPolicy.from_sources(
 | 
						|
            [
 | 
						|
            self.sample_config_1s_path,
 | 
						|
            self.sample_config_2s,
 | 
						|
            self.sample_config_3pd,
 | 
						|
            ])
 | 
						|
        self.assertEqual(policy.to_dict(), self.sample_config_123pd)
 | 
						|
 | 
						|
    def test_05_replace(self):
 | 
						|
        """test CryptPolicy.replace() constructor"""
 | 
						|
 | 
						|
        p1 = CryptPolicy(**self.sample_config_1pd)
 | 
						|
 | 
						|
        # check overlaying sample 2
 | 
						|
        p2 = p1.replace(**self.sample_config_2pd)
 | 
						|
        self.assertEqual(p2.to_dict(), self.sample_config_12pd)
 | 
						|
 | 
						|
        # check repeating overlay makes no change
 | 
						|
        p2b = p2.replace(**self.sample_config_2pd)
 | 
						|
        self.assertEqual(p2b.to_dict(), self.sample_config_12pd)
 | 
						|
 | 
						|
        # check overlaying sample 3
 | 
						|
        p3 = p2.replace(self.sample_config_3pd)
 | 
						|
        self.assertEqual(p3.to_dict(), self.sample_config_123pd)
 | 
						|
 | 
						|
    def test_06_forbidden(self):
 | 
						|
        """test CryptPolicy() forbidden kwds"""
 | 
						|
 | 
						|
        # salt not allowed to be set
 | 
						|
        self.assertRaises(KeyError, CryptPolicy,
 | 
						|
            schemes=["des_crypt"],
 | 
						|
            des_crypt__salt="xx",
 | 
						|
        )
 | 
						|
        self.assertRaises(KeyError, CryptPolicy,
 | 
						|
            schemes=["des_crypt"],
 | 
						|
            all__salt="xx",
 | 
						|
        )
 | 
						|
 | 
						|
        # schemes not allowed for category
 | 
						|
        self.assertRaises(KeyError, CryptPolicy,
 | 
						|
            schemes=["des_crypt"],
 | 
						|
            user__context__schemes=["md5_crypt"],
 | 
						|
        )
 | 
						|
 | 
						|
    #===================================================================
 | 
						|
    # reading
 | 
						|
    #===================================================================
 | 
						|
    def test_10_has_schemes(self):
 | 
						|
        """test has_schemes() method"""
 | 
						|
 | 
						|
        p1 = CryptPolicy(**self.sample_config_1pd)
 | 
						|
        self.assertTrue(p1.has_schemes())
 | 
						|
 | 
						|
        p3 = CryptPolicy(**self.sample_config_3pd)
 | 
						|
        self.assertTrue(not p3.has_schemes())
 | 
						|
 | 
						|
    def test_11_iter_handlers(self):
 | 
						|
        """test iter_handlers() method"""
 | 
						|
 | 
						|
        p1 = CryptPolicy(**self.sample_config_1pd)
 | 
						|
        s = self.sample_config_1prd['schemes']
 | 
						|
        self.assertEqual(list(p1.iter_handlers()), s)
 | 
						|
 | 
						|
        p3 = CryptPolicy(**self.sample_config_3pd)
 | 
						|
        self.assertEqual(list(p3.iter_handlers()), [])
 | 
						|
 | 
						|
    def test_12_get_handler(self):
 | 
						|
        """test get_handler() method"""
 | 
						|
 | 
						|
        p1 = CryptPolicy(**self.sample_config_1pd)
 | 
						|
 | 
						|
        # check by name
 | 
						|
        self.assertIs(p1.get_handler("bsdi_crypt"), hash.bsdi_crypt)
 | 
						|
 | 
						|
        # check by missing name
 | 
						|
        self.assertIs(p1.get_handler("sha256_crypt"), None)
 | 
						|
        self.assertRaises(KeyError, p1.get_handler, "sha256_crypt", required=True)
 | 
						|
 | 
						|
        # check default
 | 
						|
        self.assertIs(p1.get_handler(), hash.md5_crypt)
 | 
						|
 | 
						|
    def test_13_get_options(self):
 | 
						|
        """test get_options() method"""
 | 
						|
 | 
						|
        p12 = CryptPolicy(**self.sample_config_12pd)
 | 
						|
 | 
						|
        self.assertEqual(p12.get_options("bsdi_crypt"),dict(
 | 
						|
            # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
            vary_rounds = 0.1,
 | 
						|
            min_rounds = 29000,
 | 
						|
            max_rounds = 35000,
 | 
						|
            default_rounds = 31000,
 | 
						|
        ))
 | 
						|
 | 
						|
        self.assertEqual(p12.get_options("sha512_crypt"),dict(
 | 
						|
            # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
            vary_rounds = 0.1,
 | 
						|
            min_rounds = 45000,
 | 
						|
            max_rounds = 50000,
 | 
						|
        ))
 | 
						|
 | 
						|
        p4 = CryptPolicy.from_string(self.sample_config_4s)
 | 
						|
        self.assertEqual(p4.get_options("sha512_crypt"), dict(
 | 
						|
            # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
            vary_rounds=0.1,
 | 
						|
            max_rounds=20000,
 | 
						|
        ))
 | 
						|
 | 
						|
        self.assertEqual(p4.get_options("sha512_crypt", "user"), dict(
 | 
						|
            # NOTE: not maintaining backwards compat for rendering to "10%"
 | 
						|
            vary_rounds=0.1,
 | 
						|
            max_rounds=20000,
 | 
						|
        ))
 | 
						|
 | 
						|
        self.assertEqual(p4.get_options("sha512_crypt", "admin"), dict(
 | 
						|
            # NOTE: not maintaining backwards compat for rendering to "5%"
 | 
						|
            vary_rounds=0.05,
 | 
						|
            max_rounds=40000,
 | 
						|
        ))
 | 
						|
 | 
						|
    def test_14_handler_is_deprecated(self):
 | 
						|
        """test handler_is_deprecated() method"""
 | 
						|
        pa = CryptPolicy(**self.sample_config_1pd)
 | 
						|
        pb = CryptPolicy(**self.sample_config_5pd)
 | 
						|
 | 
						|
        self.assertFalse(pa.handler_is_deprecated("des_crypt"))
 | 
						|
        self.assertFalse(pa.handler_is_deprecated(hash.bsdi_crypt))
 | 
						|
        self.assertFalse(pa.handler_is_deprecated("sha512_crypt"))
 | 
						|
 | 
						|
        self.assertTrue(pb.handler_is_deprecated("des_crypt"))
 | 
						|
        self.assertFalse(pb.handler_is_deprecated(hash.bsdi_crypt))
 | 
						|
        self.assertFalse(pb.handler_is_deprecated("sha512_crypt"))
 | 
						|
 | 
						|
        # check categories as well
 | 
						|
        self.assertTrue(pb.handler_is_deprecated("des_crypt", "user"))
 | 
						|
        self.assertFalse(pb.handler_is_deprecated("bsdi_crypt", "user"))
 | 
						|
        self.assertTrue(pb.handler_is_deprecated("des_crypt", "admin"))
 | 
						|
        self.assertTrue(pb.handler_is_deprecated("bsdi_crypt", "admin"))
 | 
						|
 | 
						|
        # check deprecation is overridden per category
 | 
						|
        pc = CryptPolicy(
 | 
						|
            schemes=["md5_crypt", "des_crypt"],
 | 
						|
            deprecated=["md5_crypt"],
 | 
						|
            user__context__deprecated=["des_crypt"],
 | 
						|
        )
 | 
						|
        self.assertTrue(pc.handler_is_deprecated("md5_crypt"))
 | 
						|
        self.assertFalse(pc.handler_is_deprecated("des_crypt"))
 | 
						|
        self.assertFalse(pc.handler_is_deprecated("md5_crypt", "user"))
 | 
						|
        self.assertTrue(pc.handler_is_deprecated("des_crypt", "user"))
 | 
						|
 | 
						|
    def test_15_min_verify_time(self):
 | 
						|
        """test get_min_verify_time() method"""
 | 
						|
        # silence deprecation warnings for min verify time
 | 
						|
        warnings.filterwarnings("ignore", category=DeprecationWarning)
 | 
						|
 | 
						|
        pa = CryptPolicy()
 | 
						|
        self.assertEqual(pa.get_min_verify_time(), 0)
 | 
						|
        self.assertEqual(pa.get_min_verify_time('admin'), 0)
 | 
						|
 | 
						|
        pb = pa.replace(min_verify_time=.1)
 | 
						|
        self.assertEqual(pb.get_min_verify_time(), 0)
 | 
						|
        self.assertEqual(pb.get_min_verify_time('admin'), 0)
 | 
						|
 | 
						|
    #===================================================================
 | 
						|
    # serialization
 | 
						|
    #===================================================================
 | 
						|
    def test_20_iter_config(self):
 | 
						|
        """test iter_config() method"""
 | 
						|
        p5 = CryptPolicy(**self.sample_config_5pd)
 | 
						|
        self.assertEqual(dict(p5.iter_config()), self.sample_config_5pd)
 | 
						|
        self.assertEqual(dict(p5.iter_config(resolve=True)), self.sample_config_5prd)
 | 
						|
        self.assertEqual(dict(p5.iter_config(ini=True)), self.sample_config_5pid)
 | 
						|
 | 
						|
    def test_21_to_dict(self):
 | 
						|
        """test to_dict() method"""
 | 
						|
        p5 = CryptPolicy(**self.sample_config_5pd)
 | 
						|
        self.assertEqual(p5.to_dict(), self.sample_config_5pd)
 | 
						|
        self.assertEqual(p5.to_dict(resolve=True), self.sample_config_5prd)
 | 
						|
 | 
						|
    def test_22_to_string(self):
 | 
						|
        """test to_string() method"""
 | 
						|
        pa = CryptPolicy(**self.sample_config_5pd)
 | 
						|
        s = pa.to_string() # NOTE: can't compare string directly, ordering etc may not match
 | 
						|
        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)
 | 
						|
 | 
						|
    #===================================================================
 | 
						|
    #
 | 
						|
    #===================================================================
 | 
						|
 | 
						|
#=============================================================================
 | 
						|
# CryptContext
 | 
						|
#=============================================================================
 | 
						|
class CryptContextTest(TestCase):
 | 
						|
    """test CryptContext class"""
 | 
						|
    descriptionPrefix = "CryptContext"
 | 
						|
 | 
						|
    def setUp(self):
 | 
						|
        TestCase.setUp(self)
 | 
						|
        warnings.filterwarnings("ignore",
 | 
						|
                                r"CryptContext\(\)\.replace\(\) has been deprecated.*")
 | 
						|
        warnings.filterwarnings("ignore",
 | 
						|
                                r"The CryptContext ``policy`` keyword has been deprecated.*")
 | 
						|
        warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*")
 | 
						|
        warnings.filterwarnings("ignore",
 | 
						|
                                r"the method.*hash_needs_update.*is deprecated")
 | 
						|
 | 
						|
    #===================================================================
 | 
						|
    # constructor
 | 
						|
    #===================================================================
 | 
						|
    def test_00_constructor(self):
 | 
						|
        """test constructor"""
 | 
						|
        # create crypt context using handlers
 | 
						|
        cc = CryptContext([hash.md5_crypt, hash.bsdi_crypt, hash.des_crypt])
 | 
						|
        c,b,a = cc.policy.iter_handlers()
 | 
						|
        self.assertIs(a, hash.des_crypt)
 | 
						|
        self.assertIs(b, hash.bsdi_crypt)
 | 
						|
        self.assertIs(c, hash.md5_crypt)
 | 
						|
 | 
						|
        # create context using names
 | 
						|
        cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"])
 | 
						|
        c,b,a = cc.policy.iter_handlers()
 | 
						|
        self.assertIs(a, hash.des_crypt)
 | 
						|
        self.assertIs(b, hash.bsdi_crypt)
 | 
						|
        self.assertIs(c, hash.md5_crypt)
 | 
						|
 | 
						|
        # 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()"""
 | 
						|
 | 
						|
        cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"])
 | 
						|
        self.assertIs(cc.policy.get_handler(), hash.md5_crypt)
 | 
						|
 | 
						|
        cc2 = cc.replace()
 | 
						|
        self.assertIsNot(cc2, cc)
 | 
						|
        # NOTE: was not able to maintain backward compatibility with this...
 | 
						|
        ##self.assertIs(cc2.policy, cc.policy)
 | 
						|
 | 
						|
        cc3 = cc.replace(default="bsdi_crypt")
 | 
						|
        self.assertIsNot(cc3, cc)
 | 
						|
        # NOTE: was not able to maintain backward compatibility with this...
 | 
						|
        ##self.assertIs(cc3.policy, cc.policy)
 | 
						|
        self.assertIs(cc3.policy.get_handler(), hash.bsdi_crypt)
 | 
						|
 | 
						|
    def test_02_no_handlers(self):
 | 
						|
        """test no handlers"""
 | 
						|
 | 
						|
        # check constructor...
 | 
						|
        cc = CryptContext()
 | 
						|
        self.assertRaises(KeyError, cc.identify, 'hash', required=True)
 | 
						|
        self.assertRaises(KeyError, cc.hash, 'secret')
 | 
						|
        self.assertRaises(KeyError, cc.verify, 'secret', 'hash')
 | 
						|
 | 
						|
        # check updating policy after the fact...
 | 
						|
        cc = CryptContext(['md5_crypt'])
 | 
						|
        p = CryptPolicy(schemes=[])
 | 
						|
        cc.policy = p
 | 
						|
 | 
						|
        self.assertRaises(KeyError, cc.identify, 'hash', required=True)
 | 
						|
        self.assertRaises(KeyError, cc.hash, 'secret')
 | 
						|
        self.assertRaises(KeyError, cc.verify, 'secret', 'hash')
 | 
						|
 | 
						|
    #===================================================================
 | 
						|
    # policy adaptation
 | 
						|
    #===================================================================
 | 
						|
    sample_policy_1 = dict(
 | 
						|
            schemes = [ "des_crypt", "md5_crypt", "phpass", "bsdi_crypt",
 | 
						|
                       "sha256_crypt"],
 | 
						|
            deprecated = [ "des_crypt", ],
 | 
						|
            default = "sha256_crypt",
 | 
						|
            bsdi_crypt__max_rounds = 30,
 | 
						|
            bsdi_crypt__default_rounds = 25,
 | 
						|
            bsdi_crypt__vary_rounds = 0,
 | 
						|
            sha256_crypt__max_rounds = 3000,
 | 
						|
            sha256_crypt__min_rounds = 2000,
 | 
						|
            sha256_crypt__default_rounds = 3000,
 | 
						|
            phpass__ident = "H",
 | 
						|
            phpass__default_rounds = 7,
 | 
						|
    )
 | 
						|
 | 
						|
    def test_12_hash_needs_update(self):
 | 
						|
        """test hash_needs_update() method"""
 | 
						|
        cc = CryptContext(**self.sample_policy_1)
 | 
						|
 | 
						|
        # check deprecated scheme
 | 
						|
        self.assertTrue(cc.hash_needs_update('9XXD4trGYeGJA'))
 | 
						|
        self.assertFalse(cc.hash_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'))
 | 
						|
 | 
						|
        # 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'))
 | 
						|
 | 
						|
    #===================================================================
 | 
						|
    # border cases
 | 
						|
    #===================================================================
 | 
						|
    def test_30_nonstring_hash(self):
 | 
						|
        """test non-string hash values cause error"""
 | 
						|
        warnings.filterwarnings("ignore", ".*needs_update.*'scheme' keyword is deprecated.*")
 | 
						|
 | 
						|
        #
 | 
						|
        # test hash=None or some other non-string causes TypeError
 | 
						|
        # and that explicit-scheme code path behaves the same.
 | 
						|
        #
 | 
						|
        cc = CryptContext(["des_crypt"])
 | 
						|
        for hash, kwds in [
 | 
						|
                (None, {}),
 | 
						|
                # NOTE: 'scheme' kwd is deprecated...
 | 
						|
                (None, {"scheme": "des_crypt"}),
 | 
						|
                (1, {}),
 | 
						|
                ((), {}),
 | 
						|
                ]:
 | 
						|
 | 
						|
            self.assertRaises(TypeError, cc.hash_needs_update, hash, **kwds)
 | 
						|
 | 
						|
        cc2 = CryptContext(["mysql323"])
 | 
						|
        self.assertRaises(TypeError, cc2.hash_needs_update, None)
 | 
						|
 | 
						|
    #===================================================================
 | 
						|
    # eoc
 | 
						|
    #===================================================================
 | 
						|
 | 
						|
#=============================================================================
 | 
						|
# LazyCryptContext
 | 
						|
#=============================================================================
 | 
						|
class dummy_2(uh.StaticHandler):
 | 
						|
    name = "dummy_2"
 | 
						|
 | 
						|
class LazyCryptContextTest(TestCase):
 | 
						|
    descriptionPrefix = "LazyCryptContext"
 | 
						|
 | 
						|
    def setUp(self):
 | 
						|
        TestCase.setUp(self)
 | 
						|
 | 
						|
        # make sure this isn't registered before OR after
 | 
						|
        unload_handler_name("dummy_2")
 | 
						|
        self.addCleanup(unload_handler_name, "dummy_2")
 | 
						|
 | 
						|
        # silence some warnings
 | 
						|
        warnings.filterwarnings("ignore",
 | 
						|
                                r"CryptContext\(\)\.replace\(\) has been deprecated")
 | 
						|
        warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*")
 | 
						|
 | 
						|
    def test_kwd_constructor(self):
 | 
						|
        """test plain kwds"""
 | 
						|
        self.assertFalse(has_crypt_handler("dummy_2"))
 | 
						|
        register_crypt_handler_path("dummy_2", "passlib.tests.test_context")
 | 
						|
 | 
						|
        cc = LazyCryptContext(iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"])
 | 
						|
 | 
						|
        self.assertFalse(has_crypt_handler("dummy_2", True))
 | 
						|
 | 
						|
        self.assertTrue(cc.policy.handler_is_deprecated("des_crypt"))
 | 
						|
        self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"])
 | 
						|
 | 
						|
        self.assertTrue(has_crypt_handler("dummy_2", True))
 | 
						|
 | 
						|
    def test_callable_constructor(self):
 | 
						|
        """test create_policy() hook, returning CryptPolicy"""
 | 
						|
        self.assertFalse(has_crypt_handler("dummy_2"))
 | 
						|
        register_crypt_handler_path("dummy_2", "passlib.tests.test_context")
 | 
						|
 | 
						|
        def create_policy(flag=False):
 | 
						|
            self.assertTrue(flag)
 | 
						|
            return CryptPolicy(schemes=iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"])
 | 
						|
 | 
						|
        cc = LazyCryptContext(create_policy=create_policy, flag=True)
 | 
						|
 | 
						|
        self.assertFalse(has_crypt_handler("dummy_2", True))
 | 
						|
 | 
						|
        self.assertTrue(cc.policy.handler_is_deprecated("des_crypt"))
 | 
						|
        self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"])
 | 
						|
 | 
						|
        self.assertTrue(has_crypt_handler("dummy_2", True))
 | 
						|
 | 
						|
#=============================================================================
 | 
						|
# eof
 | 
						|
#=============================================================================
 |