2 # -*- coding: utf-8 -*-
3 # Originally based on ./sam.py
4 from __future__ import print_function
10 sys.path.insert(0, "bin/python")
12 from samba.tests.subunitrun import SubunitOptions, TestProgram
14 import samba.getopt as options
16 from samba.auth import system_session
18 from samba.samdb import SamDB
19 from samba.dcerpc import misc
21 parser = optparse.OptionParser("linked_attributes.py [options] <host>")
22 sambaopts = options.SambaOptions(parser)
23 parser.add_option_group(sambaopts)
24 parser.add_option_group(options.VersionOptions(parser))
25 # use command line creds if available
26 credopts = options.CredentialsOptions(parser)
27 parser.add_option_group(credopts)
28 subunitopts = SubunitOptions(parser)
29 parser.add_option_group(subunitopts)
31 parser.add_option('--delete-in-setup', action='store_true',
32 help="cleanup in setup")
34 parser.add_option('--no-cleanup', action='store_true',
35 help="don't cleanup in teardown")
37 parser.add_option('--no-reveal-internals', action='store_true',
38 help="Only use windows compatible ldap controls")
40 opts, args = parser.parse_args()
48 lp = sambaopts.get_loadparm()
49 creds = credopts.get_credentials(lp)
52 class LATestException(Exception):
56 class LATests(samba.tests.TestCase):
59 super(LATests, self).setUp()
60 self.samdb = SamDB(host, credentials=creds,
61 session_info=system_session(lp), lp=lp)
63 self.base_dn = self.samdb.domain_dn()
64 self.ou = "OU=la,%s" % self.base_dn
65 if opts.delete_in_setup:
67 self.samdb.delete(self.ou, ['tree_delete:1'])
68 except ldb.LdbError as e:
69 print("tried deleting %s, got error %s" % (self.ou, e))
70 self.samdb.add({'objectclass': 'organizationalUnit',
74 super(LATests, self).tearDown()
75 if not opts.no_cleanup:
76 self.samdb.delete(self.ou, ['tree_delete:1'])
78 def add_object(self, cn, objectclass, more_attrs={}):
79 dn = "CN=%s,%s" % (cn, self.ou)
81 'objectclass': objectclass,
83 attrs.update(more_attrs)
88 def add_objects(self, n, objectclass, prefix=None, more_attrs={}):
93 dns.append(self.add_object("%s%d" % (prefix, i + 1),
95 more_attrs=more_attrs))
98 def add_linked_attribute(self, src, dest, attr='member',
101 m.dn = ldb.Dn(self.samdb, src)
102 m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_ADD, attr)
103 self.samdb.modify(m, controls=controls)
105 def remove_linked_attribute(self, src, dest, attr='member',
108 m.dn = ldb.Dn(self.samdb, src)
109 m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_DELETE, attr)
110 self.samdb.modify(m, controls=controls)
112 def replace_linked_attribute(self, src, dest, attr='member',
115 m.dn = ldb.Dn(self.samdb, src)
116 m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_REPLACE, attr)
117 self.samdb.modify(m, controls=controls)
119 def attr_search(self, obj, attr, scope=ldb.SCOPE_BASE, **controls):
120 if opts.no_reveal_internals:
121 if 'reveal_internals' in controls:
122 del controls['reveal_internals']
124 controls = ['%s:%d' % (k, int(v)) for k, v in controls.items()]
126 res = self.samdb.search(obj,
132 def assert_links(self, obj, expected, attr, msg='', **kwargs):
133 res = self.attr_search(obj, attr, **kwargs)
135 if len(expected) == 0:
137 self.fail("found attr '%s' in %s" % (attr, res[0]))
141 results = list([x[attr] for x in res][0])
143 self.fail("missing attr '%s' on %s" % (attr, obj))
145 expected = sorted(expected)
146 results = sorted(results)
148 if expected != results:
150 print("expected %s" % expected)
151 print("received %s" % results)
153 self.assertEqual(results, expected)
155 def assert_back_links(self, obj, expected, attr='memberOf', **kwargs):
156 self.assert_links(obj, expected, attr=attr,
157 msg='back links do not match', **kwargs)
159 def assert_forward_links(self, obj, expected, attr='member', **kwargs):
160 self.assert_links(obj, expected, attr=attr,
161 msg='forward links do not match', **kwargs)
163 def get_object_guid(self, dn):
164 res = self.samdb.search(dn,
165 scope=ldb.SCOPE_BASE,
166 attrs=['objectGUID'])
167 return str(misc.GUID(res[0]['objectGUID'][0]))
169 def assertRaisesLdbError(self, errcode, msg, f, *args, **kwargs):
170 """Assert a function raises a particular LdbError."""
173 except ldb.LdbError as e:
176 lut = {v: k for k, v in vars(ldb).items()
177 if k.startswith('ERR_') and isinstance(v, int)}
178 self.fail("%s, expected "
180 "got %s (%d)" % (msg,
181 lut.get(errcode), errcode,
184 lut = {v: k for k, v in vars(ldb).items()
185 if k.startswith('ERR_') and isinstance(v, int)}
186 self.fail("%s, expected "
188 "but we got success" % (msg, lut.get(errcode), errcode))
190 def _test_la_backlinks(self, reveal=False):
195 kwargs = {'reveal_internals': 0}
197 u1, u2 = self.add_objects(2, 'user', 'u_%s' % tag)
198 g1, g2 = self.add_objects(2, 'group', 'g_%s' % tag)
200 self.add_linked_attribute(g1, u1)
201 self.add_linked_attribute(g2, u1)
202 self.add_linked_attribute(g2, u2)
204 self.assert_back_links(u1, [g1, g2], **kwargs)
205 self.assert_back_links(u2, [g2], **kwargs)
207 def test_la_backlinks(self):
208 self._test_la_backlinks()
210 def test_la_backlinks_reveal(self):
211 if opts.no_reveal_internals:
212 print('skipping because --no-reveal-internals')
214 self._test_la_backlinks(True)
216 def _test_la_backlinks_delete_group(self, reveal=False):
221 kwargs = {'reveal_internals': 0}
223 u1, u2 = self.add_objects(2, 'user', 'u_' + tag)
224 g1, g2 = self.add_objects(2, 'group', 'g_' + tag)
226 self.add_linked_attribute(g1, u1)
227 self.add_linked_attribute(g2, u1)
228 self.add_linked_attribute(g2, u2)
230 self.samdb.delete(g2, ['tree_delete:1'])
232 self.assert_back_links(u1, [g1], **kwargs)
233 self.assert_back_links(u2, set(), **kwargs)
235 def test_la_backlinks_delete_group(self):
236 self._test_la_backlinks_delete_group()
238 def test_la_backlinks_delete_group_reveal(self):
239 if opts.no_reveal_internals:
240 print('skipping because --no-reveal-internals')
242 self._test_la_backlinks_delete_group(True)
244 def test_links_all_delete_group(self):
245 u1, u2 = self.add_objects(2, 'user', 'u_all_del_group')
246 g1, g2 = self.add_objects(2, 'group', 'g_all_del_group')
247 g2guid = self.get_object_guid(g2)
249 self.add_linked_attribute(g1, u1)
250 self.add_linked_attribute(g2, u1)
251 self.add_linked_attribute(g2, u2)
253 self.samdb.delete(g2)
254 self.assert_back_links(u1, [g1], show_deleted=1, show_recycled=1,
255 show_deactivated_link=0)
256 self.assert_back_links(u2, set(), show_deleted=1, show_recycled=1,
257 show_deactivated_link=0)
258 self.assert_forward_links(g1, [u1], show_deleted=1, show_recycled=1,
259 show_deactivated_link=0)
260 self.assert_forward_links('<GUID=%s>' % g2guid,
261 [], show_deleted=1, show_recycled=1,
262 show_deactivated_link=0)
264 def test_links_all_delete_group_reveal(self):
265 u1, u2 = self.add_objects(2, 'user', 'u_all_del_group_reveal')
266 g1, g2 = self.add_objects(2, 'group', 'g_all_del_group_reveal')
267 g2guid = self.get_object_guid(g2)
269 self.add_linked_attribute(g1, u1)
270 self.add_linked_attribute(g2, u1)
271 self.add_linked_attribute(g2, u2)
273 self.samdb.delete(g2)
274 self.assert_back_links(u1, [g1], show_deleted=1, show_recycled=1,
275 show_deactivated_link=0,
277 self.assert_back_links(u2, set(), show_deleted=1, show_recycled=1,
278 show_deactivated_link=0,
280 self.assert_forward_links(g1, [u1], show_deleted=1, show_recycled=1,
281 show_deactivated_link=0,
283 self.assert_forward_links('<GUID=%s>' % g2guid,
284 [], show_deleted=1, show_recycled=1,
285 show_deactivated_link=0,
288 def test_la_links_delete_link(self):
289 u1, u2 = self.add_objects(2, 'user', 'u_del_link')
290 g1, g2 = self.add_objects(2, 'group', 'g_del_link')
292 res = self.samdb.search(g1, scope=ldb.SCOPE_BASE,
293 attrs=['uSNChanged'])
294 old_usn1 = int(res[0]['uSNChanged'][0])
296 self.add_linked_attribute(g1, u1)
298 res = self.samdb.search(g1, scope=ldb.SCOPE_BASE,
299 attrs=['uSNChanged'])
300 new_usn1 = int(res[0]['uSNChanged'][0])
302 self.assertNotEqual(old_usn1, new_usn1, "USN should have incremented")
304 self.add_linked_attribute(g2, u1)
305 self.add_linked_attribute(g2, u2)
307 res = self.samdb.search(g2, scope=ldb.SCOPE_BASE,
308 attrs=['uSNChanged'])
309 old_usn2 = int(res[0]['uSNChanged'][0])
311 self.remove_linked_attribute(g2, u1)
313 res = self.samdb.search(g2, scope=ldb.SCOPE_BASE,
314 attrs=['uSNChanged'])
315 new_usn2 = int(res[0]['uSNChanged'][0])
317 self.assertNotEqual(old_usn2, new_usn2, "USN should have incremented")
319 self.assert_forward_links(g1, [u1])
320 self.assert_forward_links(g2, [u2])
322 self.add_linked_attribute(g2, u1)
323 self.assert_forward_links(g2, [u1, u2])
324 self.remove_linked_attribute(g2, u2)
325 self.assert_forward_links(g2, [u1])
326 self.remove_linked_attribute(g2, u1)
327 self.assert_forward_links(g2, [])
328 self.remove_linked_attribute(g1, [])
329 self.assert_forward_links(g1, [])
331 # removing a duplicate link in the same message should fail
332 self.add_linked_attribute(g2, [u1, u2])
333 self.assertRaises(ldb.LdbError,
334 self.remove_linked_attribute, g2, [u1, u1])
336 def _test_la_links_delete_link_reveal(self):
337 u1, u2 = self.add_objects(2, 'user', 'u_del_link_reveal')
338 g1, g2 = self.add_objects(2, 'group', 'g_del_link_reveal')
340 self.add_linked_attribute(g1, u1)
341 self.add_linked_attribute(g2, u1)
342 self.add_linked_attribute(g2, u2)
344 self.remove_linked_attribute(g2, u1)
346 self.assert_forward_links(g2, [u1, u2], show_deleted=1,
348 show_deactivated_link=0,
352 def test_la_links_delete_link_reveal(self):
353 if opts.no_reveal_internals:
354 print('skipping because --no-reveal-internals')
356 self._test_la_links_delete_link_reveal()
358 def test_la_links_delete_user(self):
359 u1, u2 = self.add_objects(2, 'user', 'u_del_user')
360 g1, g2 = self.add_objects(2, 'group', 'g_del_user')
362 self.add_linked_attribute(g1, u1)
363 self.add_linked_attribute(g2, u1)
364 self.add_linked_attribute(g2, u2)
366 res = self.samdb.search(g1, scope=ldb.SCOPE_BASE,
367 attrs=['uSNChanged'])
368 old_usn1 = int(res[0]['uSNChanged'][0])
370 res = self.samdb.search(g2, scope=ldb.SCOPE_BASE,
371 attrs=['uSNChanged'])
372 old_usn2 = int(res[0]['uSNChanged'][0])
374 self.samdb.delete(u1)
376 self.assert_forward_links(g1, [])
377 self.assert_forward_links(g2, [u2])
379 res = self.samdb.search(g1, scope=ldb.SCOPE_BASE,
380 attrs=['uSNChanged'])
381 new_usn1 = int(res[0]['uSNChanged'][0])
383 res = self.samdb.search(g2, scope=ldb.SCOPE_BASE,
384 attrs=['uSNChanged'])
385 new_usn2 = int(res[0]['uSNChanged'][0])
387 # Assert the USN on the alternate object is unchanged
388 self.assertEqual(old_usn1, new_usn1)
389 self.assertEqual(old_usn2, new_usn2)
391 def test_la_links_delete_user_reveal(self):
392 u1, u2 = self.add_objects(2, 'user', 'u_del_user_reveal')
393 g1, g2 = self.add_objects(2, 'group', 'g_del_user_reveal')
395 self.add_linked_attribute(g1, u1)
396 self.add_linked_attribute(g2, u1)
397 self.add_linked_attribute(g2, u2)
399 self.samdb.delete(u1)
401 self.assert_forward_links(g2, [u2],
402 show_deleted=1, show_recycled=1,
403 show_deactivated_link=0,
405 self.assert_forward_links(g1, [],
406 show_deleted=1, show_recycled=1,
407 show_deactivated_link=0,
410 def test_multiple_links(self):
411 u1, u2, u3, u4 = self.add_objects(4, 'user', 'u_multiple_links')
412 g1, g2, g3, g4 = self.add_objects(4, 'group', 'g_multiple_links')
414 self.add_linked_attribute(g1, [u1, u2, u3, u4])
415 self.add_linked_attribute(g2, [u3, u1])
416 self.add_linked_attribute(g3, u2)
418 self.assertRaisesLdbError(ldb.ERR_ENTRY_ALREADY_EXISTS,
419 "adding duplicate values",
420 self.add_linked_attribute, g2,
423 self.assert_forward_links(g1, [u1, u2, u3, u4])
424 self.assert_forward_links(g2, [u3, u1])
425 self.assert_forward_links(g3, [u2])
426 self.assert_back_links(u1, [g2, g1])
427 self.assert_back_links(u2, [g3, g1])
428 self.assert_back_links(u3, [g2, g1])
429 self.assert_back_links(u4, [g1])
431 self.remove_linked_attribute(g2, [u1, u3])
432 self.remove_linked_attribute(g1, [u1, u3])
434 self.assert_forward_links(g1, [u2, u4])
435 self.assert_forward_links(g2, [])
436 self.assert_forward_links(g3, [u2])
437 self.assert_back_links(u1, [])
438 self.assert_back_links(u2, [g3, g1])
439 self.assert_back_links(u3, [])
440 self.assert_back_links(u4, [g1])
442 self.add_linked_attribute(g1, [u1, u3])
443 self.add_linked_attribute(g2, [u3, u1])
444 self.add_linked_attribute(g3, [u1, u3])
446 self.assert_forward_links(g1, [u1, u2, u3, u4])
447 self.assert_forward_links(g2, [u1, u3])
448 self.assert_forward_links(g3, [u1, u2, u3])
449 self.assert_back_links(u1, [g1, g2, g3])
450 self.assert_back_links(u2, [g3, g1])
451 self.assert_back_links(u3, [g3, g2, g1])
452 self.assert_back_links(u4, [g1])
454 def test_la_links_replace(self):
455 u1, u2, u3, u4 = self.add_objects(4, 'user', 'u_replace')
456 g1, g2, g3, g4 = self.add_objects(4, 'group', 'g_replace')
458 self.add_linked_attribute(g1, [u1, u2])
459 self.add_linked_attribute(g2, [u1, u3])
460 self.add_linked_attribute(g3, u1)
462 self.replace_linked_attribute(g1, [u2])
463 self.replace_linked_attribute(g2, [u2, u3])
464 self.replace_linked_attribute(g3, [u1, u3])
465 self.replace_linked_attribute(g4, [u4])
467 self.assert_forward_links(g1, [u2])
468 self.assert_forward_links(g2, [u3, u2])
469 self.assert_forward_links(g3, [u3, u1])
470 self.assert_forward_links(g4, [u4])
471 self.assert_back_links(u1, [g3])
472 self.assert_back_links(u2, [g1, g2])
473 self.assert_back_links(u3, [g2, g3])
474 self.assert_back_links(u4, [g4])
476 self.replace_linked_attribute(g1, [u1, u2, u3])
477 self.replace_linked_attribute(g2, [u1])
478 self.replace_linked_attribute(g3, [u2])
479 self.replace_linked_attribute(g4, [])
481 self.assert_forward_links(g1, [u1, u2, u3])
482 self.assert_forward_links(g2, [u1])
483 self.assert_forward_links(g3, [u2])
484 self.assert_forward_links(g4, [])
485 self.assert_back_links(u1, [g1, g2])
486 self.assert_back_links(u2, [g1, g3])
487 self.assert_back_links(u3, [g1])
488 self.assert_back_links(u4, [])
490 self.assertRaisesLdbError(ldb.ERR_ENTRY_ALREADY_EXISTS,
491 "replacing duplicate values",
492 self.replace_linked_attribute, g2,
495 def test_la_links_replace2(self):
496 users = self.add_objects(12, 'user', 'u_replace2')
497 g1, = self.add_objects(1, 'group', 'g_replace2')
499 self.add_linked_attribute(g1, users[:6])
500 self.assert_forward_links(g1, users[:6])
501 self.replace_linked_attribute(g1, users)
502 self.assert_forward_links(g1, users)
503 self.replace_linked_attribute(g1, users[6:])
504 self.assert_forward_links(g1, users[6:])
505 self.remove_linked_attribute(g1, users[6:9])
506 self.assert_forward_links(g1, users[9:])
507 self.remove_linked_attribute(g1, users[9:])
508 self.assert_forward_links(g1, [])
510 def test_la_links_permutations(self):
511 """Make sure the order in which we add links doesn't matter."""
512 users = self.add_objects(3, 'user', 'u_permutations')
513 groups = self.add_objects(6, 'group', 'g_permutations')
515 for g, p in zip(groups, itertools.permutations(users)):
516 self.add_linked_attribute(g, p)
518 # everyone should be in every group
520 self.assert_forward_links(g, users)
523 self.assert_back_links(u, groups)
525 for g, p in zip(groups[::-1], itertools.permutations(users)):
526 self.replace_linked_attribute(g, p)
529 self.assert_forward_links(g, users)
532 self.assert_back_links(u, groups)
534 for g, p in zip(groups, itertools.permutations(users)):
535 self.remove_linked_attribute(g, p)
538 self.assert_forward_links(g, [])
541 self.assert_back_links(u, [])
543 def test_la_links_relaxed(self):
544 """Check that the relax control doesn't mess with linked attributes."""
545 relax_control = ['relax:0']
547 users = self.add_objects(10, 'user', 'u_relax')
548 groups = self.add_objects(3, 'group', 'g_relax',
549 more_attrs={'member': users[:2]})
550 g_relax1, g_relax2, g_uptight = groups
552 # g_relax1 has all users added at once
553 # g_relax2 gets them one at a time in reverse order
554 # g_uptight never relaxes
556 self.add_linked_attribute(g_relax1, users[2:5], controls=relax_control)
558 for u in reversed(users[2:5]):
559 self.add_linked_attribute(g_relax2, u, controls=relax_control)
560 self.add_linked_attribute(g_uptight, u)
563 self.assert_forward_links(g, users[:5])
565 self.add_linked_attribute(g, users[5:7])
566 self.assert_forward_links(g, users[:7])
569 self.add_linked_attribute(g, u)
571 self.assert_forward_links(g, users)
574 self.assert_back_links(u, groups)
576 # try some replacement permutations
581 random.shuffle(users2)
582 self.replace_linked_attribute(g_relax1, users2,
583 controls=relax_control)
585 self.assert_forward_links(g_relax1, users)
588 random.shuffle(users2)
589 self.remove_linked_attribute(g_relax2, users2,
590 controls=relax_control)
591 self.remove_linked_attribute(g_uptight, users2)
593 self.replace_linked_attribute(g_relax1, [], controls=relax_control)
595 random.shuffle(users2)
596 self.add_linked_attribute(g_relax2, users2,
597 controls=relax_control)
598 self.add_linked_attribute(g_uptight, users2)
599 self.replace_linked_attribute(g_relax1, users2,
600 controls=relax_control)
602 self.assert_forward_links(g_relax1, users)
603 self.assert_forward_links(g_relax2, users)
604 self.assert_forward_links(g_uptight, users)
607 self.assert_back_links(u, groups)
609 def test_add_all_at_once(self):
610 """All these other tests are creating linked attributes after the
611 objects are there. We want to test creating them all at once
614 users = self.add_objects(7, 'user', 'u_all_at_once')
615 g1, g3 = self.add_objects(2, 'group', 'g_all_at_once',
616 more_attrs={'member': users})
617 (g2,) = self.add_objects(1, 'group', 'g_all_at_once2',
618 more_attrs={'member': users[:5]})
620 self.assertRaisesLdbError(ldb.ERR_ENTRY_ALREADY_EXISTS,
621 "adding multiple duplicate values",
622 self.add_objects, 1, 'group',
623 'g_with_duplicate_links',
624 more_attrs={'member': users[:5] + users[1:2]})
626 self.assert_forward_links(g1, users)
627 self.assert_forward_links(g2, users[:5])
628 self.assert_forward_links(g3, users)
630 self.assert_back_links(u, [g1, g2, g3])
632 self.assert_back_links(u, [g1, g3])
634 self.remove_linked_attribute(g2, users[0])
635 self.remove_linked_attribute(g2, users[1])
636 self.add_linked_attribute(g2, users[1])
637 self.add_linked_attribute(g2, users[5])
638 self.add_linked_attribute(g2, users[6])
640 self.assert_forward_links(g1, users)
641 self.assert_forward_links(g2, users[1:])
644 self.remove_linked_attribute(g2, u)
645 self.remove_linked_attribute(g1, users)
650 self.assert_forward_links(g1, [])
651 self.assert_forward_links(g2, [])
652 self.assert_forward_links(g3, [])
654 def test_one_way_attributes(self):
655 e1, e2 = self.add_objects(2, 'msExchConfigurationContainer',
657 guid = self.get_object_guid(e2)
659 self.add_linked_attribute(e1, e2, attr="addressBookRoots")
660 self.assert_forward_links(e1, [e2], attr='addressBookRoots')
662 self.samdb.delete(e2)
664 res = self.samdb.search("<GUID=%s>" % guid,
665 scope=ldb.SCOPE_BASE,
666 controls=['show_deleted:1',
669 new_dn = str(res[0].dn)
670 self.assert_forward_links(e1, [new_dn], attr='addressBookRoots')
671 self.assert_forward_links(e1, [new_dn],
672 attr='addressBookRoots',
673 show_deactivated_link=0)
675 def test_one_way_attributes_delete_link(self):
676 e1, e2 = self.add_objects(2, 'msExchConfigurationContainer',
678 guid = self.get_object_guid(e2)
680 self.add_linked_attribute(e1, e2, attr="addressBookRoots")
681 self.assert_forward_links(e1, [e2], attr='addressBookRoots')
683 self.remove_linked_attribute(e1, e2, attr="addressBookRoots")
685 self.assert_forward_links(e1, [], attr='addressBookRoots')
686 self.assert_forward_links(e1, [], attr='addressBookRoots',
687 show_deactivated_link=0)
689 def test_pretend_one_way_attributes(self):
690 e1, e2 = self.add_objects(2, 'msExchConfigurationContainer',
692 guid = self.get_object_guid(e2)
694 self.add_linked_attribute(e1, e2, attr="addressBookRoots2")
695 self.assert_forward_links(e1, [e2], attr='addressBookRoots2')
697 self.samdb.delete(e2)
698 res = self.samdb.search("<GUID=%s>" % guid,
699 scope=ldb.SCOPE_BASE,
700 controls=['show_deleted:1',
703 new_dn = str(res[0].dn)
705 self.assert_forward_links(e1, [], attr='addressBookRoots2')
706 self.assert_forward_links(e1, [], attr='addressBookRoots2',
707 show_deactivated_link=0)
709 def test_pretend_one_way_attributes_delete_link(self):
710 e1, e2 = self.add_objects(2, 'msExchConfigurationContainer',
712 guid = self.get_object_guid(e2)
714 self.add_linked_attribute(e1, e2, attr="addressBookRoots2")
715 self.assert_forward_links(e1, [e2], attr='addressBookRoots2')
717 self.remove_linked_attribute(e1, e2, attr="addressBookRoots2")
719 self.assert_forward_links(e1, [], attr='addressBookRoots2')
720 self.assert_forward_links(e1, [], attr='addressBookRoots2',
721 show_deactivated_link=0)
724 if "://" not in host:
725 if os.path.isfile(host):
726 host = "tdb://%s" % host
728 host = "ldap://%s" % host
731 TestProgram(module=__name__, opts=subunitopts)