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