Ejemplo n.º 1
0
 def test_handlers(self):
     "verify we have tests for all handlers"
     from passlib.registry import list_crypt_handlers
     from passlib.tests.test_handlers import get_handler_case
     for name in list_crypt_handlers():
         if name.startswith("ldap_") and name[5:] in list_crypt_handlers():
             continue
         if name in ["roundup_plaintext"]:
             continue
         self.assertTrue(get_handler_case(name))
Ejemplo n.º 2
0
 def test_handlers(self):
     "verify we have tests for all handlers"
     from passlib.registry import list_crypt_handlers
     from passlib.tests.test_handlers import get_handler_case
     for name in list_crypt_handlers():
         if name.startswith("ldap_") and name[5:] in list_crypt_handlers():
             continue
         if name in ["roundup_plaintext"]:
             continue
         self.assertTrue(get_handler_case(name))
Ejemplo n.º 3
0
 def test_handlers(self):
     """verify we have tests for all builtin handlers"""
     from passlib.registry import list_crypt_handlers
     from passlib.tests.test_handlers import get_handler_case, conditionally_available_hashes
     for name in list_crypt_handlers():
         # skip some wrappers that don't need independant testing
         if name.startswith("ldap_") and name[5:] in list_crypt_handlers():
             continue
         if name in ["roundup_plaintext"]:
             continue
         # check the remaining ones all have a handler
         try:
             self.assertTrue(get_handler_case(name))
         except exc.MissingBackendError:
             if name in conditionally_available_hashes:  # expected to fail on some setups
                 continue
             raise
Ejemplo n.º 4
0
 def test_handlers(self):
     """verify we have tests for all builtin handlers"""
     from passlib.registry import list_crypt_handlers
     from passlib.tests.test_handlers import get_handler_case
     for name in list_crypt_handlers():
         # skip some wrappers that don't need independant testing
         if name.startswith("ldap_") and name[5:] in list_crypt_handlers():
             continue
         if name in ["roundup_plaintext"]:
             continue
         # check the remaining ones all have a handler
         try:
             self.assertTrue(get_handler_case(name))
         except exc.MissingBackendError:
             if name in ["bcrypt", "bcrypt_sha256"]: # expected to fail on some setups
                 continue
             raise
