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