PEP8: fix E302: expected 2 blank lines, found 1
[bbaumbach/samba-autobuild/.git] / source4 / torture / drs / python / getnc_exop.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Tests various schema replication scenarios
5 #
6 # Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2011
7 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2016
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 #
22
23 #
24 # Usage:
25 #  export DC1=dc1_dns_name
26 #  export DC2=dc2_dns_name
27 #  export SUBUNITRUN=$samba4srcdir/scripting/bin/subunitrun
28 #  PYTHONPATH="$PYTHONPATH:$samba4srcdir/torture/drs/python" $SUBUNITRUN getnc_exop -U"$DOMAIN/$DC_USERNAME"%"$DC_PASSWORD"
29 #
30
31 import random
32
33 import drs_base
34 from drs_base import AbstractLink
35
36 import samba.tests
37 import random
38 from samba import werror, WERRORError
39
40 import ldb
41 from ldb import SCOPE_BASE
42
43 from samba.dcerpc import drsuapi, misc, drsblobs
44 from samba.drs_utils import drs_DsBind
45 from samba.ndr import ndr_unpack, ndr_pack
46 from samba.compat import cmp_to_key_fn
47 from samba.compat import cmp_fn
48
49
50 def _linked_attribute_compare(la1, la2):
51     """See CompareLinks() in MS-DRSR section 4.1.10.5.17"""
52     la1, la1_target = la1
53     la2, la2_target = la2
54
55     # Ascending host object GUID
56     c = cmp_fn(ndr_pack(la1.identifier.guid), ndr_pack(la2.identifier.guid))
57     if c != 0:
58         return c
59
60     # Ascending attribute ID
61     if la1.attid != la2.attid:
62         return -1 if la1.attid < la2.attid else 1
63
64     la1_active = la1.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
65     la2_active = la2.flags & drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
66
67     # Ascending 'is present'
68     if la1_active != la2_active:
69         return 1 if la1_active else -1
70
71     # Ascending target object GUID
72     return cmp_fn(ndr_pack(la1_target), ndr_pack(la2_target))
73
74
75 class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase):
76     """Intended as a semi-black box test case for DsGetNCChanges
77        implementation for extended operations. It should be testing
78        how DsGetNCChanges handles different input params (mostly invalid).
79        Final goal is to make DsGetNCChanges as binary compatible to
80        Windows implementation as possible"""
81
82     def setUp(self):
83         super(DrsReplicaSyncTestCase, self).setUp()
84         self.base_dn = self.ldb_dc1.get_default_basedn()
85         self.ou = "OU=test_getncchanges,%s" % self.base_dn
86         self.ldb_dc1.add({
87             "dn": self.ou,
88             "objectclass": "organizationalUnit"})
89         (self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc1)
90         (self.default_hwm, self.default_utdv) = self._get_highest_hwm_utdv(self.ldb_dc1)
91
92     def tearDown(self):
93         try:
94             self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
95         except ldb.LdbError as e:
96             (enum, string) = e.args
97             if enum == ldb.ERR_NO_SUCH_OBJECT:
98                 pass
99         super(DrsReplicaSyncTestCase, self).tearDown()
100
101     def _determine_fSMORoleOwner(self, fsmo_obj_dn):
102         """Returns (owner, not_owner) pair where:
103              owner: dns name for FSMO owner
104              not_owner: dns name for DC not owning the FSMO"""
105         # collect info to return later
106         fsmo_info_1 = {"dns_name": self.dnsname_dc1,
107                        "invocation_id": self.ldb_dc1.get_invocation_id(),
108                        "ntds_guid": self.ldb_dc1.get_ntds_GUID(),
109                        "server_dn": self.ldb_dc1.get_serverName()}
110         fsmo_info_2 = {"dns_name": self.dnsname_dc2,
111                        "invocation_id": self.ldb_dc2.get_invocation_id(),
112                        "ntds_guid": self.ldb_dc2.get_ntds_GUID(),
113                        "server_dn": self.ldb_dc2.get_serverName()}
114
115         msgs = self.ldb_dc1.search(scope=ldb.SCOPE_BASE, base=fsmo_info_1["server_dn"], attrs=["serverReference"])
116         fsmo_info_1["server_acct_dn"] = ldb.Dn(self.ldb_dc1, msgs[0]["serverReference"][0].decode('utf8'))
117         fsmo_info_1["rid_set_dn"] = ldb.Dn(self.ldb_dc1, "CN=RID Set") + fsmo_info_1["server_acct_dn"]
118
119         msgs = self.ldb_dc2.search(scope=ldb.SCOPE_BASE, base=fsmo_info_2["server_dn"], attrs=["serverReference"])
120         fsmo_info_2["server_acct_dn"] = ldb.Dn(self.ldb_dc2, msgs[0]["serverReference"][0].decode('utf8'))
121         fsmo_info_2["rid_set_dn"] = ldb.Dn(self.ldb_dc2, "CN=RID Set") + fsmo_info_2["server_acct_dn"]
122
123         # determine the owner dc
124         res = self.ldb_dc1.search(fsmo_obj_dn,
125                                   scope=SCOPE_BASE, attrs=["fSMORoleOwner"])
126         assert len(res) == 1, "Only one fSMORoleOwner value expected for %s!" %fsmo_obj_dn
127         fsmo_owner = res[0]["fSMORoleOwner"][0]
128         if fsmo_owner == self.info_dc1["dsServiceName"][0]:
129             return (fsmo_info_1, fsmo_info_2)
130         return (fsmo_info_2, fsmo_info_1)
131
132     def _check_exop_failed(self, ctr6, expected_failure):
133         self.assertEqual(ctr6.extended_ret, expected_failure)
134         #self.assertEqual(ctr6.object_count, 0)
135         #self.assertEqual(ctr6.first_object, None)
136         self.assertEqual(ctr6.more_data, False)
137         self.assertEqual(ctr6.nc_object_count, 0)
138         self.assertEqual(ctr6.nc_linked_attributes_count, 0)
139         self.assertEqual(ctr6.linked_attributes_count, 0)
140         self.assertEqual(ctr6.linked_attributes, [])
141         self.assertEqual(ctr6.drs_error[0], 0)
142
143     def test_do_single_repl(self):
144         """
145         Make sure that DRSUAPI_EXOP_REPL_OBJ never replicates more than
146         one object, even when we use DRS_GET_ANC/GET_TGT.
147         """
148
149         ou1 = "OU=get_anc1,%s" % self.ou
150         self.ldb_dc1.add({
151             "dn": ou1,
152             "objectclass": "organizationalUnit"
153         })
154         ou1_id = self._get_identifier(self.ldb_dc1, ou1)
155         ou2 = "OU=get_anc2,%s" % ou1
156         self.ldb_dc1.add({
157             "dn": ou2,
158             "objectclass": "organizationalUnit"
159         })
160         ou2_id = self._get_identifier(self.ldb_dc1, ou2)
161         dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
162         self.ldb_dc1.add({
163             "dn": dc3,
164             "objectclass": "computer",
165             "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
166         })
167         dc3_id = self._get_identifier(self.ldb_dc1, dc3)
168
169         # Add some linked attributes (for checking GET_TGT behaviour)
170         m = ldb.Message()
171         m.dn = ldb.Dn(self.ldb_dc2, ou1)
172         m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_ADD, "managedBy")
173         self.ldb_dc1.modify(m)
174         ou1_link = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
175                                 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
176                                 ou1_id.guid, ou2_id.guid)
177
178         m.dn = ldb.Dn(self.ldb_dc2, dc3)
179         m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_ADD, "managedBy")
180         self.ldb_dc1.modify(m)
181         dc3_link = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
182                                 drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
183                                 dc3_id.guid, ou2_id.guid)
184
185         req = self._getnc_req10(dest_dsa=None,
186                                 invocation_id=self.ldb_dc1.get_invocation_id(),
187                                 nc_dn_str=ou1,
188                                 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
189                                 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP,
190                                 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT)
191         (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req)
192         self._check_ctr6(ctr, [ou1], expected_links=[ou1_link])
193
194         # DRSUAPI_DRS_WRIT_REP means that we should only replicate the dn we give (dc3).
195         # DRSUAPI_DRS_GET_ANC means that we should also replicate its ancestors, but
196         # Windows doesn't do this if we use both.
197         req = self._getnc_req10(dest_dsa=None,
198                                 invocation_id=self.ldb_dc1.get_invocation_id(),
199                                 nc_dn_str=dc3,
200                                 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
201                                 replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP |
202                                 drsuapi.DRSUAPI_DRS_GET_ANC,
203                                 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT)
204         (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req)
205         self._check_ctr6(ctr, [dc3], expected_links=[dc3_link])
206
207         # Even though the ancestor of ou2 (ou1) has changed since last hwm, and we're
208         # sending DRSUAPI_DRS_GET_ANC, the expected response is that it will only try
209         # and replicate the single object still.
210         req = self._getnc_req10(dest_dsa=None,
211                                 invocation_id=self.ldb_dc1.get_invocation_id(),
212                                 nc_dn_str=ou2,
213                                 exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
214                                 replica_flags=drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
215                                 drsuapi.DRSUAPI_DRS_GET_ANC,
216                                 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT)
217         (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req)
218         self._check_ctr6(ctr, [ou2])
219
220     def test_do_full_repl_on_ou(self):
221         """
222         Make sure that a full replication on a not-an-nc fails with
223         the right error code
224         """
225
226         non_nc_ou = "OU=not-an-NC,%s" % self.ou
227         self.ldb_dc1.add({
228             "dn": non_nc_ou,
229             "objectclass": "organizationalUnit"
230         })
231         req8 = self._exop_req8(dest_dsa=None,
232                                invocation_id=self.ldb_dc1.get_invocation_id(),
233                                nc_dn_str=non_nc_ou,
234                                exop=drsuapi.DRSUAPI_EXOP_NONE,
235                                replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
236
237         try:
238             (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 8, req8)
239             self.fail("Expected DsGetNCChanges to fail with WERR_DS_CANT_FIND_EXPECTED_NC")
240         except WERRORError as e1:
241             (enum, estr) = e1.args
242             self.assertEquals(enum, werror.WERR_DS_CANT_FIND_EXPECTED_NC)
243
244     def test_link_utdv_hwm(self):
245         """Test verify the DRS_GET_ANC behavior."""
246
247         ou1 = "OU=get_anc1,%s" % self.ou
248         self.ldb_dc1.add({
249             "dn": ou1,
250             "objectclass": "organizationalUnit"
251         })
252         ou1_id = self._get_identifier(self.ldb_dc1, ou1)
253         ou2 = "OU=get_anc2,%s" % ou1
254         self.ldb_dc1.add({
255             "dn": ou2,
256             "objectclass": "organizationalUnit"
257         })
258         ou2_id = self._get_identifier(self.ldb_dc1, ou2)
259         dc3 = "CN=test_anc_dc_%u,%s" % (random.randint(0, 4294967295), ou2)
260         self.ldb_dc1.add({
261             "dn": dc3,
262             "objectclass": "computer",
263             "userAccountControl": "%d" % (samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_SERVER_TRUST_ACCOUNT)
264         })
265         dc3_id = self._get_identifier(self.ldb_dc1, dc3)
266
267         (hwm1, utdv1) = self._check_replication([ou1, ou2, dc3],
268                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
269
270         self._check_replication([ou1, ou2, dc3],
271                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
272                                 drsuapi.DRSUAPI_DRS_GET_ANC)
273
274         self._check_replication([dc3],
275                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
276
277         self._check_replication([ou1, ou2, dc3],
278                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
279                                 drsuapi.DRSUAPI_DRS_GET_ANC)
280
281         m = ldb.Message()
282         m.dn = ldb.Dn(self.ldb_dc1, ou1)
283         m["displayName"] = ldb.MessageElement("OU1", ldb.FLAG_MOD_ADD, "displayName")
284         self.ldb_dc1.modify(m)
285
286         (hwm2, utdv2) = self._check_replication([ou2, dc3, ou1],
287                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
288
289         self._check_replication([ou1, ou2, dc3],
290                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
291                                 drsuapi.DRSUAPI_DRS_GET_ANC)
292
293         self._check_replication([dc3],
294                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
295
296         self._check_replication([ou1, ou2, dc3],
297                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
298                                 drsuapi.DRSUAPI_DRS_GET_ANC)
299
300         self._check_replication([ou1],
301                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
302                                 highwatermark=hwm1)
303
304         self._check_replication([ou1],
305                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
306                                 drsuapi.DRSUAPI_DRS_GET_ANC,
307                                 highwatermark=hwm1)
308
309         self._check_replication([ou1],
310                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
311                                 drsuapi.DRSUAPI_DRS_GET_ANC,
312                                 uptodateness_vector=utdv1)
313
314         m = ldb.Message()
315         m.dn = ldb.Dn(self.ldb_dc1, ou2)
316         m["displayName"] = ldb.MessageElement("OU2", ldb.FLAG_MOD_ADD, "displayName")
317         self.ldb_dc1.modify(m)
318
319         (hwm3, utdv3) = self._check_replication([dc3, ou1, ou2],
320                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
321
322         self._check_replication([ou1, ou2, dc3],
323                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
324                                 drsuapi.DRSUAPI_DRS_GET_ANC)
325
326         self._check_replication([dc3],
327                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
328
329         self._check_replication([ou1, ou2, dc3],
330                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
331                                 drsuapi.DRSUAPI_DRS_GET_ANC)
332
333         self._check_replication([ou1, ou2],
334                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
335                                 highwatermark=hwm1)
336
337         self._check_replication([ou1, ou2],
338                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
339                                 drsuapi.DRSUAPI_DRS_GET_ANC,
340                                 highwatermark=hwm1)
341
342         self._check_replication([ou1, ou2],
343                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
344                                 drsuapi.DRSUAPI_DRS_GET_ANC,
345                                 uptodateness_vector=utdv1)
346
347         m = ldb.Message()
348         m.dn = ldb.Dn(self.ldb_dc1, self.ou)
349         m["displayName"] = ldb.MessageElement("OU", ldb.FLAG_MOD_ADD, "displayName")
350         self.ldb_dc1.modify(m)
351
352         (hwm4, utdv4) = self._check_replication([dc3, ou1, ou2, self.ou],
353                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
354
355         self._check_replication([self.ou, ou1, ou2, dc3],
356                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
357                                 drsuapi.DRSUAPI_DRS_GET_ANC)
358
359         self._check_replication([dc3],
360                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
361
362         self._check_replication([self.ou, ou1, ou2, dc3],
363                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
364                                 drsuapi.DRSUAPI_DRS_GET_ANC)
365
366         self._check_replication([self.ou, ou2],
367                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
368                                 drsuapi.DRSUAPI_DRS_GET_ANC,
369                                 uptodateness_vector=utdv2)
370
371         cn3 = "CN=get_anc3,%s" % ou2
372         self.ldb_dc1.add({
373             "dn": cn3,
374             "objectclass": "container",
375         })
376         cn3_id = self._get_identifier(self.ldb_dc1, cn3)
377
378         (hwm5, utdv5) = self._check_replication([dc3, ou1, ou2, self.ou, cn3],
379                                                 drsuapi.DRSUAPI_DRS_WRIT_REP)
380
381         self._check_replication([self.ou, ou1, ou2, dc3, cn3],
382                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
383                                 drsuapi.DRSUAPI_DRS_GET_ANC)
384
385         self._check_replication([dc3],
386                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
387
388         self._check_replication([self.ou, ou1, ou2, dc3],
389                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
390                                 drsuapi.DRSUAPI_DRS_GET_ANC)
391
392         m = ldb.Message()
393         m.dn = ldb.Dn(self.ldb_dc1, ou2)
394         m["managedBy"] = ldb.MessageElement(dc3, ldb.FLAG_MOD_ADD, "managedBy")
395         self.ldb_dc1.modify(m)
396         ou2_managedBy_dc3 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
397                                          drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
398                                          ou2_id.guid, dc3_id.guid)
399
400         (hwm6, utdv6) = self._check_replication([dc3, ou1, self.ou, cn3, ou2],
401                                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
402                                                 expected_links=[ou2_managedBy_dc3])
403
404         # Can fail against Windows due to equal precedence of dc3, cn3
405         self._check_replication([self.ou, ou1, ou2, dc3, cn3],
406                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
407                                 drsuapi.DRSUAPI_DRS_GET_ANC,
408                                 expected_links=[ou2_managedBy_dc3])
409
410         self._check_replication([dc3],
411                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
412
413         self._check_replication([self.ou, ou1, ou2, dc3],
414                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
415                                 drsuapi.DRSUAPI_DRS_GET_ANC)
416
417         self._check_replication([],
418                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
419                                 uptodateness_vector=utdv5,
420                                 expected_links=[ou2_managedBy_dc3])
421
422         self._check_replication([],
423                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
424                                 uptodateness_vector=utdv5)
425
426         self._check_replication([],
427                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
428                                 uptodateness_vector=utdv5)
429
430         m = ldb.Message()
431         m.dn = ldb.Dn(self.ldb_dc1, dc3)
432         m["managedBy"] = ldb.MessageElement(ou1, ldb.FLAG_MOD_ADD, "managedBy")
433         self.ldb_dc1.modify(m)
434         dc3_managedBy_ou1 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
435                                          drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
436                                          dc3_id.guid, ou1_id.guid)
437
438         (hwm7, utdv7) = self._check_replication([ou1, self.ou, cn3, ou2, dc3],
439                                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
440                                                 expected_links=[ou2_managedBy_dc3, dc3_managedBy_ou1])
441
442         # Can fail against Windows due to equal precedence of dc3, cn3
443         # self._check_replication([self.ou,ou1,ou2,dc3,cn3],
444         #    drsuapi.DRSUAPI_DRS_WRIT_REP|
445         #    drsuapi.DRSUAPI_DRS_GET_ANC,
446         #    expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1])
447
448         self._check_replication([dc3],
449                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
450                                 expected_links=[dc3_managedBy_ou1])
451
452         self._check_replication([dc3],
453                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
454                                 expected_links=[dc3_managedBy_ou1])
455
456         self._check_replication([self.ou, ou1, ou2, dc3],
457                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
458                                 drsuapi.DRSUAPI_DRS_GET_ANC,
459                                 expected_links=[dc3_managedBy_ou1])
460
461         # GET_TGT seems to override DRS_CRITICAL_ONLY and also returns any
462         # object(s) that relate to the linked attributes (similar to GET_ANC)
463         self._check_replication([ou1, dc3],
464                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
465                                 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
466                                 expected_links=[dc3_managedBy_ou1], dn_ordered=False)
467
468         # Change DC3's managedBy to OU2 instead of OU1
469         # Note that the OU1 managedBy linked attribute will still exist as
470         # a tombstone object (and so will be returned in the replication still)
471         m = ldb.Message()
472         m.dn = ldb.Dn(self.ldb_dc1, dc3)
473         m["managedBy"] = ldb.MessageElement(ou2, ldb.FLAG_MOD_REPLACE, "managedBy")
474         self.ldb_dc1.modify(m)
475         dc3_managedBy_ou1.flags &= ~drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
476         dc3_managedBy_ou2 = AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
477                                          drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
478                                          dc3_id.guid, ou2_id.guid)
479
480         (hwm8, utdv8) = self._check_replication([ou1, self.ou, cn3, ou2, dc3],
481                                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
482                                                 expected_links=[ou2_managedBy_dc3, dc3_managedBy_ou1, dc3_managedBy_ou2])
483
484         # Can fail against Windows due to equal precedence of dc3, cn3
485         # self._check_replication([self.ou,ou1,ou2,dc3,cn3],
486         #    drsuapi.DRSUAPI_DRS_WRIT_REP|
487         #    drsuapi.DRSUAPI_DRS_GET_ANC,
488         #    expected_links=[ou2_managedBy_dc3,dc3_managedBy_ou1,dc3_managedBy_ou2])
489
490         self._check_replication([dc3],
491                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
492                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2])
493
494         self._check_replication([self.ou, ou1, ou2, dc3],
495                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
496                                 drsuapi.DRSUAPI_DRS_GET_ANC,
497                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2])
498
499         # GET_TGT will also return any DNs referenced by the linked attributes
500         # (including the Tombstone attribute)
501         self._check_replication([ou1, ou2, dc3],
502                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
503                                 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
504                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2], dn_ordered=False)
505
506         # Use the highwater-mark prior to changing ManagedBy - this should
507         # only return the old/Tombstone and new linked attributes (we already
508         # know all the DNs)
509         self._check_replication([],
510                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
511                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
512                                 highwatermark=hwm7)
513
514         self._check_replication([],
515                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
516                                 drsuapi.DRSUAPI_DRS_GET_ANC,
517                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
518                                 highwatermark=hwm7)
519
520         self._check_replication([],
521                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
522                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
523                                 highwatermark=hwm7)
524
525         self._check_replication([],
526                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
527                                 drsuapi.DRSUAPI_DRS_GET_ANC,
528                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
529                                 highwatermark=hwm7)
530
531         self._check_replication([],
532                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
533                                 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
534                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
535                                 highwatermark=hwm7)
536
537         # Repeat the above set of tests using the uptodateness_vector
538         # instead of the highwater-mark
539         self._check_replication([],
540                                 drsuapi.DRSUAPI_DRS_WRIT_REP,
541                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
542                                 uptodateness_vector=utdv7)
543
544         self._check_replication([],
545                                 drsuapi.DRSUAPI_DRS_WRIT_REP |
546                                 drsuapi.DRSUAPI_DRS_GET_ANC,
547                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
548                                 uptodateness_vector=utdv7)
549
550         self._check_replication([],
551                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
552                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
553                                 uptodateness_vector=utdv7)
554
555         self._check_replication([],
556                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY |
557                                 drsuapi.DRSUAPI_DRS_GET_ANC,
558                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
559                                 uptodateness_vector=utdv7)
560
561         self._check_replication([],
562                                 drsuapi.DRSUAPI_DRS_CRITICAL_ONLY,
563                                 more_flags=drsuapi.DRSUAPI_DRS_GET_TGT,
564                                 expected_links=[dc3_managedBy_ou1, dc3_managedBy_ou2],
565                                 uptodateness_vector=utdv7)
566
567     def test_FSMONotOwner(self):
568         """Test role transfer with against DC not owner of the role"""
569         fsmo_dn = self.ldb_dc1.get_schema_basedn()
570         (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
571
572         req8 = self._exop_req8(dest_dsa=fsmo_owner["ntds_guid"],
573                                invocation_id=fsmo_not_owner["invocation_id"],
574                                nc_dn_str=fsmo_dn,
575                                exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
576
577         (drs, drs_handle) = self._ds_bind(fsmo_not_owner["dns_name"])
578         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
579         self.assertEqual(level, 6, "Expected level 6 response!")
580         self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER)
581         self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_not_owner["ntds_guid"]))
582         self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_not_owner["invocation_id"]))
583
584     def test_InvalidDestDSA(self):
585         """Test role transfer with invalid destination DSA guid"""
586         fsmo_dn = self.ldb_dc1.get_schema_basedn()
587         (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
588
589         req8 = self._exop_req8(dest_dsa="9c637462-5b8c-4467-aef2-bdb1f57bc4ef",
590                                invocation_id=fsmo_owner["invocation_id"],
591                                nc_dn_str=fsmo_dn,
592                                exop=drsuapi.DRSUAPI_EXOP_FSMO_REQ_ROLE)
593
594         (drs, drs_handle) = self._ds_bind(fsmo_owner["dns_name"])
595         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
596         self.assertEqual(level, 6, "Expected level 6 response!")
597         self._check_exop_failed(ctr, drsuapi.DRSUAPI_EXOP_ERR_UNKNOWN_CALLER)
598         self.assertEqual(ctr.source_dsa_guid, misc.GUID(fsmo_owner["ntds_guid"]))
599         self.assertEqual(ctr.source_dsa_invocation_id, misc.GUID(fsmo_owner["invocation_id"]))
600
601
602 class DrsReplicaPrefixMapTestCase(drs_base.DrsBaseTestCase):
603     def setUp(self):
604         super(DrsReplicaPrefixMapTestCase, self).setUp()
605         self.base_dn = self.ldb_dc1.get_default_basedn()
606         self.ou = "ou=pfm_exop,%s" % self.base_dn
607         self.ldb_dc1.add({
608             "dn": self.ou,
609             "objectclass": "organizationalUnit"})
610         self.user = "cn=testuser,%s" % self.ou
611         self.ldb_dc1.add({
612             "dn": self.user,
613             "objectclass": "user"})
614
615     def tearDown(self):
616         super(DrsReplicaPrefixMapTestCase, self).tearDown()
617         try:
618             self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
619         except ldb.LdbError as e2:
620             (enum, string) = e2.args
621             if enum == ldb.ERR_NO_SUCH_OBJECT:
622                 pass
623
624     def test_missing_prefix_map_dsa(self):
625         partial_attribute_set = self.get_partial_attribute_set()
626
627         dc_guid_1 = self.ldb_dc1.get_invocation_id()
628
629         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
630
631         req8 = self._exop_req8(dest_dsa=None,
632                                invocation_id=dc_guid_1,
633                                nc_dn_str=self.user,
634                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
635                                partial_attribute_set=partial_attribute_set)
636
637         try:
638             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
639             self.assertEqual(ctr.extended_ret, drsuapi.DRSUAPI_EXOP_ERR_SUCCESS)
640         except RuntimeError:
641             self.fail("Missing prefixmap shouldn't have triggered an error")
642
643     def test_invalid_prefix_map_attid(self):
644         # Request for invalid attid
645         partial_attribute_set = self.get_partial_attribute_set([99999])
646
647         dc_guid_1 = self.ldb_dc1.get_invocation_id()
648         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
649
650         try:
651             pfm = self._samdb_fetch_pfm_and_schi()
652         except KeyError:
653             # On Windows, prefixMap isn't available over LDAP
654             req8 = self._exop_req8(dest_dsa=None,
655                                    invocation_id=dc_guid_1,
656                                    nc_dn_str=self.user,
657                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
658             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
659             pfm = ctr.mapping_ctr
660
661         req8 = self._exop_req8(dest_dsa=None,
662                                invocation_id=dc_guid_1,
663                                nc_dn_str=self.user,
664                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
665                                partial_attribute_set=partial_attribute_set,
666                                mapping_ctr=pfm)
667
668         try:
669             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
670             self.fail("Invalid attid (99999) should have triggered an error")
671         except RuntimeError as e3:
672             (ecode, emsg) = e3.args
673             self.assertEqual(ecode, 0x000020E2, "Error code should have been "
674                              "WERR_DS_DRA_SCHEMA_MISMATCH")
675
676     def test_secret_prefix_map_attid(self):
677         # Request for a secret attid
678         partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
679
680         dc_guid_1 = self.ldb_dc1.get_invocation_id()
681         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
682
683         try:
684             pfm = self._samdb_fetch_pfm_and_schi()
685         except KeyError:
686             # On Windows, prefixMap isn't available over LDAP
687             req8 = self._exop_req8(dest_dsa=None,
688                                    invocation_id=dc_guid_1,
689                                    nc_dn_str=self.user,
690                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
691             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
692             pfm = ctr.mapping_ctr
693
694
695         req8 = self._exop_req8(dest_dsa=None,
696                                invocation_id=dc_guid_1,
697                                nc_dn_str=self.user,
698                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
699                                partial_attribute_set=partial_attribute_set,
700                                mapping_ctr=pfm)
701
702         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
703
704         found = False
705         for attr in ctr.first_object.object.attribute_ctr.attributes:
706             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
707                 found = True
708                 break
709
710         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
711
712         for i, mapping in enumerate(pfm.mappings):
713             # OID: 2.5.4.*
714             # objectClass: 2.5.4.0
715             if mapping.oid.binary_oid == [85, 4]:
716                 idx1 = i
717             # OID: 1.2.840.113556.1.4.*
718             # unicodePwd: 1.2.840.113556.1.4.90
719             elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
720                 idx2 = i
721
722         (pfm.mappings[idx1].id_prefix,
723          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
724                                           pfm.mappings[idx1].id_prefix)
725
726         tmp = pfm.mappings
727         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
728         pfm.mappings = tmp
729
730         # 90 for unicodePwd (with new prefix = 0)
731         # 589824, 589827 for objectClass and CN
732         # Use of three ensures sorting is correct
733         partial_attribute_set = self.get_partial_attribute_set([90, 589824, 589827])
734         req8 = self._exop_req8(dest_dsa=None,
735                                invocation_id=dc_guid_1,
736                                nc_dn_str=self.user,
737                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
738                                partial_attribute_set=partial_attribute_set,
739                                mapping_ctr=pfm)
740
741         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
742
743         found = False
744         for attr in ctr.first_object.object.attribute_ctr.attributes:
745             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
746                 found = True
747                 break
748
749         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
750
751     def test_regular_prefix_map_attid(self):
752         # Request for a regular (non-secret) attid
753         partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
754
755         dc_guid_1 = self.ldb_dc1.get_invocation_id()
756         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
757
758         try:
759             pfm = self._samdb_fetch_pfm_and_schi()
760         except KeyError:
761             # On Windows, prefixMap isn't available over LDAP
762             req8 = self._exop_req8(dest_dsa=None,
763                                    invocation_id=dc_guid_1,
764                                    nc_dn_str=self.user,
765                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
766             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
767             pfm = ctr.mapping_ctr
768
769
770         req8 = self._exop_req8(dest_dsa=None,
771                                invocation_id=dc_guid_1,
772                                nc_dn_str=self.user,
773                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
774                                partial_attribute_set=partial_attribute_set,
775                                mapping_ctr=pfm)
776
777         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
778
779         found = False
780         for attr in ctr.first_object.object.attribute_ctr.attributes:
781             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
782                 found = True
783                 break
784
785         self.assertTrue(found, "Ensure we get the name attribute back")
786
787         for i, mapping in enumerate(pfm.mappings):
788             # OID: 2.5.4.*
789             # objectClass: 2.5.4.0
790             if mapping.oid.binary_oid == [85, 4]:
791                 idx1 = i
792             # OID: 1.2.840.113556.1.4.*
793             # name: 1.2.840.113556.1.4.1
794             elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
795                 idx2 = i
796
797         (pfm.mappings[idx1].id_prefix,
798          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
799                                           pfm.mappings[idx1].id_prefix)
800
801         tmp = pfm.mappings
802         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
803         pfm.mappings = tmp
804
805         # 1 for name (with new prefix = 0)
806         partial_attribute_set = self.get_partial_attribute_set([1])
807         req8 = self._exop_req8(dest_dsa=None,
808                                invocation_id=dc_guid_1,
809                                nc_dn_str=self.user,
810                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
811                                partial_attribute_set=partial_attribute_set,
812                                mapping_ctr=pfm)
813
814         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
815
816         found = False
817         for attr in ctr.first_object.object.attribute_ctr.attributes:
818             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
819                 found = True
820                 break
821
822         self.assertTrue(found, "Ensure we get the name attribute back")
823
824     def test_regular_prefix_map_ex_attid(self):
825         # Request for a regular (non-secret) attid
826         partial_attribute_set = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_name])
827         partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
828
829         dc_guid_1 = self.ldb_dc1.get_invocation_id()
830         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
831
832         try:
833             pfm = self._samdb_fetch_pfm_and_schi()
834         except KeyError:
835             # On Windows, prefixMap isn't available over LDAP
836             req8 = self._exop_req8(dest_dsa=None,
837                                    invocation_id=dc_guid_1,
838                                    nc_dn_str=self.user,
839                                    exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
840             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
841             pfm = ctr.mapping_ctr
842
843
844         req8 = self._exop_req8(dest_dsa=None,
845                                invocation_id=dc_guid_1,
846                                nc_dn_str=self.user,
847                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
848                                partial_attribute_set=partial_attribute_set,
849                                partial_attribute_set_ex=partial_attribute_set_ex,
850                                mapping_ctr=pfm)
851
852         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
853
854         found = False
855         for attr in ctr.first_object.object.attribute_ctr.attributes:
856             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
857                 found = True
858                 break
859
860         self.assertTrue(found, "Ensure we get the name attribute back")
861
862         found = False
863         for attr in ctr.first_object.object.attribute_ctr.attributes:
864             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
865                 found = True
866                 break
867
868         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
869
870         for i, mapping in enumerate(pfm.mappings):
871             # OID: 2.5.4.*
872             # objectClass: 2.5.4.0
873             if mapping.oid.binary_oid == [85, 4]:
874                 idx1 = i
875             # OID: 1.2.840.113556.1.4.*
876             # name: 1.2.840.113556.1.4.1
877             # unicodePwd: 1.2.840.113556.1.4.90
878             elif mapping.oid.binary_oid == [42, 134, 72, 134, 247, 20, 1, 4]:
879                 idx2 = i
880
881         (pfm.mappings[idx1].id_prefix,
882          pfm.mappings[idx2].id_prefix) = (pfm.mappings[idx2].id_prefix,
883                                           pfm.mappings[idx1].id_prefix)
884
885         tmp = pfm.mappings
886         tmp[idx1], tmp[idx2] = tmp[idx2], tmp[idx1]
887         pfm.mappings = tmp
888
889         # 1 for name (with new prefix = 0)
890         partial_attribute_set = self.get_partial_attribute_set([1])
891         # 90 for unicodePwd (with new prefix = 0)
892         # HOWEVER: Windows doesn't seem to respect incoming maps for PartialAttrSetEx
893         partial_attribute_set_ex = self.get_partial_attribute_set([drsuapi.DRSUAPI_ATTID_unicodePwd])
894         req8 = self._exop_req8(dest_dsa=None,
895                                invocation_id=dc_guid_1,
896                                nc_dn_str=self.user,
897                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
898                                partial_attribute_set=partial_attribute_set,
899                                partial_attribute_set_ex=partial_attribute_set_ex,
900                                mapping_ctr=pfm)
901
902         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
903
904         found = False
905         for attr in ctr.first_object.object.attribute_ctr.attributes:
906             if attr.attid == drsuapi.DRSUAPI_ATTID_name:
907                 found = True
908                 break
909
910         self.assertTrue(found, "Ensure we get the name attribute back")
911
912         found = False
913         for attr in ctr.first_object.object.attribute_ctr.attributes:
914             if attr.attid == drsuapi.DRSUAPI_ATTID_unicodePwd:
915                 found = True
916                 break
917
918         self.assertTrue(found, "Ensure we get the unicodePwd attribute back")
919
920     def _samdb_fetch_pfm_and_schi(self):
921         """Fetch prefixMap and schemaInfo stored in SamDB using LDB connection"""
922         samdb = self.ldb_dc1
923         res = samdb.search(base=samdb.get_schema_basedn(), scope=SCOPE_BASE,
924                            attrs=["prefixMap", "schemaInfo"])
925
926         pfm = ndr_unpack(drsblobs.prefixMapBlob,
927                          str(res[0]['prefixMap']))
928
929         schi = drsuapi.DsReplicaOIDMapping()
930         schi.id_prefix = 0
931         if 'schemaInfo' in res[0]:
932             binary_oid = [x if isinstance(x, int) else ord(x) for x in res[0]['schemaInfo'][0]]
933             schi.oid.length = len(binary_oid)
934             schi.oid.binary_oid = binary_oid
935         else:
936             schema_info = drsblobs.schemaInfoBlob()
937             schema_info.revision = 0
938             schema_info.marker = 0xFF
939             schema_info.invocation_id = misc.GUID(samdb.get_invocation_id())
940
941             binary_oid = [x if isinstance(x, int) else ord(x) for x in ndr_pack(schema_info)]
942             # you have to set the length before setting binary_oid
943             schi.oid.length = len(binary_oid)
944             schi.oid.binary_oid = binary_oid
945
946         pfm.ctr.mappings = pfm.ctr.mappings + [schi]
947         pfm.ctr.num_mappings += 1
948         return pfm.ctr
949
950
951 class DrsReplicaSyncSortTestCase(drs_base.DrsBaseTestCase):
952     def setUp(self):
953         super(DrsReplicaSyncSortTestCase, self).setUp()
954         self.base_dn = self.ldb_dc1.get_default_basedn()
955         self.ou = "ou=sort_exop,%s" % self.base_dn
956         self.ldb_dc1.add({
957             "dn": self.ou,
958             "objectclass": "organizationalUnit"})
959
960     def tearDown(self):
961         super(DrsReplicaSyncSortTestCase, self).tearDown()
962         # tidyup groups and users
963         try:
964             self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
965         except ldb.LdbError as e4:
966             (enum, string) = e4.args
967             if enum == ldb.ERR_NO_SUCH_OBJECT:
968                 pass
969
970     def add_linked_attribute(self, src, dest, attr='member'):
971         m = ldb.Message()
972         m.dn = ldb.Dn(self.ldb_dc1, src)
973         m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_ADD, attr)
974         self.ldb_dc1.modify(m)
975
976     def remove_linked_attribute(self, src, dest, attr='member'):
977         m = ldb.Message()
978         m.dn = ldb.Dn(self.ldb_dc1, src)
979         m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_DELETE, attr)
980         self.ldb_dc1.modify(m)
981
982     def test_sort_behaviour_single_object(self):
983         """Testing sorting behaviour on single objects"""
984
985         user1_dn = "cn=test_user1,%s" % self.ou
986         user2_dn = "cn=test_user2,%s" % self.ou
987         user3_dn = "cn=test_user3,%s" % self.ou
988         group_dn = "cn=test_group,%s" % self.ou
989
990         self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
991         self.ldb_dc1.add({"dn": user2_dn, "objectclass": "user"})
992         self.ldb_dc1.add({"dn": user3_dn, "objectclass": "user"})
993         self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
994
995         u1_guid = misc.GUID(self.ldb_dc1.search(base=user1_dn,
996                                                 attrs=["objectGUID"])[0]['objectGUID'][0])
997         u2_guid = misc.GUID(self.ldb_dc1.search(base=user2_dn,
998                                                 attrs=["objectGUID"])[0]['objectGUID'][0])
999         u3_guid = misc.GUID(self.ldb_dc1.search(base=user3_dn,
1000                                                 attrs=["objectGUID"])[0]['objectGUID'][0])
1001         g_guid = misc.GUID(self.ldb_dc1.search(base=group_dn,
1002                                                attrs=["objectGUID"])[0]['objectGUID'][0])
1003
1004         self.add_linked_attribute(group_dn, user1_dn,
1005                                   attr='member')
1006         self.add_linked_attribute(group_dn, user2_dn,
1007                                   attr='member')
1008         self.add_linked_attribute(group_dn, user3_dn,
1009                                   attr='member')
1010         self.add_linked_attribute(group_dn, user1_dn,
1011                                   attr='managedby')
1012         self.add_linked_attribute(group_dn, user2_dn,
1013                                   attr='nonSecurityMember')
1014         self.add_linked_attribute(group_dn, user3_dn,
1015                                   attr='nonSecurityMember')
1016
1017         set_inactive = AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1018                                     drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1019                                     g_guid, u3_guid)
1020
1021         expected_links = set([set_inactive,
1022                               AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1023                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1024                                            g_guid,
1025                                            u1_guid),
1026                               AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1027                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1028                                            g_guid,
1029                                            u2_guid),
1030                               AbstractLink(drsuapi.DRSUAPI_ATTID_member,
1031                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1032                                            g_guid,
1033                                            u3_guid),
1034                               AbstractLink(drsuapi.DRSUAPI_ATTID_managedBy,
1035                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1036                                            g_guid,
1037                                            u1_guid),
1038                               AbstractLink(drsuapi.DRSUAPI_ATTID_nonSecurityMember,
1039                                            drsuapi.DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE,
1040                                            g_guid,
1041                                            u2_guid),
1042         ])
1043
1044         dc_guid_1 = self.ldb_dc1.get_invocation_id()
1045
1046         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1047
1048         req8 = self._exop_req8(dest_dsa=None,
1049                                invocation_id=dc_guid_1,
1050                                nc_dn_str=group_dn,
1051                                exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ)
1052
1053         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1054
1055         no_inactive = []
1056         for link in ctr.linked_attributes:
1057             target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1058                                      link.value.blob).guid
1059             no_inactive.append((link, target_guid))
1060             self.assertTrue(AbstractLink(link.attid, link.flags,
1061                                          link.identifier.guid,
1062                                          target_guid) in expected_links)
1063
1064         no_inactive.sort(key=cmp_to_key_fn(_linked_attribute_compare))
1065
1066         # assert the two arrays are the same
1067         self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1068         self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)
1069
1070         self.remove_linked_attribute(group_dn, user3_dn,
1071                                      attr='nonSecurityMember')
1072
1073         # Set the link inactive
1074         expected_links.remove(set_inactive)
1075         set_inactive.flags = 0
1076         expected_links.add(set_inactive)
1077
1078         has_inactive = []
1079         (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1080         for link in ctr.linked_attributes:
1081             target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1082                                      link.value.blob).guid
1083             has_inactive.append((link, target_guid))
1084             self.assertTrue(AbstractLink(link.attid, link.flags,
1085                                          link.identifier.guid,
1086                                          target_guid) in expected_links)
1087
1088         has_inactive.sort(key=cmp_to_key_fn(_linked_attribute_compare))
1089
1090         # assert the two arrays are the same
1091         self.assertEqual(len(expected_links), ctr.linked_attributes_count)
1092         self.assertEqual([x[0] for x in has_inactive], ctr.linked_attributes)
1093
1094     def test_sort_behaviour_ncchanges(self):
1095         """Testing sorting behaviour on a group of objects."""
1096         user1_dn = "cn=test_user1,%s" % self.ou
1097         group_dn = "cn=test_group,%s" % self.ou
1098         self.ldb_dc1.add({"dn": user1_dn, "objectclass": "user"})
1099         self.ldb_dc1.add({"dn": group_dn, "objectclass": "group"})
1100
1101         self.add_linked_attribute(group_dn, user1_dn,
1102                                   attr='member')
1103
1104         dc_guid_1 = self.ldb_dc1.get_invocation_id()
1105
1106         drs, drs_handle = self._ds_bind(self.dnsname_dc1)
1107
1108         # Make sure the max objects count is high enough
1109         req8 = self._exop_req8(dest_dsa=None,
1110                                invocation_id=dc_guid_1,
1111                                nc_dn_str=self.base_dn,
1112                                replica_flags=0,
1113                                max_objects=100,
1114                                exop=drsuapi.DRSUAPI_EXOP_NONE)
1115
1116         # Loop until we get linked attributes, or we get to the end.
1117         # Samba sends linked attributes at the end, unlike Windows.
1118         while True:
1119             (level, ctr) = drs.DsGetNCChanges(drs_handle, 8, req8)
1120             if ctr.more_data == 0 or ctr.linked_attributes_count != 0:
1121                 break
1122             req8.highwatermark = ctr.new_highwatermark
1123
1124         self.assertTrue(ctr.linked_attributes_count != 0)
1125
1126         no_inactive = []
1127         for link in ctr.linked_attributes:
1128             try:
1129                 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
1130                                          link.value.blob).guid
1131             except:
1132                 target_guid = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
1133                                          link.value.blob).guid
1134             no_inactive.append((link, target_guid))
1135
1136         no_inactive.sort(key=cmp_to_key_fn(_linked_attribute_compare))
1137
1138         # assert the two arrays are the same
1139         self.assertEqual([x[0] for x in no_inactive], ctr.linked_attributes)