Ejemplo n.º 5
0
    def test_config(self):
        """test hashing interface

        this function is run against both the actual django code, to
        verify the assumptions of the unittests are correct;
        and run against the passlib extension, to verify it matches
        those assumptions.
        """
        log = self.getLogger()
        patched, config = self.patched, self.config
        # this tests the following methods:
        #   User.set_password()
        #   User.check_password()
        #   make_password() -- 1.4 only
        #   check_password()
        #   identify_hasher()
        #   User.has_usable_password()
        #   User.set_unusable_password()
        # XXX: this take a while to run. what could be trimmed?

        #  TODO: get_hasher()

        #=======================================================
        # setup helpers & imports
        #=======================================================
        ctx = self.context
        setter = create_mock_setter()
        PASS1 = "toomanysecrets"
        WRONG1 = "letmein"

        from django.contrib.auth.hashers import (check_password, make_password,
                                                 is_password_usable,
                                                 identify_hasher)

        #=======================================================
        # make sure extension is configured correctly
        #=======================================================
        if patched:
            # contexts should match
            from passlib.ext.django.models import password_context
            self.assertEqual(password_context.to_dict(resolve=True),
                             ctx.to_dict(resolve=True))

            # should have patched both places
            from django.contrib.auth.models import check_password as check_password2
            self.assertEqual(check_password2, check_password)

        #=======================================================
        # default algorithm
        #=======================================================
        # User.set_password() should use default alg
        user = FakeUser()
        user.set_password(PASS1)
        self.assertTrue(ctx.handler().verify(PASS1, user.password))
        self.assert_valid_password(user)

        # User.check_password() - n/a

        # make_password() should use default alg
        hash = make_password(PASS1)
        self.assertTrue(ctx.handler().verify(PASS1, hash))

        # check_password() - n/a

        #=======================================================
        # empty password behavior
        #=======================================================

        # User.set_password() should use default alg
        user = FakeUser()
        user.set_password('')
        hash = user.password
        self.assertTrue(ctx.handler().verify('', hash))
        self.assert_valid_password(user, hash)

        # User.check_password() should return True
        self.assertTrue(user.check_password(""))
        self.assert_valid_password(user, hash)

        # no make_password()

        # check_password() should return True
        self.assertTrue(check_password("", hash))

        #=======================================================
        # 'unusable flag' behavior
        #=======================================================

        # sanity check via user.set_unusable_password()
        user = FakeUser()
        user.set_unusable_password()
        self.assert_unusable_password(user)

        # ensure User.set_password() sets unusable flag
        user = FakeUser()
        user.set_password(None)
        self.assert_unusable_password(user)

        # User.check_password() should always fail
        self.assertFalse(user.check_password(None))
        self.assertFalse(user.check_password('None'))
        self.assertFalse(user.check_password(''))
        self.assertFalse(user.check_password(PASS1))
        self.assertFalse(user.check_password(WRONG1))
        self.assert_unusable_password(user)

        # make_password() should also set flag
        self.assertTrue(make_password(None).startswith("!"))

        # check_password() should return False (didn't handle disabled under 1.3)
        self.assertFalse(check_password(PASS1, '!'))

        # identify_hasher() and is_password_usable() should reject it
        self.assertFalse(is_password_usable(user.password))
        self.assertRaises(ValueError, identify_hasher, user.password)

        #=======================================================
        # hash=None
        #=======================================================
        # User.set_password() - n/a

        # User.check_password() - returns False
        # FIXME: at some point past 1.8, some of these django started handler None differently;
        #        and/or throwing TypeError.  need to investigate when that change occurred;
        #        update these tests, and maybe passlib.ext.django as well.
        user = FakeUser()
        user.password = None
        self.assertFalse(user.check_password(PASS1))
        self.assertFalse(user.has_usable_password())

        # make_password() - n/a

        # check_password() - error
        self.assertFalse(check_password(PASS1, None))

        # identify_hasher() - error
        self.assertRaises(TypeError, identify_hasher, None)

        #=======================================================
        # empty & invalid hash values
        # NOTE: django 1.5 behavior change due to django ticket 18453
        # NOTE: passlib integration tries to match current django version
        #=======================================================
        for hash in (
                "",  # empty hash
                "$789$foo",  # empty identifier
        ):
            # User.set_password() - n/a

            # User.check_password()
            # As of django 1.5, blank OR invalid hash returns False
            user = FakeUser()
            user.password = hash
            self.assertFalse(user.check_password(PASS1))

            # verify hash wasn't changed/upgraded during check_password() call
            self.assertEqual(user.password, hash)
            self.assertEqual(user.pop_saved_passwords(), [])

            # User.has_usable_password()
            self.assertFalse(user.has_usable_password())

            # make_password() - n/a

            # check_password()
            self.assertFalse(check_password(PASS1, hash))

            # identify_hasher() - throws error
            self.assertRaises(ValueError, identify_hasher, hash)

        #=======================================================
        # run through all the schemes in the context,
        # testing various bits of per-scheme behavior.
        #=======================================================
        for scheme in ctx.schemes():

            #
            # TODO: break this loop up into separate parameterized tests.
            #

            #-------------------------------------------------------
            # setup constants & imports, pick a sample secret/hash combo
            #-------------------------------------------------------

            handler = ctx.handler(scheme)
            log.debug("testing scheme: %r => %r", scheme, handler)
            deprecated = ctx.handler(scheme).deprecated
            assert not deprecated or scheme != ctx.default_scheme()
            try:
                testcase = get_handler_case(scheme)
            except exc.MissingBackendError:
                continue
            assert handler_derived_from(handler, testcase.handler)
            if handler.is_disabled:
                continue

            # verify that django has a backend available
            # (since our hasher may use different set of backends,
            #  get_handler_case() above may work, but django will have nothing)
            if not patched and not check_django_hasher_has_backend(
                    handler.django_name):
                assert scheme in ["django_bcrypt", "django_bcrypt_sha256", "django_argon2"], \
                    "%r scheme should always have active backend" % scheme
                # TODO: make this a SkipTest() once this loop has been parameterized.
                log.warn("skipping scheme %r due to missing django dependancy",
                         scheme)
                continue

            # find a sample (secret, hash) pair to test with
            try:
                secret, hash = sample_hashes[scheme]
            except KeyError:
                get_sample_hash = testcase("setUp").get_sample_hash
                while True:
                    secret, hash = get_sample_hash()
                    if secret:  # don't select blank passwords
                        break
            other = 'dontletmein'

            #-------------------------------------------------------
            # User.set_password() - not tested here
            #-------------------------------------------------------

            #-------------------------------------------------------
            # User.check_password()+migration against known hash
            #-------------------------------------------------------
            user = FakeUser()
            user.password = hash

            # check against invalid password
            self.assertFalse(user.check_password(None))
            ##self.assertFalse(user.check_password(''))
            self.assertFalse(user.check_password(other))
            self.assert_valid_password(user, hash)

            # check against valid password
            self.assertTrue(user.check_password(secret))

            # check if it upgraded the hash
            # NOTE: needs_update kept separate in case we need to test rounds.
            needs_update = deprecated
            if needs_update:
                self.assertNotEqual(user.password, hash)
                self.assertFalse(handler.identify(user.password))
                self.assertTrue(ctx.handler().verify(secret, user.password))
                self.assert_valid_password(user, saved=user.password)
            else:
                self.assert_valid_password(user, hash)

            # don't need to check rest for most deployments
            if TEST_MODE(max="default"):
                continue

            #-------------------------------------------------------
            # make_password() correctly selects algorithm
            #-------------------------------------------------------
            alg = DjangoTranslator().passlib_to_django_name(scheme)
            hash2 = make_password(secret, hasher=alg)
            self.assertTrue(handler.verify(secret, hash2))

            #-------------------------------------------------------
            # check_password()+setter against known hash
            #-------------------------------------------------------
            # should call setter only if it needs_update
            self.assertTrue(check_password(secret, hash, setter=setter))
            self.assertEqual(setter.popstate(),
                             [secret] if needs_update else [])

            # should not call setter
            self.assertFalse(check_password(other, hash, setter=setter))
            self.assertEqual(setter.popstate(), [])

            ### check preferred kwd is ignored (feature we don't currently support fully)
            ##self.assertTrue(check_password(secret, hash, setter=setter, preferred='fooey'))
            ##self.assertEqual(setter.popstate(), [secret])

            # TODO: get_hasher()

            #-------------------------------------------------------
            # identify_hasher() recognizes known hash
            #-------------------------------------------------------
            self.assertTrue(is_password_usable(hash))
            name = DjangoTranslator().django_to_passlib_name(
                identify_hasher(hash).algorithm)
            self.assertEqual(name, scheme)
