test_utils.py 10.9 KB
Newer Older
1
# -*- coding: utf-8 -*-
Valentin Samir's avatar
Valentin Samir committed
2 3 4 5 6 7 8 9 10 11
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
# more details.
#
# You should have received a copy of the GNU General Public License version 3
# along with this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# (c) 2016 Valentin Samir
Valentin Samir's avatar
Valentin Samir committed
12
"""Tests module for utils"""
13
from django.test import TestCase, RequestFactory
14
from django.db import connection
15 16

import six
17
import warnings
18
import datetime
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

from cas_server import utils


class CheckPasswordCase(TestCase):
    """Tests for the utils function `utils.check_password`"""

    def setUp(self):
        """Generate random bytes string that will be used ass passwords"""
        self.password1 = utils.gen_saml_id()
        self.password2 = utils.gen_saml_id()
        if not isinstance(self.password1, bytes):  # pragma: no cover executed only in python3
            self.password1 = self.password1.encode("utf8")
            self.password2 = self.password2.encode("utf8")

    def test_setup(self):
        """check that generated password are bytes"""
        self.assertIsInstance(self.password1, bytes)
        self.assertIsInstance(self.password2, bytes)

    def test_plain(self):
        """test the plain auth method"""
        self.assertTrue(utils.check_password("plain", self.password1, self.password1, "utf8"))
        self.assertFalse(utils.check_password("plain", self.password1, self.password2, "utf8"))

Valentin Samir's avatar
Valentin Samir committed
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
    def test_plain_unicode(self):
        """test the plain auth method with unicode input"""
        self.assertTrue(
            utils.check_password(
                "plain",
                self.password1.decode("utf8"),
                self.password1.decode("utf8"),
                "utf8"
            )
        )
        self.assertFalse(
            utils.check_password(
                "plain",
                self.password1.decode("utf8"),
                self.password2.decode("utf8"),
                "utf8"
            )
        )

63 64
    def test_crypt(self):
        """test the crypt auth method"""
Valentin Samir's avatar
Valentin Samir committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
        salts = ["$6$UVVAQvrMyXMF3FF3", "aa"]
        hashed_password1 = []
        for salt in salts:
            if six.PY3:
                hashed_password1.append(
                    utils.crypt.crypt(
                        self.password1.decode("utf8"),
                        salt
                    ).encode("utf8")
                )
            else:
                hashed_password1.append(utils.crypt.crypt(self.password1, salt))

        for hp1 in hashed_password1:
            self.assertTrue(utils.check_password("crypt", self.password1, hp1, "utf8"))
            self.assertFalse(utils.check_password("crypt", self.password2, hp1, "utf8"))
81

Valentin Samir's avatar
Valentin Samir committed
82 83
        with self.assertRaises(ValueError):
            utils.check_password("crypt", self.password1, b"$truc$s$dsdsd", "utf8")
84

Valentin Samir's avatar
Valentin Samir committed
85 86
    def test_ldap_password_valid(self):
        """test the ldap auth method with all the schemes"""
87
        salt = b"UVVAQvrMyXMF3FF3"
Valentin Samir's avatar
Valentin Samir committed
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
        schemes_salt = [b"{SMD5}", b"{SSHA}", b"{SSHA256}", b"{SSHA384}", b"{SSHA512}"]
        schemes_nosalt = [b"{MD5}", b"{SHA}", b"{SHA256}", b"{SHA384}", b"{SHA512}"]
        hashed_password1 = []
        for scheme in schemes_salt:
            hashed_password1.append(
                utils.LdapHashUserPassword.hash(scheme, self.password1, salt, charset="utf8")
            )
        for scheme in schemes_nosalt:
            hashed_password1.append(
                utils.LdapHashUserPassword.hash(scheme, self.password1, charset="utf8")
            )
        hashed_password1.append(
            utils.LdapHashUserPassword.hash(
                b"{CRYPT}",
                self.password1,
103
                b"$6$UVVAQvrMyXMF3FF3",
Valentin Samir's avatar
Valentin Samir committed
104 105 106 107 108 109 110
                charset="utf8"
            )
        )
        for hp1 in hashed_password1:
            self.assertIsInstance(hp1, bytes)
            self.assertTrue(utils.check_password("ldap", self.password1, hp1, "utf8"))
            self.assertFalse(utils.check_password("ldap", self.password2, hp1, "utf8"))
