File: test_utils.py

package info (click to toggle)
django-cas-server 3.1.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,704 kB
  • sloc: python: 6,154; makefile: 275; xml: 100; javascript: 92; sh: 3
file content (266 lines) | stat: -rw-r--r-- 11,698 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# -*- coding: utf-8 -*-
# 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-2025 Valentin Samir
"""Tests module for utils"""
import django
from django.test import TestCase, RequestFactory
from django.db import connection

import warnings
import datetime

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"))

    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"
            )
        )

    def test_crypt(self):
        """test the crypt auth method"""
        # Only run test if crypt is available
        if utils.crypt is None:
            return
        salts = ["$6$UVVAQvrMyXMF3FF3", "aa"]
        hashed_password1 = []
        for salt in salts:
            hashed_password1.append(
                utils.crypt.crypt(
                    self.password1.decode("utf8"),
                    salt
                ).encode("utf8")
            )

        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"))

        with self.assertRaises(ValueError):
            utils.check_password("crypt", self.password1, b"$truc$s$dsdsd", "utf8")

    def test_ldap_password_valid(self):
        """test the ldap auth method with all the 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}"]
        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")
            )
        if utils.crypt is not None:
            hashed_password1.append(
                utils.LdapHashUserPassword.hash(
                    b"{CRYPT}",
                    self.password1,
                    b"$6$UVVAQvrMyXMF3FF3",
                    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"))

    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}"]

        # 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)
        if utils.crypt is not None:
            with self.assertRaises(utils.LdapHashUserPassword.BadSalt):
                utils.LdapHashUserPassword.hash(b'{CRYPT}', self.password1, b"$truc$toto")

        # 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:
            # bad length
            with self.assertRaises(utils.LdapHashUserPassword.BadHash):
                utils.check_password("ldap", self.password1, scheme + b"dG90b3E8ZHNkcw==", "utf8")
            # bad base64
            with self.assertRaises(utils.LdapHashUserPassword.BadHash):
                utils.check_password("ldap", self.password1, scheme + b"dG90b3E8ZHNkcw", "utf8")

    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 = []
        for hash_scheme in hashes:
            hashed_password1.append(
                (
                    "hex_%s" % hash_scheme,
                    getattr(utils.hashlib, hash_scheme)(self.password1).hexdigest()
                )
            )
        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"))

    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')
        if django.VERSION < (3, 2):
            self.assertEqual(
                utils.import_attr('cas_server.default_app_config'),
                'cas_server.apps.CasAppConfig'
            )
        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")

    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

    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)

    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.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, str)
            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())

    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)

    def test_regexpr_validator(self):
        """test the function regexpr_validator"""
        utils.regexpr_validator("^a$")
        with self.assertRaises(utils.ValidationError):
            utils.regexpr_validator("[")