Ejemplo n.º 6
0
    def test_config(self):
        """test hashing interface

        this function is run against both the actual django code, to
        verify the assumptions of the unittests are correct;
        and run against the passlib extension, to verify it matches
        those assumptions.
        """
        patched, config = self.patched, self.config
        # this tests the following methods:
        #   User.set_password()
        #   User.check_password()
        #   make_password() -- 1.4 only
        #   check_password()
        #   identify_hasher()
        #   User.has_usable_password()
        #   User.set_unusable_password()
        # XXX: this take a while to run. what could be trimmed?

        #  TODO: get_hasher()

        #=======================================================
        # setup helpers & imports
        #=======================================================
        ctx = self.context
        setter = create_mock_setter()
        PASS1 = "toomanysecrets"
        WRONG1 = "letmein"

        has_identify_hasher = False
        from passlib.ext.django.utils import hasher_to_passlib_name, passlib_to_hasher_name
        from django.contrib.auth.hashers import check_password, make_password, is_password_usable
        if patched or DJANGO_VERSION >= (1,5):
            # identify_hasher()
            #   django 1.4 -- not present
            #   django 1.5 -- present (added in django ticket 18184)
            #   passlib integration -- present even under 1.4
            from django.contrib.auth.hashers import identify_hasher
            has_identify_hasher = True

        #=======================================================
        # make sure extension is configured correctly
        #=======================================================
        if patched:
            # contexts should match
            from passlib.ext.django.models import password_context
            self.assertEqual(password_context.to_dict(resolve=True),
                             ctx.to_dict(resolve=True))

            # should have patched both places
            from django.contrib.auth.models import check_password as check_password2
            self.assertIs(check_password2, check_password)

        #=======================================================
        # default algorithm
        #=======================================================
        # User.set_password() should use default alg
        user = FakeUser()
        user.set_password(PASS1)
        self.assertTrue(ctx.handler().verify(PASS1, user.password))
        self.assert_valid_password(user)

        # User.check_password() - n/a

        # make_password() should use default alg
        hash = make_password(PASS1)
        self.assertTrue(ctx.handler().verify(PASS1, hash))

        # check_password() - n/a

        #=======================================================
        # empty password behavior
        #=======================================================
        if DJANGO_VERSION < (1,6):
            # NOTE: django 1.4-1.5 treat empty password as invalid

            # User.set_password() should set unusable flag
            user = FakeUser()
            user.set_password('')
            self.assert_unusable_password(user)

            # User.check_password() should never return True
            user = FakeUser()
            user.password = hash = ctx.encrypt("")
            self.assertFalse(user.check_password(""))
            self.assert_valid_password(user, hash)

            # make_password() should reject empty passwords
            self.assertEqual(make_password(""), "!")

            # check_password() should never return True
            self.assertFalse(check_password("", hash))

        else:
            # User.set_password() should use default alg
            user = FakeUser()
            user.set_password('')
            hash = user.password
            self.assertTrue(ctx.handler().verify('', hash))
            self.assert_valid_password(user, hash)

            # User.check_password() should return True
            self.assertTrue(user.check_password(""))
            self.assert_valid_password(user, hash)

            # no make_password()

            # check_password() should return True
            self.assertTrue(check_password("", hash))

        #=======================================================
        # 'unusable flag' behavior
        #=======================================================

        # sanity check via user.set_unusable_password()
        user = FakeUser()
        user.set_unusable_password()
        self.assert_unusable_password(user)

        # ensure User.set_password() sets unusable flag
        user = FakeUser()
        user.set_password(None)
        self.assert_unusable_password(user)

        # User.check_password() should always fail
        self.assertFalse(user.check_password(None))
        self.assertFalse(user.check_password('None'))
        self.assertFalse(user.check_password(''))
        self.assertFalse(user.check_password(PASS1))
        self.assertFalse(user.check_password(WRONG1))
        self.assert_unusable_password(user)

        # make_password() should also set flag
        if DJANGO_VERSION >= (1,6):
            self.assertTrue(make_password(None).startswith("!"))
        else:
            self.assertEqual(make_password(None), "!")

        # check_password() should return False (didn't handle disabled under 1.3)
        self.assertFalse(check_password(PASS1, '!'))

        # identify_hasher() and is_password_usable() should reject it
        self.assertFalse(is_password_usable(user.password))
        if has_identify_hasher:
            self.assertRaises(ValueError, identify_hasher, user.password)

        #=======================================================
        # hash=None
        #=======================================================
        # User.set_password() - n/a

        # User.check_password() - returns False
        user = FakeUser()
        user.password = None
        self.assertFalse(user.check_password(PASS1))
        self.assertFalse(user.has_usable_password())

        # make_password() - n/a

        # check_password() - error
        self.assertFalse(check_password(PASS1, None))

        # identify_hasher() - error
        if has_identify_hasher:
            self.assertRaises(TypeError, identify_hasher, None)

        #=======================================================
        # empty & invalid hash values
        # NOTE: django 1.5 behavior change due to django ticket 18453
        # NOTE: passlib integration tries to match current django version
        #=======================================================
        for hash in ("", # empty hash
                     "$789$foo", # empty identifier
                     ):
            # User.set_password() - n/a

            # User.check_password()
            #   empty
            #   -----
            #   django 1.4 -- blank threw error (fixed in 1.5)
            #   django 1.5 -- blank hash returns False
            #
            #   invalid
            #   -------
            #   django 1.4 -- invalid hash threw error (fixed in 1.5)
            #   django 1.5 -- invalid hash returns False
            user = FakeUser()
            user.password = hash
            if DJANGO_VERSION >= (1,5):
                # returns False for hash
                self.assertFalse(user.check_password(PASS1))
            else:
                # throws error for hash
                self.assertRaises(ValueError, user.check_password, PASS1)

            # verify hash wasn't changed/upgraded during check_password() call
            self.assertEqual(user.password, hash)
            self.assertEqual(user.pop_saved_passwords(), [])

            # User.has_usable_password()
            #   django 1.4 -- invalid/empty usable (fixed in 1.5)
            #   django 1.5 -- invalid/empty no longer usable
            if DJANGO_VERSION < (1,5):
                self.assertTrue(user.has_usable_password())
            else:
                self.assertFalse(user.has_usable_password())

            # make_password() - n/a

            # check_password()
            #   django 1.4 -- invalid/empty hash threw error (fixed in 1.5)
            #   django 1.5 -- invalid/empty hash now returns False
            if DJANGO_VERSION < (1,5):
                self.assertRaises(ValueError, check_password, PASS1, hash)
            else:
                self.assertFalse(check_password(PASS1, hash))

            # identify_hasher() - throws error
            if has_identify_hasher:
                self.assertRaises(ValueError, identify_hasher, hash)

        #=======================================================
        # run through all the schemes in the context,
        # testing various bits of per-scheme behavior.
        #=======================================================
        for scheme in ctx.schemes():
            #-------------------------------------------------------
            # setup constants & imports, pick a sample secret/hash combo
            #-------------------------------------------------------
            handler = ctx.handler(scheme)
            deprecated = ctx._is_deprecated_scheme(scheme)
            assert not deprecated or scheme != ctx.default_scheme()
            try:
                testcase = get_handler_case(scheme)
            except exc.MissingBackendError:
                assert scheme == "bcrypt"
                continue
            assert testcase.handler is handler
            if testcase.is_disabled_handler:
                continue
            if not has_active_backend(handler):
                # TODO: move this above get_handler_case(),
                #       and omit MissingBackendError check.
                assert scheme in ["django_bcrypt", "django_bcrypt_sha256"], "%r scheme should always have active backend" % scheme
                continue
            try:
                secret, hash = sample_hashes[scheme]
            except KeyError:
                while True:
                    secret, hash = testcase('setUp').get_sample_hash()
                    if secret: # don't select blank passwords, especially under django 1.4/1.5
                        break
            other = 'dontletmein'

            # User.set_password() - n/a

            #-------------------------------------------------------
            # User.check_password()+migration against known hash
            #-------------------------------------------------------
            user = FakeUser()
            user.password = hash

            # check against invalid password
            self.assertFalse(user.check_password(None))
            ##self.assertFalse(user.check_password(''))
            self.assertFalse(user.check_password(other))
            self.assert_valid_password(user, hash)

            # check against valid password
            self.assertTrue(user.check_password(secret))

            # check if it upgraded the hash
            # NOTE: needs_update kept separate in case we need to test rounds.
            needs_update = deprecated
            if needs_update:
                self.assertNotEqual(user.password, hash)
                self.assertFalse(handler.identify(user.password))
                self.assertTrue(ctx.handler().verify(secret, user.password))
                self.assert_valid_password(user, saved=user.password)
            else:
                self.assert_valid_password(user, hash)

            # don't need to check rest for most deployments
            if TEST_MODE(max="default"):
                continue

            #-------------------------------------------------------
            # make_password() correctly selects algorithm
            #-------------------------------------------------------
            hash2 = make_password(secret, hasher=passlib_to_hasher_name(scheme))
            self.assertTrue(handler.verify(secret, hash2))

            #-------------------------------------------------------
            # check_password()+setter against known hash
            #-------------------------------------------------------
            # should call setter only if it needs_update
            self.assertTrue(check_password(secret, hash, setter=setter))
            self.assertEqual(setter.popstate(), [secret] if needs_update else [])

            # should not call setter
            self.assertFalse(check_password(other, hash, setter=setter))
            self.assertEqual(setter.popstate(), [])

            ### check preferred kwd is ignored (django 1.4 feature we don't support)
            ##self.assertTrue(check_password(secret, hash, setter=setter, preferred='fooey'))
            ##self.assertEqual(setter.popstate(), [secret])

            # TODO: get_hasher()

            #-------------------------------------------------------
            # identify_hasher() recognizes known hash
            #-------------------------------------------------------
            if has_identify_hasher:
                self.assertTrue(is_password_usable(hash))
                name = hasher_to_passlib_name(identify_hasher(hash).algorithm)
                self.assertEqual(name, scheme)