111

Valentin Samir's avatar
Valentin Samir committed
112 113 114 115 116
    def test_ldap_password_fail(self):
        """test the ldap auth method with malformed hash or bad schemes"""
        salt = b"UVVAQvrMyXMF3FF3"
        schemes_salt = [b"{SMD5}", b"{SSHA}", b"{SSHA256}", b"{SSHA384}", b"{SSHA512}"]
        schemes_nosalt = [b"{MD5}", b"{SHA}", b"{SHA256}", b"{SHA384}", b"{SHA512}"]
117

Valentin Samir's avatar
Valentin Samir committed
118 119 120 121 122 123 124 125 126 127
        # first try to hash with bad parameters
        with self.assertRaises(utils.LdapHashUserPassword.BadScheme):
            utils.LdapHashUserPassword.hash(b"TOTO", self.password1)
        for scheme in schemes_nosalt:
            with self.assertRaises(utils.LdapHashUserPassword.BadScheme):
                utils.LdapHashUserPassword.hash(scheme, self.password1, salt)
        for scheme in schemes_salt:
            with self.assertRaises(utils.LdapHashUserPassword.BadScheme):
                utils.LdapHashUserPassword.hash(scheme, self.password1)
        with self.assertRaises(utils.LdapHashUserPassword.BadSalt):
128
            utils.LdapHashUserPassword.hash(b'{CRYPT}', self.password1, b"$truc$toto")
129

Valentin Samir's avatar
Valentin Samir committed
130 131 132 133 134 135
        # then try to check hash with bad hashes
        with self.assertRaises(utils.LdapHashUserPassword.BadHash):
            utils.check_password("ldap", self.password1, b"TOTOssdsdsd", "utf8")
        for scheme in schemes_salt:
            with self.assertRaises(utils.LdapHashUserPassword.BadHash):
                utils.check_password("ldap", self.password1, scheme + b"dG90b3E8ZHNkcw==", "utf8")
136

Valentin Samir's avatar
Valentin Samir committed
137 138 139 140
    def test_hex(self):
        """test all the hex_HASH method: the hashed password is a simple hash of the password"""
        hashes = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"]
        hashed_password1 = []
Valentin Samir's avatar
Valentin Samir committed
141
        for hash_scheme in hashes:
Valentin Samir's avatar
Valentin Samir committed
142
            hashed_password1.append(
Valentin Samir's avatar
Valentin Samir committed
143 144 145 146
                (
                    "hex_%s" % hash_scheme,
                    getattr(utils.hashlib, hash_scheme)(self.password1).hexdigest()
                )
Valentin Samir's avatar
Valentin Samir committed
147 148 149 150
            )
        for (method, hp1) in hashed_password1:
            self.assertTrue(utils.check_password(method, self.password1, hp1, "utf8"))
            self.assertFalse(utils.check_password(method, self.password2, hp1, "utf8"))
151

Valentin Samir's avatar
Valentin Samir committed
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
    def test_bad_method(self):
        """try to check password with a bad method, should raise a ValueError"""
        with self.assertRaises(ValueError):
            utils.check_password("test", self.password1, b"$truc$s$dsdsd", "utf8")


class UtilsTestCase(TestCase):
    """tests for some little utils functions"""
    def test_import_attr(self):
        """
            test the import_attr function. Feeded with a dotted path string, it should
            import the dotted module and return that last componend of the dotted path
            (function, class or variable)
        """
        with self.assertRaises(ImportError):
            utils.import_attr('toto.titi.tutu')
        with self.assertRaises(AttributeError):
            utils.import_attr('cas_server.utils.toto')
        with self.assertRaises(ValueError):
            utils.import_attr('toto')
        self.assertEqual(
            utils.import_attr('cas_server.default_app_config'),
            'cas_server.apps.CasAppConfig'
175
        )
