1# -*- coding: utf-8 -*-
2"""
3Automatic tests for module ldap0.ldapurl
4"""
5
6import unittest
7from urllib.request import quote as url_quote
8
9import ldap0
10import ldap0.ldapurl
11from ldap0.ldapurl import LDAPUrl
12
13
14class TestIsLDAPUrl(unittest.TestCase):
15
16    is_ldapurl_tests = {
17        # Examples from RFC2255
18        'ldap:///o=University%20of%20Michigan,c=US':1,
19        'ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US':1,
20        'ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,':1,
21        'ldap://host.com:6666/o=University%20of%20Michigan,':1,
22        'ldap://ldap.itd.umich.edu/c=GB?objectClass?one':1,
23        'ldap://ldap.question.com/o=Question%3f,c=US?mail':1,
24        'ldap://ldap.netscape.com/o=Babsco,c=US??(int=%5c00%5c00%5c00%5c04)':1,
25        'ldap:///??sub??bindname=cn=Manager%2co=Foo':1,
26        'ldap:///??sub??!bindname=cn=Manager%2co=Foo':1,
27        # More examples from various sources
28        'ldap://ldap.nameflow.net:1389/c%3dDE':1,
29        'ldap://root.openldap.org/dc=openldap,dc=org':1,
30        'ldap://root.openldap.org/dc=openldap,dc=org':1,
31        'ldap://x500.mh.se/o=Mitthogskolan,c=se????1.2.752.58.10.2=T.61':1,
32        'ldp://root.openldap.org/dc=openldap,dc=org':0,
33        'ldap://localhost:1389/ou%3DUnstructured%20testing%20tree%2Cdc%3Dexample%2Cdc%3Dcom??one':1,
34        'ldaps://ldap.example.com/c%3dDE':1,
35        'ldapi:///dc=example,dc=com????x-saslmech=EXTERNAL':1,
36    }
37
38    def test_is_ldapurl(self):
39        for ldap_url, expected in self.is_ldapurl_tests.items():
40            result = ldap0.ldapurl.is_ldapurl(ldap_url)
41            self.assertEqual(
42                result, expected,
43                'is_ldapurl("%s") returns %d instead of %d.' % (
44                    ldap_url, result, expected,
45                )
46            )
47
48
49class TestParseLDAPUrl(unittest.TestCase):
50
51    parse_ldap_url_tests = [
52        (
53            'ldap://root.openldap.org/dc=openldap,dc=org',
54            LDAPUrl(
55                hostport='root.openldap.org',
56                dn='dc=openldap,dc=org'
57            )
58        ),
59        (
60            'ldap://root.openldap.org/dc%3dboolean%2cdc%3dnet???%28objectClass%3d%2a%29',
61            LDAPUrl(
62                hostport='root.openldap.org',
63                dn='dc=boolean,dc=net',
64                filterstr='(objectClass=*)'
65            )
66        ),
67        (
68            'ldap://root.openldap.org/dc=openldap,dc=org??sub?',
69            LDAPUrl(
70                hostport='root.openldap.org',
71                dn='dc=openldap,dc=org',
72                scope=ldap0.SCOPE_SUBTREE
73            )
74        ),
75        (
76            'ldap://root.openldap.org/dc=openldap,dc=org??one?',
77            LDAPUrl(
78                hostport='root.openldap.org',
79                dn='dc=openldap,dc=org',
80                scope=ldap0.SCOPE_ONELEVEL
81            )
82        ),
83        (
84            'ldap://root.openldap.org/dc=openldap,dc=org??base?',
85            LDAPUrl(
86                hostport='root.openldap.org',
87                dn='dc=openldap,dc=org',
88                scope=ldap0.SCOPE_BASE
89            )
90        ),
91        (
92            'ldap://x500.mh.se/o=Mitthogskolan,c=se????1.2.752.58.10.2=T.61',
93            LDAPUrl(
94                hostport='x500.mh.se',
95                dn='o=Mitthogskolan,c=se',
96                extensions=ldap0.ldapurl.LDAPUrlExtensions({
97                    '1.2.752.58.10.2':ldap0.ldapurl.LDAPUrlExtension(
98                    critical=0,extype='1.2.752.58.10.2',exvalue='T.61'
99                    )
100                })
101            )
102        ),
103        (
104            'ldap://localhost:12345/dc=example,dc=com????!bindname=cn=Michael%2Cdc=example%2Cdc=com,!X-BINDPW=secretpassword',
105            LDAPUrl(
106                hostport='localhost:12345',
107                dn='dc=example,dc=com',
108                extensions=ldap0.ldapurl.LDAPUrlExtensions({
109                    'bindname':ldap0.ldapurl.LDAPUrlExtension(
110                    critical=1,extype='bindname',exvalue='cn=Michael,dc=example,dc=com'
111                    ),
112                    'X-BINDPW':ldap0.ldapurl.LDAPUrlExtension(
113                    critical=1,extype='X-BINDPW',exvalue='secretpassword'
114                    ),
115                }),
116            )
117        ),
118        (
119            'ldap://localhost:54321/dc=example,dc=com????bindname=cn=Michael%2Cdc=example%2Cdc=com,X-BINDPW=secretpassword',
120            LDAPUrl(
121                hostport='localhost:54321',
122                dn='dc=example,dc=com',
123                who='cn=Michael,dc=example,dc=com',
124                cred='secretpassword'
125            )
126        ),
127        (
128            'ldap://localhost:54321/cn=Michael Ströder+mail=michael@example.com,o=Test????bindname=cn=Michael%20Ströder%2bmail=michael@example.com%2Co=Test,X-BINDPW=secretpassword',
129            LDAPUrl(
130                hostport='localhost:54321',
131                dn='cn=Michael Ströder+mail=michael@example.com,o=Test',
132                who='cn=Michael Ströder+mail=michael@example.com,o=Test',
133                cred='secretpassword'
134            )
135        ),
136        (
137            'ldaps://localhost:12345/dc=example,dc=com',
138            LDAPUrl(
139                urlscheme='ldaps',
140                hostport='localhost:12345',
141                dn='dc=example,dc=com',
142            ),
143        ),
144        (
145            'ldapi://%2ftmp%2fopenldap2-1389/dc=example,dc=com',
146            LDAPUrl(
147                urlscheme='ldapi',
148                hostport='/tmp/openldap2-1389',
149                dn='dc=example,dc=com',
150            ),
151        ),
152    ]
153
154    def test_ldapurl(self):
155        for ldap_url_str,test_ldap_url_obj in self.parse_ldap_url_tests:
156            ldap_url_obj = LDAPUrl(ldapUrl=ldap_url_str)
157            self.assertEqual(
158                ldap_url_obj, test_ldap_url_obj,
159                'Attributes of LDAPUrl(%r) are:\n%r\ninstead of:\n%r' % (
160                    ldap_url_str,
161                    ldap_url_obj,
162                    test_ldap_url_obj,
163                )
164            )
165            unparsed_ldap_url_str = test_ldap_url_obj.unparse()
166            unparsed_ldap_url_obj = LDAPUrl(ldapUrl=unparsed_ldap_url_str)
167            self.assertEqual(
168                unparsed_ldap_url_obj, test_ldap_url_obj,
169                'Attributes of LDAPUrl(%r) are:\n%r\ninstead of:\n%r' % (
170                    unparsed_ldap_url_str,
171                    unparsed_ldap_url_obj,
172                    test_ldap_url_obj,
173                )
174            )
175
176
177class TestLDAPUrl(unittest.TestCase):
178
179    def test_combo(self):
180        u = LDAPUrl(
181            'ldap://127.0.0.1:1234/'
182            'dc=example,dc=com'
183            '?attr1,attr2,attr3'
184            '?sub'
185            '?%28objectClass%3D%2A%29'
186            '?'
187            'bindname=cn%3Dfred%2Cc%3Dau,'
188            'X-BINDPW=%3F%3F%3F,'
189            'trace=8,'
190            'x-saslmech=DIGEST-MD5,'
191            'x-saslrealm=example.com,'
192            'x-saslauthzid=u:anna,'
193            'x-starttls=2,'
194        )
195        self.assertEqual(u.urlscheme, "ldap")
196        self.assertEqual(u.hostport, "127.0.0.1:1234")
197        self.assertEqual(u.dn, "dc=example,dc=com")
198        self.assertEqual(u.attrs, ["attr1","attr2","attr3"])
199        self.assertEqual(u.scope, ldap0.SCOPE_SUBTREE)
200        self.assertEqual(u.filterstr, "(objectClass=*)")
201        self.assertEqual(len(u.extensions), 7)
202        self.assertEqual(u.who, "cn=fred,c=au")
203        self.assertEqual(u.cred, "???")
204        self.assertEqual(u.trace_level, "8")
205        self.assertEqual(u.sasl_mech, 'DIGEST-MD5')
206        self.assertEqual(u.sasl_realm, 'example.com')
207        self.assertEqual(u.sasl_authzid, 'u:anna')
208        self.assertEqual(u.start_tls, '2')
209        with self.assertRaises(AttributeError):
210            u.foo
211
212    def test_parse_default_hostport(self):
213        u = LDAPUrl("ldap://")
214        self.assertEqual(u.urlscheme, "ldap")
215        self.assertEqual(u.hostport, "")
216
217    def test_parse_empty_dn(self):
218        u = LDAPUrl("ldap://")
219        self.assertEqual(u.dn, "")
220        u = LDAPUrl("ldap:///")
221        self.assertEqual(u.dn, "")
222        u = LDAPUrl("ldap:///?")
223        self.assertEqual(u.dn, "")
224
225    def test_parse_default_attrs(self):
226        u = LDAPUrl("ldap://")
227        self.assertIsNone(u.attrs)
228
229    def test_parse_default_scope(self):
230        u = LDAPUrl("ldap://")
231        self.assertIsNone(u.scope)     # RFC4516 s3
232
233    def test_parse_default_filter(self):
234        u = LDAPUrl("ldap://")
235        self.assertIsNone(u.filterstr) # RFC4516 s3
236
237    def test_parse_default_extensions(self):
238        u = LDAPUrl("ldap://")
239        self.assertEqual(len(u.extensions), 0)
240
241    def test_parse_schemes(self):
242        u = LDAPUrl("ldap://")
243        self.assertEqual(u.urlscheme, "ldap")
244        u = LDAPUrl("ldapi://")
245        self.assertEqual(u.urlscheme, "ldapi")
246        u = LDAPUrl("ldaps://")
247        self.assertEqual(u.urlscheme, "ldaps")
248
249    def test_parse_hostport(self):
250        u = LDAPUrl("ldap://a")
251        self.assertEqual(u.hostport, "a")
252        u = LDAPUrl("ldap://a.b")
253        self.assertEqual(u.hostport, "a.b")
254        u = LDAPUrl("ldap://a.")
255        self.assertEqual(u.hostport, "a.")
256        u = LDAPUrl("ldap://%61%62:%32/")
257        self.assertEqual(u.hostport, "ab:2")
258        u = LDAPUrl("ldap://[::1]/")
259        self.assertEqual(u.hostport, "[::1]")
260        u = LDAPUrl("ldap://[::1]")
261        self.assertEqual(u.hostport, "[::1]")
262        u = LDAPUrl("ldap://[::1]:123/")
263        self.assertEqual(u.hostport, "[::1]:123")
264        u = LDAPUrl("ldap://[::1]:123")
265        self.assertEqual(u.hostport, "[::1]:123")
266
267    def test_parse_dn(self):
268        u = LDAPUrl("ldap:///")
269        self.assertEqual(u.dn, "")
270        u = LDAPUrl("ldap:///dn=foo")
271        self.assertEqual(u.dn, "dn=foo")
272        u = LDAPUrl("ldap:///dn=foo%2cdc=bar")
273        self.assertEqual(u.dn, "dn=foo,dc=bar")
274        u = LDAPUrl("ldap:///dn=foo%20bar")
275        self.assertEqual(u.dn, "dn=foo bar")
276        u = LDAPUrl("ldap:///dn=foo%2fbar")
277        self.assertEqual(u.dn, "dn=foo/bar")
278        u = LDAPUrl("ldap:///dn=foo%2fbar?")
279        self.assertEqual(u.dn, "dn=foo/bar")
280        u = LDAPUrl("ldap:///dn=foo%3f?")
281        self.assertEqual(u.dn, "dn=foo?")
282        u = LDAPUrl("ldap:///dn=foo%3f")
283        self.assertEqual(u.dn, "dn=foo?")
284        u = LDAPUrl("ldap:///dn=str%c3%b6der.com")
285        self.assertEqual(u.dn, "dn=ströder.com")
286
287    def test_parse_attrs(self):
288        u = LDAPUrl("ldap:///?")
289        self.assertEqual(u.attrs, None)
290        u = LDAPUrl("ldap:///??")
291        self.assertEqual(u.attrs, None)
292        u = LDAPUrl("ldap:///?*?")
293        self.assertEqual(u.attrs, ['*'])
294        u = LDAPUrl("ldap:///?*,*?")
295        self.assertEqual(u.attrs, ['*','*'])
296        u = LDAPUrl("ldap:///?a")
297        self.assertEqual(u.attrs, ['a'])
298        u = LDAPUrl("ldap:///?%61")
299        self.assertEqual(u.attrs, ['a'])
300        u = LDAPUrl("ldap:///?a,b")
301        self.assertEqual(u.attrs, ['a','b'])
302        u = LDAPUrl("ldap:///?a%3fb")
303        self.assertEqual(u.attrs, ['a?b'])
304
305    def test_parse_scope_default(self):
306        u = LDAPUrl("ldap:///??")
307        self.assertIsNone(u.scope) # on opposite to RFC4516 s3 for referral chasing
308        u = LDAPUrl("ldap:///???")
309        self.assertIsNone(u.scope) # on opposite to RFC4516 s3 for referral chasing
310
311    def test_parse_scope(self):
312        u = LDAPUrl("ldap:///??sub")
313        self.assertEqual(u.scope, ldap0.SCOPE_SUBTREE)
314        u = LDAPUrl("ldap:///??sub?")
315        self.assertEqual(u.scope, ldap0.SCOPE_SUBTREE)
316        u = LDAPUrl("ldap:///??base")
317        self.assertEqual(u.scope, ldap0.SCOPE_BASE)
318        u = LDAPUrl("ldap:///??base?")
319        self.assertEqual(u.scope, ldap0.SCOPE_BASE)
320        u = LDAPUrl("ldap:///??one")
321        self.assertEqual(u.scope, ldap0.SCOPE_ONELEVEL)
322        u = LDAPUrl("ldap:///??one?")
323        self.assertEqual(u.scope, ldap0.SCOPE_ONELEVEL)
324        u = LDAPUrl("ldap:///??subordinates")
325        self.assertEqual(u.scope, ldap0.SCOPE_SUBORDINATE)
326        u = LDAPUrl("ldap:///??subordinates?")
327        self.assertEqual(u.scope, ldap0.SCOPE_SUBORDINATE)
328
329    def test_parse_filter(self):
330        u = LDAPUrl("ldap:///???(cn=Bob)")
331        self.assertEqual(u.filterstr, "(cn=Bob)")
332        u = LDAPUrl("ldap:///???(cn=Bob)?")
333        self.assertEqual(u.filterstr, "(cn=Bob)")
334        u = LDAPUrl("ldap:///???(cn=Bob%20Smith)?")
335        self.assertEqual(u.filterstr, "(cn=Bob Smith)")
336        u = LDAPUrl("ldap:///???(cn=Bob/Smith)?")
337        self.assertEqual(u.filterstr, "(cn=Bob/Smith)")
338        u = LDAPUrl("ldap:///???(cn=Bob:Smith)?")
339        self.assertEqual(u.filterstr, "(cn=Bob:Smith)")
340        u = LDAPUrl("ldap:///???&(cn=Bob)(objectClass=user)?")
341        self.assertEqual(u.filterstr, "&(cn=Bob)(objectClass=user)")
342        u = LDAPUrl("ldap:///???|(cn=Bob)(objectClass=user)?")
343        self.assertEqual(u.filterstr, "|(cn=Bob)(objectClass=user)")
344        u = LDAPUrl("ldap:///???(cn=Q%3f)?")
345        self.assertEqual(u.filterstr, "(cn=Q?)")
346        u = LDAPUrl("ldap:///???(cn=Q%3f)")
347        self.assertEqual(u.filterstr, "(cn=Q?)")
348        u = LDAPUrl("ldap:///???(sn=Str%c3%b6der)") # (possibly bad?)
349        self.assertEqual(u.filterstr, "(sn=Ströder)")
350        u = LDAPUrl("ldap:///???(sn=Str\\c3\\b6der)")
351        self.assertEqual(u.filterstr, "(sn=Str\\c3\\b6der)") # (recommended)
352        u = LDAPUrl("ldap:///???(cn=*\\2a*)")
353        self.assertEqual(u.filterstr, "(cn=*\\2a*)")
354        u = LDAPUrl("ldap:///???(cn=*%5c2a*)")
355        self.assertEqual(u.filterstr, "(cn=*\\2a*)")
356
357    def test_parse_extensions(self):
358        u = LDAPUrl("ldap:///????")
359        self.assertIsNone(u.extensions)
360        self.assertIsNone(u.who)
361        u = LDAPUrl("ldap:///????bindname=cn=root")
362        self.assertEqual(len(u.extensions), 1)
363        self.assertEqual(u.who, "cn=root")
364        u = LDAPUrl("ldap:///????!bindname=cn=root")
365        self.assertEqual(len(u.extensions), 1)
366        self.assertEqual(u.who, "cn=root")
367        u = LDAPUrl("ldap:///????bindname=%3f,X-BINDPW=%2c")
368        self.assertEqual(len(u.extensions), 2)
369        self.assertEqual(u.who, "?")
370        self.assertEqual(u.cred, ",")
371
372    def test_parse_extensions_nulls(self):
373        u = LDAPUrl("ldap:///????bindname=%00name")
374        self.assertEqual(u.who, "\0name")
375
376    def test_parse_extensions_5questions(self):
377        u = LDAPUrl("ldap:///????bindname=?")
378        self.assertEqual(len(u.extensions), 1)
379        self.assertEqual(u.who, "?")
380
381    def test_parse_extensions_novalue(self):
382        u = LDAPUrl("ldap:///????bindname")
383        self.assertEqual(len(u.extensions), 1)
384        self.assertIsNone(u.who)
385
386    def test_bad_urls1(self):
387        failed_urls = []
388        for bad in (
389                '',
390                'ldap:',
391                'ldap:/',
392                ':///',
393                '://',
394                '///',
395                '//',
396                '/',
397                'LDAP://',
398                'invalid://',
399                'ldap:///??invalid_scope',
400                r'ldap:///??%00',       # RFC4516 2.1
401        ):
402            try:
403                LDAPUrl(bad)
404            except ValueError:
405                pass
406            else:
407                failed_urls.append(bad)
408        if failed_urls:
409            self.fail("These LDAP URLs should have raised ValueError: %r" % failed_urls)
410
411    @unittest.expectedFailure
412    def test_bad_urls2(self):
413        failed_urls = []
414        for bad in (
415                #XXX-- the following should raise exceptions!
416                'ldap:///?????',       # extension can't start with '?'
417                'ldap://:389/',         # [host [COLON port]]
418                'ldap://a:/',           # [host [COLON port]]
419                r'ldap://%%%/',          # invalid URL encoding
420                'ldap:///?,',           # attrdesc *(COMMA attrdesc)
421                'ldap:///?a,',          # attrdesc *(COMMA attrdesc)
422                'ldap:///?,a',          # attrdesc *(COMMA attrdesc)
423                'ldap:///?a,,b',        # attrdesc *(COMMA attrdesc)
424                r'ldap://%00/',         # RFC4516 2.1
425                r'ldap:///%00',         # RFC4516 2.1
426                r'ldap:///?%00',        # RFC4516 2.1
427                'ldap:///????0=0',      # extype must start with Alpha
428                'ldap:///????a_b=0',    # extype contains only [-a-zA-Z0-9]
429                'ldap:///????!!a=0',    # only one exclamation allowed
430            ):
431            try:
432                LDAPUrl(bad)
433            except ValueError:
434                pass
435            else:
436                failed_urls.append(bad)
437        if failed_urls:
438            self.fail("These LDAP URLs should have raised ValueError: %r" % failed_urls)
439
440    def test_connect_uri(self):
441        for url, uri in (
442            ('ldap:///o=University%20of%20Michigan,c=US', 'ldap://'),
443            ('ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US', 'ldap://ldap.itd.umich.edu'),
444            ('ldap://host.com:6666/o=University%20of%20Michigan,', 'ldap://host.com:6666'),
445            ('ldap://ldap.nameflow.net:1389/c%3dDE', 'ldap://ldap.nameflow.net:1389'),
446            ('ldap://root.openldap.org/dc=openldap,dc=org', 'ldap://root.openldap.org'),
447            ('ldap://x500.mh.se/o=Mitthogskolan,c=se????1.2.752.58.10.2=T.61', 'ldap://x500.mh.se'),
448            ('ldap://localhost:1389/ou%3DUnstructured%20testing%20tree??one', 'ldap://localhost:1389'),
449            ('ldapi:///dc=example,dc=com????x-saslmech=EXTERNAL', 'ldapi://'),
450            ('ldapi://%2Fopt%2Fae-dir%2Frun%2Fslapd%2Fldapi/ou=ae-dir????bindname=aead', 'ldapi://%2Fopt%2Fae-dir%2Frun%2Fslapd%2Fldapi'),
451        ):
452            self.assertEqual(LDAPUrl(url).connect_uri(), uri)
453
454
455if __name__ == '__main__':
456    unittest.main()
457