Ejemplo n.º 7
0
    def _do_test_available_scheme(self, scheme):
        """
        helper to test how specific hasher behaves.
        :param scheme: *passlib* name of hasher (e.g. "django_pbkdf2_sha256")
        """
        log = self.getLogger()
        ctx = self.context
        patched = self.patched
        setter = create_mock_setter()

        # NOTE: import has to be done w/in method, in case monkeypatching is applied by setUp()
        from django.contrib.auth.hashers import (
            check_password,
            make_password,
            is_password_usable,
            identify_hasher,
        )

        # -------------------------------------------------------
        # setup constants & imports, pick a sample secret/hash combo
        # -------------------------------------------------------
        handler = ctx.handler(scheme)
        log.debug("testing scheme: %r => %r", scheme, handler)
        deprecated = ctx.handler(scheme).deprecated
        assert not deprecated or scheme != ctx.default_scheme()
        try:
            testcase = get_handler_case(scheme)
        except exc.MissingBackendError:
            raise self.skipTest("backend not available")
        assert handler_derived_from(handler, testcase.handler)
        if handler.is_disabled:
            raise self.skipTest("skip disabled hasher")

        # verify that django has a backend available
        # (since our hasher may use different set of backends,
        #  get_handler_case() above may work, but django will have nothing)
        if not patched and not check_django_hasher_has_backend(
                handler.django_name):
            assert scheme in ["django_bcrypt", "django_bcrypt_sha256", "django_argon2"], \
                "%r scheme should always have active backend" % scheme
            log.warning("skipping scheme %r due to missing django dependency",
                        scheme)
            raise self.skipTest("skip due to missing dependency")

        # find a sample (secret, hash) pair to test with
        try:
            secret, hash = sample_hashes[scheme]
        except KeyError:
            get_sample_hash = testcase("setUp").get_sample_hash
            while True:
                secret, hash = get_sample_hash()
                if secret:  # don't select blank passwords
                    break
        other = 'dontletmein'

        # -------------------------------------------------------
        # User.set_password() - not tested here
        # -------------------------------------------------------

        # -------------------------------------------------------
        # User.check_password()+migration against known hash
        # -------------------------------------------------------
        user = FakeUser()
        user.password = hash

        # check against invalid password
        self.assertFalse(user.check_password(None))
        # self.assertFalse(user.check_password(''))
        self.assertFalse(user.check_password(other))
        self.assert_valid_password(user, hash)

        # check against valid password
        self.assertTrue(user.check_password(secret))

        # check if it upgraded the hash
        # NOTE: needs_update kept separate in case we need to test rounds.
        needs_update = deprecated
        if needs_update:
            self.assertNotEqual(user.password, hash)
            self.assertFalse(handler.identify(user.password))
            self.assertTrue(ctx.handler().verify(secret, user.password))
            self.assert_valid_password(user, saved=user.password)
        else:
            self.assert_valid_password(user, hash)

        # don't need to check rest for most deployments
        if TEST_MODE(max="default"):
            return

        # -------------------------------------------------------
        # make_password() correctly selects algorithm
        # -------------------------------------------------------
        alg = DjangoTranslator().passlib_to_django_name(scheme)
        hash2 = make_password(secret, hasher=alg)
        self.assertTrue(handler.verify(secret, hash2))

        # -------------------------------------------------------
        # check_password()+setter against known hash
        # -------------------------------------------------------
        # should call setter only if it needs_update
        self.assertTrue(check_password(secret, hash, setter=setter))
        self.assertEqual(setter.popstate(), [secret] if needs_update else [])

        # should not call setter
        self.assertFalse(check_password(other, hash, setter=setter))
        self.assertEqual(setter.popstate(), [])

        # check preferred kwd is ignored (feature we don't currently support fully)
        ##self.assertTrue(check_password(secret, hash, setter=setter, preferred='fooey'))
        ##self.assertEqual(setter.popstate(), [secret])

        # TODO: get_hasher()

        # -------------------------------------------------------
        # identify_hasher() recognizes known hash
        # -------------------------------------------------------
        self.assertTrue(is_password_usable(hash))
        name = DjangoTranslator().django_to_passlib_name(
            identify_hasher(hash).algorithm)
        self.assertEqual(name, scheme)
