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