Valentin Samir's avatar
Valentin Samir committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189
        self.assertEqual(utils.import_attr(utils), utils)

    def test_update_url(self):
        """
            test the update_url function. Given an url with possible GET parameter and a dict
            the function build a url with GET parameters updated by the dictionnary
        """
        url1 = utils.update_url(u"https://www.example.com?toto=1", {u"tata": u"2"})
        url2 = utils.update_url(b"https://www.example.com?toto=1", {b"tata": b"2"})
        self.assertEqual(url1, u"https://www.example.com?tata=2&toto=1")
        self.assertEqual(url2, u"https://www.example.com?tata=2&toto=1")

        url3 = utils.update_url(u"https://www.example.com?toto=1", {u"toto": u"2"})
        self.assertEqual(url3, u"https://www.example.com?toto=2")
190 191 192 193 194 195 196 197

    def test_crypt_salt_is_valid(self):
        """test the function crypt_salt_is_valid who test if a crypt salt is valid"""
        self.assertFalse(utils.crypt_salt_is_valid(""))  # len 0
        self.assertFalse(utils.crypt_salt_is_valid("a"))  # len 1
        self.assertFalse(utils.crypt_salt_is_valid("$$"))  # start with $ followed by $
        self.assertFalse(utils.crypt_salt_is_valid("$toto"))  # start with $ but no secondary $
        self.assertFalse(utils.crypt_salt_is_valid("$toto$toto"))  # algorithm toto not known
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216

    def test_get_current_url(self):
        """test the function get_current_url"""
        factory = RequestFactory()
        request = factory.get('/truc/muche?test=1')
        self.assertEqual(utils.get_current_url(request), 'http://testserver/truc/muche?test=1')
        self.assertEqual(
            utils.get_current_url(request, ignore_params={'test'}),
            'http://testserver/truc/muche'
        )

    def test_get_tuple(self):
        """test the function get_tuple"""
        test_tuple = (1, 2, 3)
        for index, value in enumerate(test_tuple):
            self.assertEqual(utils.get_tuple(test_tuple, index), value)
        self.assertEqual(utils.get_tuple(test_tuple, 3), None)
        self.assertEqual(utils.get_tuple(test_tuple, 3, 'toto'), 'toto')
        self.assertEqual(utils.get_tuple(None, 3), None)
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241

    def test_last_version(self):
        """
            test the function last_version. An internet connection is needed, if you do not have
            one, this test will fail and you should ignore it.
        """
        try:
            # first check if pypi is available
            utils.requests.get("https://pypi.python.org/simple/django-cas-server/")
        except utils.requests.exceptions.RequestException:
            warnings.warn(
                (
                    "Pypi seems not available, perhaps you do not have internet access. "
                    "Consequently, the test cas_server.tests.test_utils.UtilsTestCase.test_last_"
                    "version is ignored"
                ),
                RuntimeWarning
            )
        else:
            version = utils.last_version()
            self.assertIsInstance(version, six.text_type)
            self.assertEqual(len(version.split('.')), 3)

            # version is cached 24h so calling it a second time should return the save value
            self.assertEqual(version, utils.last_version())
242 243 244 245 246 247 248 249 250 251 252 253

    def test_dictfetchall(self):
        """test the function dictfetchall"""
        with connection.cursor() as curs:
            curs.execute("SELECT * FROM django_migrations")
            results = utils.dictfetchall(curs)
            self.assertIsInstance(results, list)
            self.assertTrue(len(results) > 0)
            for result in results:
                self.assertIsInstance(result, dict)
                self.assertIn('applied', result)
                self.assertIsInstance(result['applied'], datetime.datetime)