Ejemplo n.º 8
0
    def test_config(self):
        """test hashing interface

        this function is run against both the actual django code, to
        verify the assumptions of the unittests are correct;
        and run against the passlib extension, to verify it matches
        those assumptions.
        """
        patched, config = self.patched, self.config
        # this tests the following methods:
        #   User.set_password()
        #   User.check_password()
        #   make_password() -- 1.4 only
        #   check_password()
        #   identify_hasher()
        #   User.has_usable_password()
        #   User.set_unusable_password()
        # XXX: this take a while to run. what could be trimmed?

        #  TODO: get_hasher()

        #=======================================================
        # setup helpers & imports
        #=======================================================
        ctx = self.context
        setter = create_mock_setter()
        PASS1 = "toomanysecrets"
        WRONG1 = "letmein"

        if has_django14:
            from passlib.ext.django.utils import hasher_to_passlib_name, passlib_to_hasher_name
            from django.contrib.auth.hashers import check_password, make_password, is_password_usable
            if patched:
                from django.contrib.auth.hashers import identify_hasher
        else:
            from django.contrib.auth.models import check_password

        #=======================================================
        # make sure extension is configured correctly
        #=======================================================
        if patched:
            # contexts should match
            from passlib.ext.django.models import password_context
            self.assertEqual(password_context.to_dict(resolve=True),
                             ctx.to_dict(resolve=True))

            # should have patched both places
            if has_django14:
                from django.contrib.auth.models import check_password as check_password2
                self.assertIs(check_password2, check_password)

        #=======================================================
        # default algorithm
        #=======================================================
        # User.set_password() should use default alg
        user = FakeUser()
        user.set_password(PASS1)
        self.assertTrue(ctx.handler().verify(PASS1, user.password))
        self.assert_valid_password(user)

        # User.check_password() - n/a

        # make_password() should use default alg
        if has_django14:
            hash = make_password(PASS1)
            self.assertTrue(ctx.handler().verify(PASS1, hash))

        # check_password() - n/a

        #=======================================================
        # empty password behavior
        #=======================================================
        if has_django14:
            # NOTE: django 1.4 treats empty password as invalid

            # User.set_password() should set unusable flag
            user = FakeUser()
            user.set_password('')
            self.assert_unusable_password(user)

            # User.check_password() should never return True
            user = FakeUser()
            user.password = hash = ctx.encrypt("")
            self.assertFalse(user.check_password(""))
            self.assert_valid_password(user, hash)

            # make_password() should reject empty passwords
            self.assertEqual(make_password(""), "!")

            # check_password() should never return True
            self.assertFalse(check_password("", hash))

        else:
            # User.set_password() should use default alg
            user = FakeUser()
            user.set_password('')
            hash = user.password
            self.assertTrue(ctx.handler().verify('', hash))
            self.assert_valid_password(user, hash)

            # User.check_password() should return True
            self.assertTrue(user.check_password(""))
            self.assert_valid_password(user, hash)

            # no make_password()

            # check_password() should return True
            self.assertTrue(check_password("", hash))

        #=======================================================
        # 'unusable flag' behavior
        #=======================================================
        if has_django1 or patched:

            # sanity check via user.set_unusable_password()
            user = FakeUser()
            user.set_unusable_password()
            self.assert_unusable_password(user)

            # ensure User.set_password() sets flag
            user = FakeUser()
            user.set_password(None)
            self.assert_unusable_password(user)

            # User.check_password() should always fail
            self.assertFalse(user.check_password(None))
            self.assertFalse(user.check_password(''))
            self.assertFalse(user.check_password(PASS1))
            self.assertFalse(user.check_password(WRONG1))
            self.assert_unusable_password(user)

            # make_password() should also set flag
            if has_django14:
                self.assertEqual(make_password(None), "!")

            # check_password() should return False (didn't handle disabled under 1.3)
            if has_django14 or patched:
                self.assertFalse(check_password(PASS1, '!'))

            # identify_hasher() and is_password_usable() should reject it
            if has_django14:
                self.assertFalse(is_password_usable(user.password))
            if has_django14 and patched:
                self.assertRaises(ValueError, identify_hasher, user.password)

        #=======================================================
        # hash=None
        #=======================================================
        # User.set_password() - n/a

        # User.check_password() - returns False
        user = FakeUser()
        user.password = None
        if has_django14 or patched:
            self.assertFalse(user.check_password(PASS1))
        else:
            self.assertRaises(TypeError, user.check_password, PASS1)
        if has_django1 or patched:
            self.assertFalse(user.has_usable_password())

        # make_password() - n/a

        # check_password() - error
        if has_django14 or patched:
            self.assertFalse(check_password(PASS1, None))
        else:
            self.assertRaises(AttributeError, check_password, PASS1, None)

        # identify_hasher() - error
        if has_django14 and patched:
            self.assertRaises(TypeError, identify_hasher, None)

        #=======================================================
        # invalid hash values
        #=======================================================
        for hash in ("", "$789$foo"):
            # User.set_password() - n/a

            # User.check_password() - blank hash causes error
            user = FakeUser()
            user.password = hash
            if has_django14 or patched or hash:
                self.assertRaises(ValueError, user.check_password, PASS1)
            else:
                # django 1.3 returns False for empty hashes
                self.assertFalse(user.check_password(PASS1))
            self.assert_valid_password(
                user, hash)  # '' counts as valid for some reason

            # make_password() - n/a

            # check_password() - error
            self.assertRaises(ValueError, check_password, PASS1, hash)

            # identify_hasher() - error
            if has_django14 and patched:
                self.assertRaises(ValueError, identify_hasher, hash)

        #=======================================================
        # run through all the schemes in the context,
        # testing various bits of per-scheme behavior.
        #=======================================================
        for scheme in ctx.schemes():
            #-------------------------------------------------------
            # setup constants & imports, pick a sample secret/hash combo
            #-------------------------------------------------------
            handler = ctx.handler(scheme)
            deprecated = ctx._is_deprecated_scheme(scheme)
            assert not deprecated or scheme != ctx.default_scheme()
            try:
                testcase = get_handler_case(scheme)
            except exc.MissingBackendError:
                assert scheme == "bcrypt"
                continue
            assert testcase.handler is handler
            if testcase.is_disabled_handler:
                continue
            if not has_active_backend(handler):
                assert scheme == "django_bcrypt"
                continue
            while True:
                secret, hash = testcase('setUp').get_sample_hash()
                if secret:  # don't select blank passwords, special under django
                    break
            other = 'letmein'

            # User.set_password() - n/a

            #-------------------------------------------------------
            # User.check_password()+migration against known hash
            #-------------------------------------------------------
            user = FakeUser()
            user.password = hash

            # check against invalid password
            if has_django1 or patched:
                self.assertFalse(user.check_password(None))
            else:
                self.assertRaises(TypeError, user.check_password, None)
            ##self.assertFalse(user.check_password(''))
            self.assertFalse(user.check_password(other))
            self.assert_valid_password(user, hash)

            # check against valid password
            if has_django0 and isinstance(secret, unicode):
                secret = secret.encode("utf-8")
            self.assertTrue(user.check_password(secret))

            # check if it upgraded the hash
            needs_update = deprecated
            if needs_update:
                self.assertFalse(handler.identify(user.password))
                self.assertTrue(ctx.handler().verify(secret, user.password))
                self.assert_valid_password(user, saved=user.password)
            else:
                self.assert_valid_password(user, hash)

            # don't need to check rest for most deployments
            if TEST_MODE(max="default"):
                continue

            #-------------------------------------------------------
            # make_password() correctly selects algorithm
            #-------------------------------------------------------
            if has_django14:
                hash2 = make_password(secret,
                                      hasher=passlib_to_hasher_name(scheme))
                self.assertTrue(handler.verify(secret, hash2))

            #-------------------------------------------------------
            # check_password()+setter against known hash
            #-------------------------------------------------------
            if has_django14 or patched:
                # should call setter only if it needs_update
                self.assertTrue(check_password(secret, hash, setter=setter))
                self.assertEqual(setter.popstate(),
                                 [secret] if needs_update else [])

                # should not call setter
                self.assertFalse(check_password(other, hash, setter=setter))
                self.assertEqual(setter.popstate(), [])

                ### check preferred kwd is ignored (django 1.4 feature we don't support)
                ##self.assertTrue(check_password(secret, hash, setter=setter, preferred='fooey'))
                ##self.assertEqual(setter.popstate(), [secret])

            elif patched or scheme != "hex_md5":
                # django 1.3 never called check_password() for hex_md5
                self.assertTrue(check_password(secret, hash))
                self.assertFalse(check_password(other, hash))

            # TODO: get_hasher()

            #-------------------------------------------------------
            # identify_hasher() recognizes known hash
            #-------------------------------------------------------
            if has_django14 and patched:
                self.assertTrue(is_password_usable(hash))
                name = hasher_to_passlib_name(identify_hasher(hash).algorithm)
                self.assertEqual(name, scheme)
