ctdb-failover: Split statd_callout add-client/del-client
[Samba.git] / python / samba / netcmd / delegation.py
blob413dd0fd61cc9b02f90314606e0bd450154a357d
1 # delegation management
3 # Copyright Matthieu Patou mat@samba.org 2010
4 # Copyright Stefan Metzmacher metze@samba.org 2011
5 # Copyright Bjoern Baumbach bb@sernet.de 2011
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 import samba.getopt as options
22 import ldb
23 from samba import provision
24 from samba import dsdb
25 from samba.samdb import SamDB
26 from samba.auth import system_session
27 from samba.dcerpc import security
28 from samba.ndr import ndr_pack, ndr_unpack
29 from samba.netcmd.common import _get_user_realm_domain
30 from samba.netcmd import (
31 Command,
32 CommandError,
33 SuperCommand,
34 Option
38 class cmd_delegation_show(Command):
39 """Show the delegation setting of an account."""
41 synopsis = "%prog <accountname> [options]"
43 takes_optiongroups = {
44 "sambaopts": options.SambaOptions,
45 "credopts": options.CredentialsOptions,
46 "versionopts": options.VersionOptions,
49 takes_options = [
50 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
51 metavar="URL", dest="H"),
54 takes_args = ["accountname"]
56 def show_security_descriptor(self, sam, security_descriptor):
57 dacl = security_descriptor.dacl
58 desc_type = security_descriptor.type
60 warning_info = ('Security Descriptor of attribute '
61 'msDS-AllowedToActOnBehalfOfOtherIdentity')
63 if dacl is None or not desc_type & security.SEC_DESC_DACL_PRESENT:
64 self.errf.write(f'Warning: DACL not present in {warning_info}!\n')
65 return
67 if not desc_type & security.SEC_DESC_SELF_RELATIVE:
68 self.errf.write(f'Warning: DACL in {warning_info} lacks '
69 f'SELF_RELATIVE flag!\n')
70 return
72 first = True
74 for ace in dacl.aces:
75 trustee = ace.trustee
77 # Convert the trustee SID into a DN if we can.
78 try:
79 res = sam.search(f'<SID={trustee}>',
80 scope=ldb.SCOPE_BASE)
81 except ldb.LdbError as err:
82 num, _ = err.args
83 if num != ldb.ERR_NO_SUCH_OBJECT:
84 raise
85 else:
86 if len(res) == 1:
87 trustee = res[0].dn
89 ignore = False
91 if (ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED
92 or ace.type == security.SEC_ACE_TYPE_ACCESS_DENIED_OBJECT):
93 self.errf.write(f'Warning: ACE in {warning_info} denies '
94 f'access for trustee {trustee}!\n')
95 # Ignore the ACE if it denies access
96 ignore = True
97 elif (ace.type != security.SEC_ACE_TYPE_ACCESS_ALLOWED
98 and ace.type != security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT):
99 # Ignore the ACE if it doesn't explicitly allow access
100 ignore = True
102 inherit_only = ace.flags & security.SEC_ACE_FLAG_INHERIT_ONLY
103 object_inherit = ace.flags & security.SEC_ACE_FLAG_OBJECT_INHERIT
104 container_inherit = (
105 ace.flags & security.SEC_ACE_FLAG_CONTAINER_INHERIT)
106 inherited_ace = ace.flags & security.SEC_ACE_FLAG_INHERITED_ACE
108 if inherit_only and not object_inherit and not container_inherit:
109 # Ignore the ACE if it is propagated only to child objects, but
110 # neither of the object and container inherit flags are set.
111 ignore = True
112 else:
113 if container_inherit:
114 self.errf.write(f'Warning: ACE for trustee {trustee} has '
115 f'unexpected CONTAINER_INHERIT flag set in '
116 f'{warning_info}!\n')
117 ignore = True
119 if inherited_ace:
120 self.errf.write(f'Warning: ACE for trustee {trustee} has '
121 f'unexpected INHERITED_ACE flag set in '
122 f'{warning_info}!\n')
123 ignore = True
125 if not ace.access_mask:
126 # Ignore the ACE if it doesn't grant any permissions.
127 ignore = True
129 if not ignore:
130 if first:
131 self.outf.write(' Principals that may delegate to this '
132 'account:\n')
133 first = False
135 self.outf.write(f'msDS-AllowedToActOnBehalfOfOtherIdentity: '
136 f'{trustee}\n')
138 def run(self, accountname, H=None, credopts=None, sambaopts=None, versionopts=None):
139 lp = sambaopts.get_loadparm()
140 creds = credopts.get_credentials(lp)
142 if H is None:
143 paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
144 path = paths.samdb
145 else:
146 path = H
148 sam = SamDB(path, session_info=system_session(),
149 credentials=creds, lp=lp)
150 # TODO once I understand how, use the domain info to naildown
151 # to the correct domain
152 (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname,
153 sam)
155 res = sam.search(expression="sAMAccountName=%s" %
156 ldb.binary_encode(cleanedaccount),
157 scope=ldb.SCOPE_SUBTREE,
158 attrs=["userAccountControl", "msDS-AllowedToDelegateTo",
159 "msDS-AllowedToActOnBehalfOfOtherIdentity"])
160 if len(res) == 0:
161 raise CommandError("Unable to find account name '%s'" % accountname)
162 elif len(res) != 1:
163 raise CommandError("Found multiple accounts.")
165 uac = int(res[0].get("userAccountControl")[0])
166 allowed = res[0].get("msDS-AllowedToDelegateTo")
167 allowed_from = res[0].get("msDS-AllowedToActOnBehalfOfOtherIdentity", idx=0)
169 self.outf.write("Account-DN: %s\n" % str(res[0].dn))
170 self.outf.write("UF_TRUSTED_FOR_DELEGATION: %s\n"
171 % bool(uac & dsdb.UF_TRUSTED_FOR_DELEGATION))
172 self.outf.write("UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: %s\n" %
173 bool(uac & dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION))
175 if allowed:
176 self.outf.write(" Services this account may delegate to:\n")
177 for a in allowed:
178 self.outf.write("msDS-AllowedToDelegateTo: %s\n" % a)
179 if allowed_from is not None:
180 try:
181 security_descriptor = ndr_unpack(security.descriptor, allowed_from)
182 except RuntimeError:
183 self.errf.write("Warning: Security Descriptor of attribute "
184 "msDS-AllowedToActOnBehalfOfOtherIdentity "
185 "could not be unmarshalled!\n")
186 else:
187 self.show_security_descriptor(sam, security_descriptor)
190 class cmd_delegation_for_any_service(Command):
191 """Set/unset UF_TRUSTED_FOR_DELEGATION for an account."""
193 synopsis = "%prog <accountname> [(on|off)] [options]"
195 takes_optiongroups = {
196 "sambaopts": options.SambaOptions,
197 "credopts": options.CredentialsOptions,
198 "versionopts": options.VersionOptions,
201 takes_options = [
202 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
203 metavar="URL", dest="H"),
206 takes_args = ["accountname", "onoff"]
208 def run(self, accountname, onoff, H=None, credopts=None, sambaopts=None,
209 versionopts=None):
211 if onoff == "on":
212 on = True
213 elif onoff == "off":
214 on = False
215 else:
216 raise CommandError("invalid argument: '%s' (choose from 'on', 'off')" % onoff)
218 lp = sambaopts.get_loadparm()
219 creds = credopts.get_credentials(lp)
220 paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
221 if H is None:
222 path = paths.samdb
223 else:
224 path = H
226 sam = SamDB(path, session_info=system_session(),
227 credentials=creds, lp=lp)
228 # TODO once I understand how, use the domain info to naildown
229 # to the correct domain
230 (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname,
231 sam)
233 search_filter = "sAMAccountName=%s" % ldb.binary_encode(cleanedaccount)
234 flag = dsdb.UF_TRUSTED_FOR_DELEGATION
235 try:
236 sam.toggle_userAccountFlags(search_filter, flag,
237 flags_str="Trusted-for-Delegation",
238 on=on, strict=True)
239 except Exception as err:
240 raise CommandError(err)
243 class cmd_delegation_for_any_protocol(Command):
244 """Set/unset UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION (S4U2Proxy) for an account."""
246 synopsis = "%prog <accountname> [(on|off)] [options]"
248 takes_optiongroups = {
249 "sambaopts": options.SambaOptions,
250 "credopts": options.CredentialsOptions,
251 "versionopts": options.VersionOptions,
254 takes_options = [
255 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
256 metavar="URL", dest="H"),
259 takes_args = ["accountname", "onoff"]
261 def run(self, accountname, onoff, H=None, credopts=None, sambaopts=None,
262 versionopts=None):
264 on = False
265 if onoff == "on":
266 on = True
267 elif onoff == "off":
268 on = False
269 else:
270 raise CommandError("invalid argument: '%s' (choose from 'on', 'off')" % onoff)
272 lp = sambaopts.get_loadparm()
273 creds = credopts.get_credentials(lp, fallback_machine=True)
274 paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
275 if H is None:
276 path = paths.samdb
277 else:
278 path = H
280 sam = SamDB(path, session_info=system_session(),
281 credentials=creds, lp=lp)
282 # TODO once I understand how, use the domain info to naildown
283 # to the correct domain
284 (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname,
285 sam)
287 search_filter = "sAMAccountName=%s" % ldb.binary_encode(cleanedaccount)
288 flag = dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION
289 try:
290 sam.toggle_userAccountFlags(search_filter, flag,
291 flags_str="Trusted-to-Authenticate-for-Delegation",
292 on=on, strict=True)
293 except Exception as err:
294 raise CommandError(err)
297 class cmd_delegation_add_service(Command):
298 """Add a service principal to msDS-AllowedToDelegateTo so that an account may delegate to it."""
300 synopsis = "%prog <accountname> <principal> [options]"
302 takes_optiongroups = {
303 "sambaopts": options.SambaOptions,
304 "credopts": options.CredentialsOptions,
305 "versionopts": options.VersionOptions,
308 takes_options = [
309 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
310 metavar="URL", dest="H"),
313 takes_args = ["accountname", "principal"]
315 def run(self, accountname, principal, H=None, credopts=None, sambaopts=None,
316 versionopts=None):
318 lp = sambaopts.get_loadparm()
319 creds = credopts.get_credentials(lp)
320 paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
321 if H is None:
322 path = paths.samdb
323 else:
324 path = H
326 sam = SamDB(path, session_info=system_session(),
327 credentials=creds, lp=lp)
328 # TODO once I understand how, use the domain info to naildown
329 # to the correct domain
330 (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname,
331 sam)
333 res = sam.search(expression="sAMAccountName=%s" %
334 ldb.binary_encode(cleanedaccount),
335 scope=ldb.SCOPE_SUBTREE,
336 attrs=["msDS-AllowedToDelegateTo"])
337 if len(res) == 0:
338 raise CommandError("Unable to find account name '%s'" % accountname)
339 elif len(res) != 1:
340 raise CommandError("Found multiple accounts.")
342 msg = ldb.Message()
343 msg.dn = res[0].dn
344 msg["msDS-AllowedToDelegateTo"] = ldb.MessageElement([principal],
345 ldb.FLAG_MOD_ADD,
346 "msDS-AllowedToDelegateTo")
347 try:
348 sam.modify(msg)
349 except Exception as err:
350 raise CommandError(err)
353 class cmd_delegation_del_service(Command):
354 """Delete a service principal from msDS-AllowedToDelegateTo so that an account may no longer delegate to it."""
356 synopsis = "%prog <accountname> <principal> [options]"
358 takes_optiongroups = {
359 "sambaopts": options.SambaOptions,
360 "credopts": options.CredentialsOptions,
361 "versionopts": options.VersionOptions,
364 takes_options = [
365 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
366 metavar="URL", dest="H"),
369 takes_args = ["accountname", "principal"]
371 def run(self, accountname, principal, H=None, credopts=None, sambaopts=None,
372 versionopts=None):
374 lp = sambaopts.get_loadparm()
375 creds = credopts.get_credentials(lp)
376 paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
377 if H is None:
378 path = paths.samdb
379 else:
380 path = H
382 sam = SamDB(path, session_info=system_session(),
383 credentials=creds, lp=lp)
384 # TODO once I understand how, use the domain info to naildown
385 # to the correct domain
386 (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname,
387 sam)
389 res = sam.search(expression="sAMAccountName=%s" %
390 ldb.binary_encode(cleanedaccount),
391 scope=ldb.SCOPE_SUBTREE,
392 attrs=["msDS-AllowedToDelegateTo"])
393 if len(res) == 0:
394 raise CommandError("Unable to find account name '%s'" % accountname)
395 elif len(res) != 1:
396 raise CommandError("Found multiple accounts.")
398 msg = ldb.Message()
399 msg.dn = res[0].dn
400 msg["msDS-AllowedToDelegateTo"] = ldb.MessageElement([principal],
401 ldb.FLAG_MOD_DELETE,
402 "msDS-AllowedToDelegateTo")
403 try:
404 sam.modify(msg)
405 except Exception as err:
406 raise CommandError(err)
409 class cmd_delegation_add_principal(Command):
410 """Add a principal to msDS-AllowedToActOnBehalfOfOtherIdentity that may delegate to an account."""
412 synopsis = "%prog <accountname> <principal> [options]"
414 takes_optiongroups = {
415 "sambaopts": options.SambaOptions,
416 "credopts": options.CredentialsOptions,
417 "versionopts": options.VersionOptions,
420 takes_options = [
421 Option("-H", "--URL", help="LDB URL for database or target server",
422 type=str, metavar="URL", dest="H"),
425 takes_args = ["accountname", "principal"]
427 def run(self, accountname, principal, H=None, credopts=None, sambaopts=None,
428 versionopts=None):
430 lp = sambaopts.get_loadparm()
431 creds = credopts.get_credentials(lp)
432 paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
433 if H is None:
434 path = paths.samdb
435 else:
436 path = H
438 sam = SamDB(path, session_info=system_session(),
439 credentials=creds, lp=lp)
440 # TODO once I understand how, use the domain info to naildown
441 # to the correct domain
442 cleanedaccount, _, _ = _get_user_realm_domain(accountname, sam)
444 account_res = sam.search(
445 expression="sAMAccountName=%s" %
446 ldb.binary_encode(cleanedaccount),
447 scope=ldb.SCOPE_SUBTREE,
448 attrs=["msDS-AllowedToActOnBehalfOfOtherIdentity"])
449 if len(account_res) == 0:
450 raise CommandError(f"Unable to find account name '{accountname}'")
451 elif len(account_res) != 1:
452 raise CommandError("Found multiple accounts.")
454 data = account_res[0].get(
455 "msDS-AllowedToActOnBehalfOfOtherIdentity", idx=0)
456 if data is None:
457 # Create the security descriptor if it is not present.
458 owner_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
460 security_desc = security.descriptor()
461 security_desc.revision = security.SD_REVISION
462 security_desc.type = (security.SEC_DESC_DACL_PRESENT |
463 security.SEC_DESC_SELF_RELATIVE)
464 security_desc.owner_sid = owner_sid
466 dacl = None
467 else:
468 try:
469 security_desc = ndr_unpack(security.descriptor, data)
470 except RuntimeError:
471 raise CommandError(f"Security Descriptor of attribute "
472 f"msDS-AllowedToActOnBehalfOfOtherIdentity "
473 f"for account '{accountname}' could not be "
474 f"unmarshalled!")
476 dacl = security_desc.dacl
478 if dacl is None:
479 # Create the DACL if it is not present.
480 dacl = security.acl()
481 dacl.revision = security.SECURITY_ACL_REVISION_ADS
482 dacl.num_aces = 0
484 # TODO once I understand how, use the domain info to naildown
485 # to the correct domain
486 cleanedprinc, _, _ = _get_user_realm_domain(principal, sam)
488 princ_res = sam.search(expression="sAMAccountName=%s" %
489 ldb.binary_encode(cleanedprinc),
490 scope=ldb.SCOPE_SUBTREE,
491 attrs=["objectSid"])
492 if len(princ_res) == 0:
493 raise CommandError(f"Unable to find principal name '{principal}'")
494 elif len(princ_res) != 1:
495 raise CommandError("Found multiple accounts.")
497 princ_sid = security.dom_sid(
498 sam.schema_format_value(
499 "objectSID",
500 princ_res[0].get("objectSID", idx=0)).decode("utf-8"))
502 aces = dacl.aces
504 # Check that there is no existing ACE for this principal.
505 if any(ace.trustee == princ_sid for ace in aces):
506 raise CommandError(
507 f"ACE for principal '{principal}' already present in Security "
508 f"Descriptor of attribute "
509 f"msDS-AllowedToActOnBehalfOfOtherIdentity for account "
510 f"'{accountname}'.")
512 # Create the new ACE.
513 ace = security.ace()
514 ace.type = security.SEC_ACE_TYPE_ACCESS_ALLOWED
515 ace.flags = 0
516 ace.access_mask = security.SEC_ADS_GENERIC_ALL
517 ace.trustee = princ_sid
519 aces.append(ace)
521 dacl.aces = aces
522 dacl.num_aces += 1
524 security_desc.dacl = dacl
526 new_data = ndr_pack(security_desc)
528 # Set the new security descriptor. First, delete the original value to
529 # detect a race condition if someone else updates the attribute at the
530 # same time.
531 msg = ldb.Message()
532 msg.dn = account_res[0].dn
533 if data is not None:
534 msg["0"] = ldb.MessageElement(
535 data, ldb.FLAG_MOD_DELETE,
536 "msDS-AllowedToActOnBehalfOfOtherIdentity")
537 msg["1"] = ldb.MessageElement(
538 new_data, ldb.FLAG_MOD_ADD,
539 "msDS-AllowedToActOnBehalfOfOtherIdentity")
540 try:
541 sam.modify(msg)
542 except ldb.LdbError as err:
543 num, _ = err.args
544 if num == ldb.ERR_NO_SUCH_ATTRIBUTE:
545 raise CommandError(
546 f"Refused to update attribute "
547 f"msDS-AllowedToActOnBehalfOfOtherIdentity for account "
548 f"'{accountname}': a conflicting attribute update "
549 f"occurred simultaneously.")
550 else:
551 raise CommandError(err)
554 class cmd_delegation_del_principal(Command):
555 """Delete a principal from msDS-AllowedToActOnBehalfOfOtherIdentity that may no longer delegate to an account."""
557 synopsis = "%prog <accountname> <principal> [options]"
559 takes_optiongroups = {
560 "sambaopts": options.SambaOptions,
561 "credopts": options.CredentialsOptions,
562 "versionopts": options.VersionOptions,
565 takes_options = [
566 Option("-H", "--URL", help="LDB URL for database or target server",
567 type=str, metavar="URL", dest="H"),
570 takes_args = ["accountname", "principal"]
572 def run(self, accountname, principal, H=None, credopts=None, sambaopts=None,
573 versionopts=None):
575 lp = sambaopts.get_loadparm()
576 creds = credopts.get_credentials(lp)
577 paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
578 if H is None:
579 path = paths.samdb
580 else:
581 path = H
583 sam = SamDB(path, session_info=system_session(),
584 credentials=creds, lp=lp)
585 # TODO once I understand how, use the domain info to naildown
586 # to the correct domain
587 cleanedaccount, _, _ = _get_user_realm_domain(accountname, sam)
589 account_res = sam.search(
590 expression="sAMAccountName=%s" %
591 ldb.binary_encode(cleanedaccount),
592 scope=ldb.SCOPE_SUBTREE,
593 attrs=["msDS-AllowedToActOnBehalfOfOtherIdentity"])
594 if len(account_res) == 0:
595 raise CommandError("Unable to find account name '%s'" % accountname)
596 elif len(account_res) != 1:
597 raise CommandError("Found multiple accounts.")
599 data = account_res[0].get(
600 "msDS-AllowedToActOnBehalfOfOtherIdentity", idx=0)
601 if data is None:
602 raise CommandError(f"Attribute "
603 f"msDS-AllowedToActOnBehalfOfOtherIdentity for "
604 f"account '{accountname}' not present!")
606 try:
607 security_desc = ndr_unpack(security.descriptor, data)
608 except RuntimeError:
609 raise CommandError(f"Security Descriptor of attribute "
610 f"msDS-AllowedToActOnBehalfOfOtherIdentity for "
611 f"account '{accountname}' could not be "
612 f"unmarshalled!")
614 dacl = security_desc.dacl
615 if dacl is None:
616 raise CommandError(f"DACL not present on Security Descriptor of "
617 f"attribute "
618 f"msDS-AllowedToActOnBehalfOfOtherIdentity for "
619 f"account '{accountname}'!")
621 # TODO once I understand how, use the domain info to naildown
622 # to the correct domain
623 cleanedprinc, _, _ = _get_user_realm_domain(principal, sam)
625 princ_res = sam.search(expression="sAMAccountName=%s" %
626 ldb.binary_encode(cleanedprinc),
627 scope=ldb.SCOPE_SUBTREE,
628 attrs=["objectSid"])
629 if len(princ_res) == 0:
630 raise CommandError(f"Unable to find principal name '{principal}'")
631 elif len(princ_res) != 1:
632 raise CommandError("Found multiple accounts.")
634 princ_sid = security.dom_sid(
635 sam.schema_format_value(
636 "objectSID",
637 princ_res[0].get("objectSID", idx=0)).decode("utf-8"))
639 old_aces = dacl.aces
641 # Remove any ACEs relating to the specified principal.
642 aces = [ace for ace in old_aces if ace.trustee != princ_sid]
644 # Raise an error if we didn't find any.
645 if len(aces) == len(old_aces):
646 raise CommandError(f"Unable to find ACE for principal "
647 f"'{principal}' in Security Descriptor of "
648 f"attribute "
649 f"msDS-AllowedToActOnBehalfOfOtherIdentity for "
650 f"account '{accountname}'.")
652 dacl.num_aces = len(aces)
653 dacl.aces = aces
655 security_desc.dacl = dacl
657 new_data = ndr_pack(security_desc)
659 # Set the new security descriptor. First, delete the original value to
660 # detect a race condition if someone else updates the attribute at the
661 # same time.
662 msg = ldb.Message()
663 msg.dn = account_res[0].dn
664 msg["0"] = ldb.MessageElement(
665 data, ldb.FLAG_MOD_DELETE,
666 "msDS-AllowedToActOnBehalfOfOtherIdentity")
667 msg["1"] = ldb.MessageElement(
668 new_data, ldb.FLAG_MOD_ADD,
669 "msDS-AllowedToActOnBehalfOfOtherIdentity")
670 try:
671 sam.modify(msg)
672 except ldb.LdbError as err:
673 num, _ = err.args
674 if num == ldb.ERR_NO_SUCH_ATTRIBUTE:
675 raise CommandError(
676 f"Refused to update attribute "
677 f"msDS-AllowedToActOnBehalfOfOtherIdentity for account "
678 f"'{accountname}': a conflicting attribute update "
679 f"occurred simultaneously.")
680 else:
681 raise CommandError(err)
684 class cmd_delegation(SuperCommand):
685 """Delegation management."""
687 subcommands = {}
688 subcommands["show"] = cmd_delegation_show()
689 subcommands["for-any-service"] = cmd_delegation_for_any_service()
690 subcommands["for-any-protocol"] = cmd_delegation_for_any_protocol()
691 subcommands["add-service"] = cmd_delegation_add_service()
692 subcommands["del-service"] = cmd_delegation_del_service()
693 subcommands["add-principal"] = cmd_delegation_add_principal()
694 subcommands["del-principal"] = cmd_delegation_del_principal()