ctdb-scripts: Improve update and listing code
[samba4-gss.git] / python / samba / tests / ldap_spn.py
blob6ebdf8f9a32d4d737944fd4850d41e43e791013c
1 # Unix SMB/CIFS implementation.
3 # Copyright 2021 (C) Catalyst IT Ltd
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys
20 import os
21 import pprint
22 import re
23 from samba.samdb import SamDB
24 from samba.auth import system_session
25 import ldb
26 from samba.sd_utils import SDUtils
27 from samba.credentials import DONT_USE_KERBEROS, Credentials
28 from samba.gensec import FEATURE_SEAL
29 from samba.tests.subunitrun import SubunitOptions, TestProgram
30 from samba.tests import TestCase, ldb_err
31 from samba.tests import DynamicTestCase
32 import samba.getopt as options
33 import optparse
34 from samba.colour import c_RED, c_GREEN, c_DARK_YELLOW
35 from samba.dsdb import (
36 UF_SERVER_TRUST_ACCOUNT,
37 UF_TRUSTED_FOR_DELEGATION,
41 SPN_GUID = 'f3a64788-5306-11d1-a9c5-0000f80367c1'
43 RELEVANT_ATTRS = {'dNSHostName',
44 'servicePrincipalName',
45 'sAMAccountName',
46 'dn'}
48 ok = True
49 bad = False
50 report = 'report'
52 operr = ldb.ERR_OPERATIONS_ERROR
53 denied = ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS
54 constraint = ldb.ERR_CONSTRAINT_VIOLATION
55 exists = ldb.ERR_ENTRY_ALREADY_EXISTS
57 add = ldb.FLAG_MOD_ADD
58 replace = ldb.FLAG_MOD_REPLACE
59 delete = ldb.FLAG_MOD_DELETE
61 try:
62 breakpoint
63 except NameError:
64 # for python <= 3.6
65 def breakpoint():
66 import pdb
67 pdb.set_trace()
70 def init():
71 # This needs to happen before the class definition, and we put it
72 # in a function to keep the namespace clean.
73 global LP, CREDS, SERVER, REALM, COLOUR_TEXT, subunitopts, FILTER
75 parser = optparse.OptionParser(
76 "python3 ldap_spn.py <server> [options]")
77 sambaopts = options.SambaOptions(parser)
78 parser.add_option_group(sambaopts)
80 # use command line creds if available
81 credopts = options.CredentialsOptions(parser)
82 parser.add_option_group(credopts)
83 subunitopts = SubunitOptions(parser)
84 parser.add_option_group(subunitopts)
86 parser.add_option('--colour', action="store_true",
87 help="use colour text",
88 default=sys.stdout.isatty())
90 parser.add_option('--filter', help="only run tests matching this regex")
92 opts, args = parser.parse_args()
93 if len(args) != 1:
94 parser.print_usage()
95 sys.exit(1)
97 LP = sambaopts.get_loadparm()
98 CREDS = credopts.get_credentials(LP)
99 SERVER = args[0]
100 REALM = CREDS.get_realm()
101 COLOUR_TEXT = opts.colour
102 FILTER = opts.filter
105 init()
108 def colour_text(x, state=None):
109 if not COLOUR_TEXT:
110 return x
111 if state == 'error':
112 return c_RED(x)
113 if state == 'pass':
114 return c_GREEN(x)
116 return c_DARK_YELLOW(x)
119 def get_samdb(creds=None):
120 if creds is None:
121 creds = CREDS
122 session = system_session()
123 else:
124 session = None
126 return SamDB(url=f"ldap://{SERVER}",
127 lp=LP,
128 session_info=session,
129 credentials=creds)
132 def add_unpriv_user(samdb, ou, username,
133 writeable_objects=None,
134 password="samba123@"):
135 creds = Credentials()
136 creds.set_username(username)
137 creds.set_password(password)
138 creds.set_domain(CREDS.get_domain())
139 creds.set_realm(CREDS.get_realm())
140 creds.set_workstation(CREDS.get_workstation())
141 creds.set_gensec_features(CREDS.get_gensec_features() | FEATURE_SEAL)
142 creds.set_kerberos_state(DONT_USE_KERBEROS)
143 dnstr = f"CN={username},{ou}"
145 # like, WTF, samdb.newuser(), this is what you make us do.
146 short_ou = ou.split(',', 1)[0]
148 samdb.newuser(username, password, userou=short_ou)
150 if writeable_objects:
151 sd_utils = SDUtils(samdb)
152 sid = sd_utils.get_object_sid(dnstr)
153 for obj in writeable_objects:
154 mod = f"(OA;CI;WP;{ SPN_GUID };;{ sid })"
155 sd_utils.dacl_add_ace(obj, mod)
157 unpriv_samdb = get_samdb(creds=creds)
158 return unpriv_samdb
161 class LdapSpnTestBase(TestCase):
162 _disabled = False
164 @classmethod
165 def setUpDynamicTestCases(cls):
166 if getattr(cls, '_disabled', False):
167 return
168 for doc, *rows in cls.cases:
169 if FILTER:
170 if not re.search(FILTER, doc):
171 continue
172 name = re.sub(r'\W+', '_', doc)
173 cls.generate_dynamic_test("test_spn", name, rows, doc)
175 def setup_objects(self, rows):
176 objects = set(r[0] for r in rows)
177 for name in objects:
178 if ':' in name:
179 objtype, name = name.split(':', 1)
180 else:
181 objtype = 'dc'
182 getattr(self, f'add_{objtype}')(name)
184 def setup_users(self, rows):
185 # When you are adding an SPN that aliases (or would be aliased
186 # by) another SPN on another object, you need to have write
187 # permission on that other object too.
189 # To test this negatively and positively, we need to have
190 # users with various combinations of write permission, which
191 # means fiddling with SDs on the objects.
193 # The syntax is:
194 # '' : user with no special permissions
195 # '*' : admin user
196 # 'A' : user can write to A only
197 # 'A,C' : user can write to A and C
198 # 'C,A' : same, but makes another user
199 self.userdbs = {
200 '*': self.samdb
203 permissions = set(r[2] for r in rows)
204 for p in permissions:
205 if p == '*':
206 continue
207 if p == '':
208 user = 'nobody'
209 writeable_objects = None
210 else:
211 user = 'writes_' + p.replace(",", '_')
212 writeable_objects = [self.objects[x][0] for x in p.split(',')]
214 self.userdbs[p] = add_unpriv_user(self.samdb, self.ou, user,
215 writeable_objects)
217 def _test_spn_with_args(self, rows, doc):
218 cdoc = colour_text(doc)
219 edoc = colour_text(doc, 'error')
220 pdoc = colour_text(doc, 'pass')
222 if COLOUR_TEXT:
223 sys.stderr.flush()
224 print('\n', c_DARK_YELLOW('#' * 10), f'starting «{cdoc}»\n')
225 sys.stdout.flush()
227 self.samdb = get_samdb()
228 self.base_dn = self.samdb.get_default_basedn()
229 self.short_id = self.id().rsplit('.', 1)[1][:63]
230 self.objects = {}
231 self.ou = f"OU={ self.short_id },{ self.base_dn }"
232 self.addCleanup(self.samdb.delete, self.ou, ["tree_delete:1"])
233 self.samdb.create_ou(self.ou)
235 self.setup_objects(rows)
236 self.setup_users(rows)
238 for i, row in enumerate(rows):
239 if len(row) == 5:
240 obj, data, rights, expected, op = row
241 else:
242 obj, data, rights, expected = row
243 op = ldb.FLAG_MOD_REPLACE
245 # We use this DB with possibly restricted rights for this row
246 samdb = self.userdbs[rights]
248 if ':' in obj:
249 objtype, obj = obj.split(':', 1)
250 else:
251 objtype = 'dc'
253 dn, dnsname = self.objects[obj]
254 m = {"dn": dn}
256 if isinstance(data, dict):
257 m.update(data)
258 else:
259 m['servicePrincipalName'] = data
261 # for python's sake (and our sanity) we try to ensure we
262 # have consistent canonical case in our attributes
263 keys = set(m.keys())
264 if not keys.issubset(RELEVANT_ATTRS):
265 raise ValueError(f"unexpected attr {keys - RELEVANT_ATTRS}. "
266 "Casefold typo?")
268 for k in ('dNSHostName', 'servicePrincipalName'):
269 if isinstance(m.get(k), str):
270 m[k] = m[k].format(dnsname=f"x.{REALM}")
271 elif isinstance(m.get(k), list):
272 m[k] = [x.format(dnsname=f"x.{REALM}") for x in m[k]]
274 msg = ldb.Message.from_dict(samdb, m, op)
276 if expected is bad:
277 try:
278 samdb.modify(msg)
279 except ldb.LdbError as e:
280 print(f"row {i+1} of '{pdoc}' failed as expected with "
281 f"{ldb_err(e)}\n")
282 continue
283 self.fail(f"row {i+1}: "
284 f"{rights} {pprint.pformat(m)} on {objtype} {obj} "
285 f"should fail ({edoc})")
287 elif expected is ok:
288 try:
289 samdb.modify(msg)
290 except ldb.LdbError as e:
291 self.fail(f"row {i+1} of {edoc} failed with {ldb_err(e)}:\n"
292 f"{rights} {pprint.pformat(m)} on {objtype} {obj}")
294 elif expected is report:
295 try:
296 self.samdb.modify(msg)
297 print(f"row {i+1} "
298 f"of '{cdoc}' {colour_text('SUCCEEDED', 'pass')}:\n"
299 f"{pprint.pformat(m)} on {obj}")
300 except ldb.LdbError as e:
301 print(f"row {i+1} "
302 f"of '{cdoc}' {colour_text('FAILED', 'error')} "
303 f"with {ldb_err(e)}:\n{pprint.pformat(m)} on {obj}")
305 elif expected is breakpoint:
306 try:
307 breakpoint()
308 samdb.modify(msg)
309 except ldb.LdbError as e:
310 print(f"row {i+1} of '{pdoc}' FAILED with {ldb_err(e)}\n")
312 else: # an ldb error number
313 try:
314 samdb.modify(msg)
315 except ldb.LdbError as e:
316 if e.args[0] == expected:
317 continue
318 self.fail(f"row {i+1} of '{edoc}' "
319 f"should have failed with {ldb_err(expected)}:\n"
320 f"not {ldb_err(e)}:\n"
321 f"{rights} {pprint.pformat(m)} on {objtype} {obj}")
322 self.fail(f"row {i+1} of '{edoc}' "
323 f"should have failed with {ldb_err(expected)}:\n"
324 f"{rights} {pprint.pformat(m)} on {objtype} {obj}")
326 def add_dc(self, name):
327 dn = f"CN={name},OU=Domain Controllers,{self.base_dn}"
328 dnsname = f"{name}.{REALM}".lower()
329 self.samdb.add({
330 "dn": dn,
331 "objectclass": "computer",
332 "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT |
333 UF_TRUSTED_FOR_DELEGATION),
334 "dnsHostName": dnsname,
335 "carLicense": self.id()
337 self.addCleanup(self.remove_object, name)
338 self.objects[name] = (dn, dnsname)
340 def add_user(self, name):
341 dn = f"CN={name},{self.ou}"
342 self.samdb.add({
343 "dn": dn,
344 "name": name,
345 "samAccountName": name,
346 "objectclass": "user",
347 "carLicense": self.id()
349 self.addCleanup(self.remove_object, name)
350 self.objects[name] = (dn, None)
352 def remove_object(self, name):
353 dn, dnsname = self.objects.pop(name)
354 self.samdb.delete(dn)
357 @DynamicTestCase
358 class LdapSpnTest(LdapSpnTestBase):
359 """Make sure we can't add clashing servicePrincipalNames.
361 This would be possible using sPNMappings aliases — for example, if
362 the mapping maps host/ to cifs/, we should not be able to add
363 different addresses for each.
366 # default sPNMappings: host=alerter, appmgmt, cisvc, clipsrv,
367 # browser, dhcp, dnscache, replicator, eventlog, eventsystem,
368 # policyagent, oakley, dmserver, dns, mcsvc, fax, msiserver, ias,
369 # messenger, netlogon, netman, netdde, netddedsm, nmagent,
370 # plugplay, protectedstorage, rasman, rpclocator, rpc, rpcss,
371 # remoteaccess, rsvp, samss, scardsvr, scesrv, seclogon, scm,
372 # dcom, cifs, spooler, snmp, schedule, tapisrv, trksvr, trkwks,
373 # ups, time, wins, www, http, w3svc, iisadmin, msdtc
375 # I think in practice this is rarely if ever changed or added to.
377 cases = [
378 ("add one as admin",
379 ('A', 'host/{dnsname}', '*', ok),
381 ("add one as rightful user",
382 ('A', 'host/{dnsname}', 'A', ok),
384 ("attempt to add one as nobody",
385 ('A', 'host/{dnsname}', '', denied),
388 ("add and replace as admin",
389 ('A', 'host/{dnsname}', '*', ok),
390 ('A', 'host/x.{dnsname}', '*', ok),
392 ("replace as rightful user",
393 ('A', 'host/{dnsname}', 'A', ok),
394 ('A', 'host/x.{dnsname}', 'A', ok),
396 ("attempt to replace one as nobody",
397 ('A', 'host/{dnsname}', '*', ok),
398 ('A', 'host/x.{dnsname}', '', denied),
401 ("add second as admin",
402 ('A', 'host/{dnsname}', '*', ok),
403 ('A', 'host/x.{dnsname}', '*', ok, add),
405 ("add second as rightful user",
406 ('A', 'host/{dnsname}', 'A', ok),
407 ('A', 'host/x.{dnsname}', 'A', ok, add),
409 ("attempt to add second as nobody",
410 ('A', 'host/{dnsname}', '*', ok),
411 ('A', 'host/x.{dnsname}', '', denied, add),
414 ("add the same one twice, simple duplicate error",
415 ('A', 'host/{dnsname}', '*', ok),
416 ('A', 'host/{dnsname}', '*', bad, add),
418 ("simple duplicate attributes, as non-admin",
419 ('A', 'host/{dnsname}', '*', ok),
420 ('A', 'host/{dnsname}', 'A', bad, add),
423 ("add the same one twice, identical duplicate",
424 ('A', 'host/{dnsname}', '*', ok),
425 ('A', 'host/{dnsname}', '*', bad, add),
428 ("add a conflict, host first, as nobody",
429 ('A', 'host/z.{dnsname}', '*', ok),
430 ('B', 'cifs/z.{dnsname}', '', denied),
433 ("add a conflict, service first, as nobody",
434 ('A', 'cifs/{dnsname}', '*', ok),
435 ('B', 'host/{dnsname}', '', denied),
439 ("three way conflict, host first, as admin",
440 ('A', 'host/z.{dnsname}', '*', ok),
441 ('B', 'cifs/z.{dnsname}', '*', ok),
442 ('C', 'www/z.{dnsname}', '*', ok),
444 ("three way conflict, host first, with sufficient rights",
445 ('A', 'host/z.{dnsname}', 'A', ok),
446 ('B', 'cifs/z.{dnsname}', 'B,A', ok),
447 ('C', 'www/z.{dnsname}', 'C,A', ok),
449 ("three way conflict, host first, adding duplicate",
450 ('A', 'host/z.{dnsname}', 'A', ok),
451 ('B', 'cifs/z.{dnsname}', 'B,A', ok),
452 ('C', 'cifs/z.{dnsname}', 'C,A', bad),
454 ("three way conflict, host first, adding duplicate, full rights",
455 ('A', 'host/z.{dnsname}', 'A', ok),
456 ('B', 'cifs/z.{dnsname}', 'B,A', ok),
457 ('C', 'cifs/z.{dnsname}', 'C,B,A', bad),
460 ("three way conflict, host first, with other write rights",
461 ('A', 'host/z.{dnsname}', '*', ok),
462 ('B', 'cifs/z.{dnsname}', 'A,B', ok),
463 ('C', 'cifs/z.{dnsname}', 'A,B', bad),
466 ("three way conflict, host first, as nobody",
467 ('A', 'host/z.{dnsname}', '*', ok),
468 ('B', 'cifs/z.{dnsname}', '*', ok),
469 ('C', 'www/z.{dnsname}', '', denied),
472 ("three way conflict, services first, as admin",
473 ('A', 'cifs/{dnsname}', '*', ok),
474 ('B', 'www/{dnsname}', '*', ok),
475 ('C', 'host/{dnsname}', '*', constraint),
477 ("three way conflict, services first, with service write rights",
478 ('A', 'cifs/{dnsname}', '*', ok),
479 ('B', 'www/{dnsname}', '*', ok),
480 ('C', 'host/{dnsname}', 'A,B', bad),
483 ("three way conflict, service first, as nobody",
484 ('A', 'cifs/{dnsname}', '*', ok),
485 ('B', 'www/{dnsname}', '*', ok),
486 ('C', 'host/{dnsname}', '', denied),
488 ("replace host before specific",
489 ('A', 'host/{dnsname}', '*', ok),
490 ('A', 'cifs/{dnsname}', '*', ok),
492 ("replace host after specific, as nobody",
493 ('A', 'cifs/{dnsname}', '*', ok),
494 ('A', 'host/{dnsname}', '', denied),
497 ("non-conflict host before specific",
498 ('A', 'host/{dnsname}', '*', ok),
499 ('A', 'cifs/{dnsname}', '*', ok, add),
501 ("non-conflict host after specific",
502 ('A', 'cifs/{dnsname}', '*', ok),
503 ('A', 'host/{dnsname}', '*', ok, add),
505 ("non-conflict host before specific, non-admin",
506 ('A', 'host/{dnsname}', 'A', ok),
507 ('A', 'cifs/{dnsname}', 'A', ok, add),
509 ("non-conflict host after specific, as nobody",
510 ('A', 'cifs/{dnsname}', '*', ok),
511 ('A', 'host/{dnsname}', '', denied, add),
514 ("add a conflict, host first on user, as admin",
515 ('user:C', 'host/{dnsname}', '*', ok),
516 ('B', 'cifs/{dnsname}', '*', ok),
518 ("add a conflict, host first on user, host rights",
519 ('user:C', 'host/{dnsname}', '*', ok),
520 ('B', 'cifs/{dnsname}', 'C', denied),
522 ("add a conflict, host first on user, both rights",
523 ('user:C', 'host/{dnsname}', '*', ok),
524 ('B', 'cifs/{dnsname}', 'B,C', ok),
526 ("add a conflict, host first both on user",
527 ('user:C', 'host/{dnsname}', '*', ok),
528 ('user:D', 'www/{dnsname}', '*', ok),
530 ("add a conflict, host first both on user, host rights",
531 ('user:C', 'host/{dnsname}', '*', ok),
532 ('user:D', 'www/{dnsname}', 'C', denied),
534 ("add a conflict, host first both on user, both rights",
535 ('user:C', 'host/{dnsname}', '*', ok),
536 ('user:D', 'www/{dnsname}', 'C,D', ok),
538 ("add a conflict, host first both on user, as nobody",
539 ('user:C', 'host/{dnsname}', '*', ok),
540 ('user:D', 'www/{dnsname}', '', denied),
542 ("add a conflict, host first, with both write rights",
543 ('A', 'host/z.{dnsname}', '*', ok),
544 ('B', 'cifs/z.{dnsname}', 'A,B', ok),
547 ("add a conflict, host first, second on user, as admin",
548 ('A', 'host/{dnsname}', '*', ok),
549 ('user:D', 'cifs/{dnsname}', '*', ok),
551 ("add a conflict, host first, second on user, with rights",
552 ('A', 'host/{dnsname}', '*', ok),
553 ('user:D', 'cifs/{dnsname}', 'A,D', ok),
556 ("nonsense SPNs, part 1, as admin",
557 ('A', 'a-b-c/{dnsname}', '*', ok),
558 ('A', 'rrrrrrrrrrrrr /{dnsname}', '*', ok),
560 ("nonsense SPNs, part 1, as user",
561 ('A', 'a-b-c/{dnsname}', 'A', ok),
562 ('A', 'rrrrrrrrrrrrr /{dnsname}', 'A', ok),
564 ("nonsense SPNs, part 1, as nobody",
565 ('A', 'a-b-c/{dnsname}', '', denied),
566 ('A', 'rrrrrrrrrrrrr /{dnsname}', '', denied),
569 ("add a conflict, using port",
570 ('A', 'dns/{dnsname}', '*', ok),
571 ('B', 'dns/{dnsname}:53', '*', ok),
573 ("add a conflict, using port, port first",
574 ('user:C', 'dns/{dnsname}:53', '*', ok),
575 ('user:D', 'dns/{dnsname}', '*', ok),
577 ("three part spns",
578 ('A', {'dNSHostName': '{dnsname}'}, '*', ok),
579 ('A', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok),
580 ('B', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', constraint),
581 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok),
582 ('B', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok),
583 ('B', 'cifs/y.{dnsname}/DomainDNSZones.{dnsname}', '*', constraint),
585 ("three part nonsense spns",
586 ('A', {'dNSHostName': 'bean'}, '*', ok),
587 ('A', 'cifs/bean/DomainDNSZones.bean', '*', ok),
588 ('B', 'cifs/bean/DomainDNSZones.bean', '*', constraint),
589 ('A', {'dNSHostName': 'y.bean'}, '*', ok),
590 ('B', 'cifs/bean/DomainDNSZones.bean', '*', ok),
591 ('B', 'cifs/y.bean/DomainDNSZones.bean', '*', constraint),
592 ('C', 'host/bean/bean', '*', ok),
595 ("one part spns (no slashes)",
596 ('A', '{dnsname}', '*', constraint),
597 ('B', 'cifs', '*', constraint),
598 ('B', 'cifs/', '*', ok),
599 ('B', ' ', '*', constraint),
600 ('user:C', 'host', '*', constraint),
603 ("dodgy spns",
604 # These tests pass on Windows. An SPN must have one or two
605 # slashes, with at least one character before the first one,
606 # UNLESS the first slash is followed by a good enough service
607 # name (e.g. "/host/x.y" rather than "sdfsd/x.y").
608 ('A', '\\/{dnsname}', '*', ok),
609 ('B', 'cifs/\\\\{dnsname}', '*', ok),
610 ('B', r'cifs/\\\{dnsname}', '*', ok),
611 ('B', r'cifs/\\\{dnsname}/', '*', ok),
612 ('A', r'cīfs/\\\{dnsname}/', '*', constraint), # 'ī' maps to 'i'
613 # on the next two, full-width solidus (U+FF0F) does not work
614 # as '/'.
615 ('A', 'cifs/sfic', '*', constraint, add),
616 ('A', r'cifs/\\\{dnsname}', '*', constraint, add),
617 ('B', '\n', '*', constraint),
618 ('B', '\n/\n', '*', ok),
619 ('B', '\n/\n/\n', '*', ok),
620 ('B', '\n/\n/\n/\n', '*', constraint),
621 ('B', ' /* and so on */ ', '*', ok, add),
622 ('B', r'¯\_(ツ)_/¯', '*', ok, add), # ¯\_(ツ)_/¯
623 # つ is hiragana for katakana ツ, so the next one fails for
624 # something analogous to casefold reasons.
625 ('A', r'¯\_(つ)_/¯', '*', constraint),
626 ('A', r'¯\_(㋡)_/¯', '*', constraint), # circled ツ
627 ('B', '//', '*', constraint), # all can't be empty,
628 ('B', ' //', '*', ok), # service can be space
629 ('B', '/host/{dnsname}', '*', ok), # or empty if others aren't
630 ('B', '/host/x.y.z', '*', ok),
631 ('B', '/ /x.y.z', '*', ok),
632 ('B', ' / / ', '*', ok),
633 ('user:C', b'host/', '*', ok),
634 ('user:C', ' /host', '*', ok), # service is ' ' (space)
635 ('B', ' /host', '*', constraint), # already on C
636 ('B', ' /HōST', '*', constraint), # ō equiv to O
637 ('B', ' /ħØşt', '*', constraint), # maps to ' /host'
638 ('B', ' /H0ST', '*', ok), # 0 is zero
639 ('B', ' /НoST', '*', ok), # Cyrillic Н (~N)
640 ('B', ' /host', '*', ok), # two space
641 ('B', '\u00a0/host', '*', ok), # non-breaking space
642 ('B', ' 2/HōST/⌷[ ][]¨(', '*', ok),
643 ('B', ' (//)', '*', ok, add),
644 ('B', ' ///', '*', constraint),
645 ('B', r' /\//', '*', constraint), # escape doesn't help
646 ('B', ' /\\//', '*', constraint), # double escape doesn't help
647 ('B', r'\//', '*', ok),
648 ('A', r'\\/\\/', '*', ok),
649 ('B', '|//|', '*', ok, add),
650 ('B', r'\/\/\\', '*', ok, add),
652 ('A', ':', '*', constraint),
653 ('A', ':/:', '*', ok),
654 ('A', ':/:80', '*', ok), # port number syntax is not special
655 ('A', ':/:( ツ', '*', ok),
656 ('A', ':/:/:', '*', ok),
657 ('B', b'cifs/\x11\xaa\xbb\xcc\\example.com', '*', ok),
658 ('A', b':/\xcc\xcc\xcc\xcc', '*', ok),
659 ('A', b':/b\x00/b/b/b', '*', ok), # string handlng truncates at \x00
660 ('A', b'a@b/a@b/a@b', '*', ok),
661 ('A', b'a/a@b/a@b', '*', ok),
663 ("empty part spns (consecutive slashes)",
664 ('A', 'cifs//{dnsname}', '*', ok),
665 ('B', 'cifs//{dnsname}', '*', bad), # should clash with line 1
666 ('B', 'cifs/zzzy.{dnsname}/', '*', ok),
667 ('B', '/host/zzzy.{dnsname}', '*', ok),
669 ("too many spn parts",
670 ('A', 'cifs/{dnsname}/{dnsname}/{dnsname}', '*', bad),
671 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok),
672 ('B', 'cifs/{dnsname}/{dnsname}/', '*', bad),
673 ('B', 'cifs/y.{dnsname}/{dnsname}/toop', '*', bad),
674 ('B', 'host/{dnsname}/a/b/c', '*', bad),
676 ("add a conflict, host first, as admin",
677 ('A', 'host/z.{dnsname}', '*', ok),
678 ('B', 'cifs/z.{dnsname}', '*', ok),
680 ("add a conflict, host first, with host write rights",
681 ('A', 'host/z.{dnsname}', '*', ok),
682 ('B', 'cifs/z.{dnsname}', 'A', denied),
684 ("add a conflict, service first, with service write rights",
685 ('A', 'cifs/{dnsname}', '*', ok),
686 ('B', 'host/{dnsname}', 'A', denied),
688 ("adding dNSHostName after cifs with no old dNSHostName",
689 ('A', 'cifs/{dnsname}', '*', ok),
690 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok),
691 ('B', 'cifs/{dnsname}', '*', constraint),
692 ('B', 'cifs/y.{dnsname}', '*', ok),
693 ('B', 'host/y.{dnsname}', '*', ok),
695 ("changing dNSHostName after cifs",
696 ('A', {'dNSHostName': '{dnsname}'}, '*', ok),
697 ('A', 'cifs/{dnsname}', '*', ok),
698 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok),
699 ('B', 'cifs/{dnsname}', '*', ok),
700 ('B', 'cifs/y.{dnsname}', '*', bad),
701 ('B', 'host/y.{dnsname}', '*', bad),
706 @DynamicTestCase
707 class LdapSpnSambaOnlyTest(LdapSpnTestBase):
708 # We don't run these ones outside of selftest, where we are
709 # probably testing against Windows and these are known failures.
710 _disabled = 'SAMBA_SELFTEST' not in os.environ
711 cases = [
712 ("add a conflict, host first, with service write rights",
713 ('A', 'host/z.{dnsname}', '*', ok),
714 ('B', 'cifs/z.{dnsname}', 'B', denied),
716 ("add a conflict, service first, with host write rights",
717 ('A', 'cifs/{dnsname}', '*', ok),
718 ('B', 'host/{dnsname}', 'B', constraint),
720 ("add a conflict, service first, as admin",
721 ('A', 'cifs/{dnsname}', '*', ok),
722 ('B', 'host/{dnsname}', '*', constraint),
724 ("add a conflict, service first, with both write rights",
725 ('A', 'cifs/{dnsname}', '*', ok),
726 ('B', 'host/{dnsname}', 'A,B', constraint),
728 ("add a conflict, host first both on user, service rights",
729 ('user:C', 'host/{dnsname}', '*', ok),
730 ('user:D', 'www/{dnsname}', 'D', denied),
732 ("add a conflict, along with a re-added SPN",
733 ('A', 'cifs/{dnsname}', '*', ok),
734 ('B', 'cifs/heeble.example.net', 'B', ok),
735 ('B', ['cifs/heeble.example.net', 'host/{dnsname}'], 'B', constraint),
738 ("changing dNSHostName after host",
739 ('A', {'dNSHostName': '{dnsname}'}, '*', ok),
740 ('A', 'host/{dnsname}', '*', ok),
741 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok),
742 ('B', 'cifs/{dnsname}', 'B', ok), # no clash with A
743 ('B', 'cifs/y.{dnsname}', 'B', bad), # should clash with A
744 ('B', 'host/y.{dnsname}', '*', bad),
747 ("mystery dnsname clash, host first",
748 ('user:C', 'host/heeble.example.net', '*', ok),
749 ('user:D', 'www/heeble.example.net', '*', ok),
751 ("mystery dnsname clash, www first",
752 ('user:D', 'www/heeble.example.net', '*', ok),
753 ('user:C', 'host/heeble.example.net', '*', constraint),
755 ("replace as admin",
756 ('A', 'cifs/{dnsname}', '*', ok),
757 ('A', 'host/{dnsname}', '*', ok),
758 ('A', 'cifs/{dnsname}', '*', ok),
760 ("replace as non-admin with rights",
761 ('A', 'cifs/{dnsname}', '*', ok),
762 ('A', 'host/{dnsname}', 'A', ok),
763 ('A', 'cifs/{dnsname}', 'A', ok),
765 ("replace vial delete as non-admin with rights",
766 ('A', 'cifs/{dnsname}', '*', ok),
767 ('A', 'host/{dnsname}', 'A', ok),
768 ('A', 'host/{dnsname}', 'A', ok, delete),
769 ('A', 'cifs/{dnsname}', 'A', ok, add),
771 ("replace as non-admin without rights",
772 ('B', 'cifs/b', '*', ok),
773 ('A', 'cifs/{dnsname}', '*', ok),
774 ('A', 'host/{dnsname}', 'B', denied),
775 ('A', 'cifs/{dnsname}', 'B', denied),
777 ("replace as nobody",
778 ('B', 'cifs/b', '*', ok),
779 ('A', 'cifs/{dnsname}', '*', ok),
780 ('A', 'host/{dnsname}', '', denied),
781 ('A', 'cifs/{dnsname}', '', denied),
783 ("accumulate and delete as admin",
784 ('A', 'cifs/{dnsname}', '*', ok),
785 ('A', 'host/{dnsname}', '*', ok, add),
786 ('A', 'www/{dnsname}', '*', ok, add),
787 ('A', 'www/...', '*', ok, add),
788 ('A', 'host/...', '*', ok, add),
789 ('A', 'www/{dnsname}', '*', ok, delete),
790 ('A', 'host/{dnsname}', '*', ok, delete),
791 ('A', 'host/{dnsname}', '*', ok, add),
792 ('A', 'www/{dnsname}', '*', ok, add),
793 ('A', 'host/...', '*', ok, delete),
795 ("accumulate and delete with user rights",
796 ('A', 'cifs/{dnsname}', '*', ok),
797 ('A', 'host/{dnsname}', 'A', ok, add),
798 ('A', 'www/{dnsname}', 'A', ok, add),
799 ('A', 'www/...', 'A', ok, add),
800 ('A', 'host/...', 'A', ok, add),
801 ('A', 'www/{dnsname}', 'A', ok, delete),
802 ('A', 'host/{dnsname}', 'A', ok, delete),
803 ('A', 'host/{dnsname}', 'A', ok, add),
804 ('A', 'www/{dnsname}', 'A', ok, add),
805 ('A', 'host/...', 'A', ok, delete),
807 ("three way conflict, host first, with partial write rights",
808 ('A', 'host/z.{dnsname}', 'A', ok),
809 ('B', 'cifs/z.{dnsname}', 'B', denied),
810 ('C', 'www/z.{dnsname}', 'C', denied),
812 ("three way conflict, host first, with partial write rights 2",
813 ('A', 'host/z.{dnsname}', 'A', ok),
814 ('B', 'cifs/z.{dnsname}', 'B', bad),
815 ('C', 'www/z.{dnsname}', 'C,A', ok),
818 ("three way conflict sandwich, sufficient rights",
819 ('B', 'host/{dnsname}', 'B', ok),
820 ('A', 'cifs/{dnsname}', 'A,B', ok),
821 # the replaces don't fail even though they appear to affect A
822 # and B, because they are effectively no-ops, leaving
823 # everything as it was before.
824 ('A', 'cifs/{dnsname}', 'A', ok),
825 ('B', 'host/{dnsname}', 'B', ok),
826 ('C', 'www/{dnsname}', 'A,B,C', ok),
827 ('C', 'www/{dnsname}', 'B,C', ok),
828 # because B already has host/, C doesn't matter
829 ('B', 'host/{dnsname}', 'A,B', ok),
830 # removing host (via replace) frees others, needs B only
831 ('B', 'ldap/{dnsname}', 'B', ok),
832 ('C', 'www/{dnsname}', 'C', ok),
833 ('A', 'cifs/{dnsname}', 'A', ok),
835 # re-adding host is now impossible while A and C have {dnsname} spns
836 ('B', 'host/{dnsname}', '*', bad),
837 ('B', 'host/{dnsname}', 'A,B,C', bad),
838 # so let's remove those... (not needing B rights)
839 ('C', 'www/{dnsname}', 'C', ok, delete),
840 ('A', 'cifs/{dnsname}', 'A', ok, delete),
841 # and now we can add host/ again
842 ('B', 'host/{dnsname}', 'B', ok),
843 ('C', 'www/{dnsname}', 'B,C', ok, add),
844 ('A', 'cifs/{dnsname}', 'A,B', ok),
846 ("three way conflict, service first, with all write rights",
847 ('A', 'cifs/{dnsname}', '*', ok),
848 ('B', 'www/{dnsname}', 'A,B,C', ok),
849 ('C', 'host/{dnsname}', 'A,B,C', bad),
851 ("three way conflict, service first, just sufficient rights",
852 ('A', 'cifs/{dnsname}', 'A', ok),
853 ('B', 'www/{dnsname}', 'B', ok),
854 ('C', 'host/{dnsname}', 'A,B,C', bad),
857 ("three way conflict, service first, with host write rights",
858 ('A', 'cifs/{dnsname}', '*', ok),
859 ('B', 'www/{dnsname}', '*', ok),
860 ('C', 'host/{dnsname}', 'C', bad),
862 ("three way conflict, service first, with both write rights",
863 ('A', 'cifs/{dnsname}', '*', ok),
864 ('A', 'cifs/{dnsname}', '*', ok, delete),
865 ('A', 'www/{dnsname}', 'A,B,C', ok),
866 ('B', 'host/{dnsname}', 'A,B', bad),
867 ('A', 'www/{dnsname}', 'A', ok, delete),
868 ('B', 'host/{dnsname}', 'A,B', ok),
869 ('C', 'cifs/{dnsname}', 'C', bad),
870 ('C', 'cifs/{dnsname}', 'B,C', ok),
872 ("three way conflict, services first, with partial rights",
873 ('A', 'cifs/{dnsname}', 'A,C', ok),
874 ('B', 'www/{dnsname}', '*', ok),
875 ('C', 'host/{dnsname}', 'A,C', bad),
880 @DynamicTestCase
881 class LdapSpnAmbitiousTest(LdapSpnTestBase):
882 _disabled = True
883 cases = [
884 ("add a conflict with port, host first both on user",
885 ('user:C', 'host/{dnsname}', '*', ok),
886 ('user:D', 'www/{dnsname}:80', '*', bad),
888 # see https://bugzilla.samba.org/show_bug.cgi?id=8929
889 ("add the same one twice, case-insensitive duplicate",
890 ('A', 'host/{dnsname}', '*', ok),
891 ('A', 'Host/{dnsname}', '*', bad, add),
893 ("special SPN",
894 # should fail because we don't have all the DSA infrastructure
895 ('A', ("E3514235-4B06-11D1-AB04-00C04FC2DCD2/"
896 "75b84f00-a81b-4a19-8ef2-8e483cccff11/"
897 "{dnsname}"), '*', constraint)
899 ("single part SPNs matching sAMAccountName",
900 # setting them both together is allegedly a MacOS behaviour,
901 # but all we get from Windows is a mysterious NO_SUCH_OBJECT.
902 ('user:A', {'sAMAccountName': 'A',
903 'servicePrincipalName': 'A'}, '*', ldb.ERR_NO_SUCH_OBJECT),
904 ('user:B', {'sAMAccountName': 'B'}, '*', ok),
905 ('user:B', {'servicePrincipalName': 'B'}, '*', constraint),
906 ('user:C', {'servicePrincipalName': 'C'}, '*', constraint),
907 ('user:C', {'sAMAccountName': 'C'}, '*', ok),
909 ("three part spns with dnsHostName",
910 ('A', {'dNSHostName': '{dnsname}'}, '*', ok),
911 ('A', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok),
912 ('A', {'dNSHostName': 'y.{dnsname}'}, '*', ok),
913 ('B', 'cifs/{dnsname}/DomainDNSZones.{dnsname}', '*', ok),
914 ('B', 'cifs/y.{dnsname}/DomainDNSZones.{dnsname}', '*', constraint),
915 ('C', 'host/{y.dnsname}/{y.dnsname}', '*', constraint),
916 ('A', 'host/y.{dnsname}/{dnsname}', '*', constraint),
921 def main():
922 TestProgram(module=__name__, opts=subunitopts)
924 main()