Ejemplo n.º 9
0
    def test_config(self):
        """test hashing interface

        this function is run against both the actual django code, to
        verify the assumptions of the unittests are correct;
        and run against the passlib extension, to verify it matches
        those assumptions.
        """
        patched, config = self.patched, self.config
        # this tests the following methods:
        #   User.set_password()
        #   User.check_password()
        #   make_password() -- 1.4 only
        #   check_password()
        #   identify_hasher()
        #   User.has_usable_password()
        #   User.set_unusable_password()
        # XXX: this take a while to run. what could be trimmed?

        #  TODO: get_hasher()

        #=======================================================
        # setup helpers & imports
        #=======================================================
        ctx = self.context
        setter = create_mock_setter()
        PASS1 = "toomanysecrets"
        WRONG1 = "letmein"

        if has_django14:
            from passlib.ext.django.utils import hasher_to_passlib_name, passlib_to_hasher_name
            from django.contrib.auth.hashers import check_password, make_password, is_password_usable
            if patched:
                from django.contrib.auth.hashers import identify_hasher
        else:
            from django.contrib.auth.models import check_password

        #=======================================================
        # make sure extension is configured correctly
        #=======================================================
        if patched:
            # contexts should match
            from passlib.ext.django.models import password_context
            self.assertEqual(password_context.to_dict(resolve=True),
                             ctx.to_dict(resolve=True))

            # should have patched both places
            if has_django14:
                from django.contrib.auth.models import check_password as check_password2
                self.assertIs(check_password2, check_password)

        #=======================================================
        # default algorithm
        #=======================================================
        # User.set_password() should use default alg
        user = FakeUser()
        user.set_password(PASS1)
        self.assertTrue(ctx.handler().verify(PASS1, user.password))
        self.assert_valid_password(user)

        # User.check_password() - n/a

        # make_password() should use default alg
        if has_django14:
            hash = make_password(PASS1)
            self.assertTrue(ctx.handler().verify(PASS1, hash))

        # check_password() - n/a

        #=======================================================
        # empty password behavior
        #=======================================================
        if has_django14:
            # NOTE: django 1.4 treats empty password as invalid

            # User.set_password() should set unusable flag
            user = FakeUser()
            user.set_password('')
            self.assert_unusable_password(user)

            # User.check_password() should never return True
            user = FakeUser()
            user.password = hash = ctx.encrypt("")
            self.assertFalse(user.check_password(""))
            self.assert_valid_password(user, hash)

            # make_password() should reject empty passwords
            self.assertEqual(make_password(""), "!")

            # check_password() should never return True
            self.assertFalse(check_password("", hash))

        else:
            # User.set_password() should use default alg
            user = FakeUser()
            user.set_password('')
            hash = user.password
            self.assertTrue(ctx.handler().verify('', hash))
            self.assert_valid_password(user, hash)

            # User.check_password() should return True
            self.assertTrue(user.check_password(""))
            self.assert_valid_password(user, hash)

            # no make_password()

            # check_password() should return True
            self.assertTrue(check_password("", hash))

        #=======================================================
        # 'unusable flag' behavior
        #=======================================================
        if has_django1 or patched:

            # sanity check via user.set_unusable_password()
            user = FakeUser()
            user.set_unusable_password()
            self.assert_unusable_password(user)

            # ensure User.set_password() sets flag
            user = FakeUser()
            user.set_password(None)
            self.assert_unusable_password(user)

            # User.check_password() should always fail
            self.assertFalse(user.check_password(None))
            self.assertFalse(user.check_password(''))
            self.assertFalse(user.check_password(PASS1))
            self.assertFalse(user.check_password(WRONG1))
            self.assert_unusable_password(user)

            # make_password() should also set flag
            if has_django14:
                self.assertEqual(make_password(None), "!")

            # check_password() should return False (didn't handle disabled under 1.3)
            if has_django14 or patched:
                self.assertFalse(check_password(PASS1, '!'))

            # identify_hasher() and is_password_usable() should reject it
            if has_django14:
                self.assertFalse(is_password_usable(user.password))
            if has_django14 and patched:
                self.assertRaises(ValueError, identify_hasher, user.password)

        #=======================================================
        # hash=None
        #=======================================================
        # User.set_password() - n/a

        # User.check_password() - returns False
        user = FakeUser()
        user.password = None
        if has_django14 or patched:
            self.assertFalse(user.check_password(PASS1))
        else:
            self.assertRaises(TypeError, user.check_password, PASS1)
        if has_django1 or patched:
            self.assertFalse(user.has_usable_password())

        # make_password() - n/a

        # check_password() - error
        if has_django14 or patched:
            self.assertFalse(check_password(PASS1, None))
        else:
            self.assertRaises(AttributeError, check_password, PASS1, None)

        # identify_hasher() - error
        if has_django14 and patched:
            self.assertRaises(TypeError, identify_hasher, None)

        #=======================================================
        # invalid hash values
        #=======================================================
        for hash in ("", "$789$foo"):
            # User.set_password() - n/a

            # User.check_password() - blank hash causes error
            user = FakeUser()
            user.password = hash
            if has_django14 or patched or hash:
                self.assertRaises(ValueError, user.check_password, PASS1)
            else:
                # django 1.3 returns False for empty hashes
                self.assertFalse(user.check_password(PASS1))
            self.assert_valid_password(user, hash) # '' counts as valid for some reason

            # make_password() - n/a

            # check_password() - error
            self.assertRaises(ValueError, check_password, PASS1, hash)

            # identify_hasher() - error
            if has_django14 and patched:
                self.assertRaises(ValueError, identify_hasher, hash)

        #=======================================================
        # run through all the schemes in the context,
        # testing various bits of per-scheme behavior.
        #=======================================================
        for scheme in ctx.schemes():
            #-------------------------------------------------------
            # setup constants & imports, pick a sample secret/hash combo
            #-------------------------------------------------------
            handler = ctx.handler(scheme)
            deprecated = ctx._is_deprecated_scheme(scheme)
            assert not deprecated or scheme != ctx.default_scheme()
            try:
                testcase = get_handler_case(scheme)
            except exc.MissingBackendError:
                assert scheme == "bcrypt"
                continue
            assert testcase.handler is handler
            if testcase.is_disabled_handler:
                continue
            if not has_active_backend(handler):
                assert scheme == "django_bcrypt"
                continue
            while True:
                secret, hash = testcase('setUp').get_sample_hash()
                if secret: # don't select blank passwords, special under django
                    break
            other = 'letmein'

            # User.set_password() - n/a

            #-------------------------------------------------------
            # User.check_password()+migration against known hash
            #-------------------------------------------------------
            user = FakeUser()
            user.password = hash

            # check against invalid password
            if has_django1 or patched:
                self.assertFalse(user.check_password(None))
            else:
                self.assertRaises(TypeError, user.check_password, None)
            ##self.assertFalse(user.check_password(''))
            self.assertFalse(user.check_password(other))
            self.assert_valid_password(user, hash)

            # check against valid password
            if has_django0 and isinstance(secret, unicode):
                secret = secret.encode("utf-8")
            self.assertTrue(user.check_password(secret))

            # check if it upgraded the hash
            needs_update = deprecated
            if needs_update:
                self.assertFalse(handler.identify(user.password))
                self.assertTrue(ctx.handler().verify(secret, user.password))
                self.assert_valid_password(user, saved=user.password)
            else:
                self.assert_valid_password(user, hash)

            # don't need to check rest for most deployments
            if TEST_MODE(max="default"):
                continue

            #-------------------------------------------------------
            # make_password() correctly selects algorithm
            #-------------------------------------------------------
            if has_django14:
                hash2 = make_password(secret, hasher=passlib_to_hasher_name(scheme))
                self.assertTrue(handler.verify(secret, hash2))

            #-------------------------------------------------------
            # check_password()+setter against known hash
            #-------------------------------------------------------
            if has_django14 or patched:
                # should call setter only if it needs_update
                self.assertTrue(check_password(secret, hash, setter=setter))
                self.assertEqual(setter.popstate(), [secret] if needs_update else [])

                # should not call setter
                self.assertFalse(check_password(other, hash, setter=setter))
                self.assertEqual(setter.popstate(), [])

                ### check preferred kwd is ignored (django 1.4 feature we don't support)
                ##self.assertTrue(check_password(secret, hash, setter=setter, preferred='fooey'))
                ##self.assertEqual(setter.popstate(), [secret])

            elif patched or scheme != "hex_md5":
                # django 1.3 never called check_password() for hex_md5
                self.assertTrue(check_password(secret, hash))
                self.assertFalse(check_password(other, hash))

            # TODO: get_hasher()

            #-------------------------------------------------------
            # identify_hasher() recognizes known hash
            #-------------------------------------------------------
            if has_django14 and patched:
                self.assertTrue(is_password_usable(hash))
                name = hasher_to_passlib_name(identify_hasher(hash).algorithm)
                self.assertEqual(name, scheme)