2 # -*- coding: utf-8 -*-
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008-2011
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 from itertools import permutations
25 sys.path.insert(0, "bin/python")
27 from samba.tests.subunitrun import SubunitOptions, TestProgram
28 import samba.getopt as options
30 from samba.auth import system_session
31 from ldb import SCOPE_BASE, LdbError
32 from ldb import Message, MessageElement, Dn
33 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
34 from samba.samdb import SamDB
36 from samba.tests import delete_force
38 TEST_DATA_DIR = os.path.join(
39 os.path.dirname(__file__),
43 def _build_ldb_strerr():
45 for k, v in vars(ldb).items():
46 if k.startswith('ERR_') and isinstance(v, int):
52 class ModifyOrderTests(samba.tests.TestCase):
56 self.admin_dsdb = get_dsdb(admin_creds)
57 self.base_dn = self.admin_dsdb.domain_dn()
59 def delete_object(self, dn):
60 delete_force(self.admin_dsdb, dn)
62 def get_user_dn(self, name):
63 return "CN=%s,CN=Users,%s" % (name, self.base_dn)
65 def _test_modify_order(self,
68 extra_search_attrs=(),
71 name = traceback.extract_stack()[-2][2][5:]
76 password = "pass123@#$@#"
77 self.admin_dsdb.newuser(username, password)
78 self.addCleanup(self.delete_object, self.get_user_dn(username))
79 mod_creds = self.insta_creds(template=admin_creds,
83 mod_creds = admin_creds
85 mod_dsdb = get_dsdb(mod_creds)
87 op_lut = ['', 'add', 'replace', 'delete']
89 search_attrs = set(extra_search_attrs)
90 lines = [name, "initial attrs:"]
91 for k, v in start_attrs:
92 lines.append("%20s: %r" % (k, v))
95 for k, v, op in mod_attrs:
98 search_attrs = sorted(search_attrs)
99 header = "\n".join(lines)
103 for i, attrs in enumerate(permutations(mod_attrs)):
104 # for each permuation we construct a string describing the
105 # requested operations, and a string describing the result
106 # (which may be an exception). The we cluster the
107 # attribute strings by their results.
108 dn = "cn=ldaptest_%s_%d,cn=users,%s" % (name, i, self.base_dn)
110 m.dn = Dn(self.admin_dsdb, dn)
112 # We are using Message objects here for add (rather than the
113 # more convenient dict) because we maybe care about the order
114 # in which attributes are added.
116 for k, v in start_attrs:
117 m[k] = MessageElement(v, 0, k)
119 self.admin_dsdb.add(m)
120 self.addCleanup(self.delete_object, dn)
123 m.dn = Dn(mod_dsdb, dn)
126 for k, v, op in attrs:
129 m[k] = MessageElement(v, op, k)
130 attr_lines.append("%16s %-8s %s" % (k, op_lut[op], v))
132 attr_str = '\n'.join(attr_lines)
136 except LdbError as e:
138 s = LDB_STRERR.get(err, "unknown error")
139 result_str = "%s (%d)" % (s, err)
141 res = self.admin_dsdb.search(base=dn, scope=SCOPE_BASE,
145 for k, v in sorted(dict(res[0]).items()):
146 if k != "dn" or k in extra_search_attrs:
147 lines.append("%20s: %r" % (k, sorted(v)))
149 result_str = '\n'.join(lines)
151 clusters.setdefault(result_str, []).append(attr_str)
153 for s, attrs in sorted(clusters.items()):
155 "== result ===[%3d]=======================" % len(attrs),
157 "-- operations ---------------------------"])
162 sig = '\n'.join(sig).replace(self.base_dn, "{base dn}")
167 if opts.rewrite_ground_truth:
168 f = open(os.path.join(TEST_DATA_DIR, name + '.expected'), 'w')
171 f = open(os.path.join(TEST_DATA_DIR, name + '.expected'))
175 self.assertStringsEqual(sig, expected)
177 def test_modify_order_mixed(self):
178 start_attrs = [("objectclass", "user"),
179 ("carLicense", ["1", "2", "3"]),
180 ("otherTelephone", "123")]
182 mod_attrs = [("carLicense", "3", FLAG_MOD_DELETE),
183 ("carLicense", "4", FLAG_MOD_ADD),
184 ("otherTelephone", "4", FLAG_MOD_REPLACE),
185 ("otherTelephone", "123", FLAG_MOD_DELETE)]
186 self._test_modify_order(start_attrs, mod_attrs)
188 def test_modify_order_mixed2(self):
189 start_attrs = [("objectclass", "user"),
190 ("carLicense", ["1", "2", "3"]),
193 mod_attrs = [("carLicense", "3", FLAG_MOD_DELETE),
194 ("carLicense", "4", FLAG_MOD_ADD),
195 ("ipPhone", "4", FLAG_MOD_REPLACE),
196 ("ipPhone", "123", FLAG_MOD_DELETE)]
197 self._test_modify_order(start_attrs, mod_attrs)
199 def test_modify_order_telephone(self):
200 start_attrs = [("objectclass", "user"),
201 ("otherTelephone", "123")]
203 mod_attrs = [("carLicense", "3", FLAG_MOD_REPLACE),
204 ("carLicense", "4", FLAG_MOD_ADD),
205 ("otherTelephone", "4", FLAG_MOD_REPLACE),
206 ("otherTelephone", "4", FLAG_MOD_ADD),
207 ("otherTelephone", "123", FLAG_MOD_DELETE)]
208 self._test_modify_order(start_attrs, mod_attrs)
210 def test_modify_order_telephone_delete_delete(self):
211 start_attrs = [("objectclass", "user"),
212 ("otherTelephone", "123")]
214 mod_attrs = [("carLicense", "3", FLAG_MOD_REPLACE),
215 ("carLicense", "4", FLAG_MOD_DELETE),
216 ("otherTelephone", "4", FLAG_MOD_REPLACE),
217 ("otherTelephone", "4", FLAG_MOD_DELETE),
218 ("otherTelephone", "123", FLAG_MOD_DELETE)]
219 self._test_modify_order(start_attrs, mod_attrs)
221 def test_modify_order_objectclass(self):
222 start_attrs = [("objectclass", "user"),
223 ("otherTelephone", "123")]
225 mod_attrs = [("objectclass", "computer", FLAG_MOD_REPLACE),
226 ("objectclass", "user", FLAG_MOD_DELETE),
227 ("objectclass", "person", FLAG_MOD_DELETE)]
228 self._test_modify_order(start_attrs, mod_attrs)
230 def test_modify_order_objectclass2(self):
231 start_attrs = [("objectclass", "user")]
233 mod_attrs = [("objectclass", "computer", FLAG_MOD_REPLACE),
234 ("objectclass", "user", FLAG_MOD_ADD),
235 ("objectclass", "attributeSchema", FLAG_MOD_REPLACE),
236 ("objectclass", "inetOrgPerson", FLAG_MOD_ADD),
237 ("objectclass", "person", FLAG_MOD_DELETE)]
238 self._test_modify_order(start_attrs, mod_attrs)
240 def test_modify_order_singlevalue(self):
241 start_attrs = [("objectclass", "user"),
244 mod_attrs = [("givenName", "a", FLAG_MOD_REPLACE),
245 ("givenName", ["b", "a"], FLAG_MOD_REPLACE),
246 ("givenName", "b", FLAG_MOD_DELETE),
247 ("givenName", "a", FLAG_MOD_DELETE),
248 ("givenName", "c", FLAG_MOD_ADD)]
249 self._test_modify_order(start_attrs, mod_attrs)
251 def test_modify_order_inapplicable(self):
252 #attrbutes that don't go on a user
253 start_attrs = [("objectclass", "user"),
256 mod_attrs = [("dhcpSites", "b", FLAG_MOD_REPLACE),
257 ("dhcpSites", "b", FLAG_MOD_DELETE),
258 ("dhcpSites", "c", FLAG_MOD_ADD)]
259 self._test_modify_order(start_attrs, mod_attrs)
261 def test_modify_order_sometimes_inapplicable(self):
262 # attributes that don't go on a user, but do on a computer,
263 # which we sometimes change into.
264 start_attrs = [("objectclass", "user"),
267 mod_attrs = [("objectclass", "computer", FLAG_MOD_REPLACE),
268 ("objectclass", "person", FLAG_MOD_DELETE),
269 ("dnsHostName", "b", FLAG_MOD_ADD),
270 ("dnsHostName", "c", FLAG_MOD_REPLACE)]
271 self._test_modify_order(start_attrs, mod_attrs)
273 def test_modify_order_account_locality_device(self):
274 # account, locality, and device all take l (locality name) but
275 # only device takes owner. We shouldn't be able to change
276 # objectclass at all.
277 start_attrs = [("objectclass", "account"),
280 mod_attrs = [("objectclass", ["device", "top"], FLAG_MOD_REPLACE),
281 ("l", "a", FLAG_MOD_DELETE),
282 ("owner", "c", FLAG_MOD_ADD)
284 self._test_modify_order(start_attrs, mod_attrs)
286 def test_modify_order_container_flags_multivalue(self):
287 # account, locality, and device all take l (locality name)
288 # but only device takes owner
289 start_attrs = [("objectclass", "container"),
290 ("wWWHomePage", "a")]
292 mod_attrs = [("flags", ["0", "1"], FLAG_MOD_ADD),
293 ("flags", "65355", FLAG_MOD_ADD),
294 ("flags", "65355", FLAG_MOD_DELETE),
295 ("flags", ["2", "101"], FLAG_MOD_REPLACE),
297 self._test_modify_order(start_attrs, mod_attrs)
299 def test_modify_order_container_flags(self):
300 #flags should be an integer
301 start_attrs = [("objectclass", "container")]
303 mod_attrs = [("flags", "0x6", FLAG_MOD_ADD),
304 ("flags", "5", FLAG_MOD_ADD),
305 ("flags", "101", FLAG_MOD_REPLACE),
306 ("flags", "c", FLAG_MOD_DELETE),
308 self._test_modify_order(start_attrs, mod_attrs)
310 def test_modify_order_member(self):
311 name = "modify_order_member_other_group"
313 dn2 = "cn=%s,%s" % (name, self.base_dn)
315 m.dn = Dn(self.admin_dsdb, dn2)
316 self.admin_dsdb.add({"dn": dn2, "objectclass": "group"})
317 self.addCleanup(self.delete_object, dn2)
319 start_attrs = [("objectclass", "group"),
322 mod_attrs = [("member", None, FLAG_MOD_DELETE),
323 ("member", None, FLAG_MOD_REPLACE),
324 ("member", dn2, FLAG_MOD_DELETE),
325 ("member", None, FLAG_MOD_ADD),
327 self._test_modify_order(start_attrs, mod_attrs, ["memberOf"])
330 def get_dsdb(creds=None):
335 session_info=system_session(lp),
340 parser = optparse.OptionParser("ldap_modify_order.py [options] <host>")
341 sambaopts = options.SambaOptions(parser)
342 parser.add_option_group(sambaopts)
343 parser.add_option_group(options.VersionOptions(parser))
344 credopts = options.CredentialsOptions(parser)
345 parser.add_option_group(credopts)
346 subunitopts = SubunitOptions(parser)
347 parser.add_option_group(subunitopts)
348 parser.add_option("--rewrite-ground-truth", action="store_true",
349 help="write expected values")
350 parser.add_option("-v", "--verbose", action="store_true")
351 parser.add_option("--normal-user", action="store_true")
353 opts, args = parser.parse_args()
361 lp = sambaopts.get_loadparm()
362 admin_creds = credopts.get_credentials(lp)
364 if "://" not in host:
365 if os.path.isfile(host):
366 host = "tdb://%s" % host
368 host = "ldap://%s" % host
371 TestProgram(module=__name__, opts=subunitopts)