1# -*- coding: ascii -*- 2""" 3web2ldap plugin classes for 4 5\xC6-DIR -- Authorized Entities Directory 6""" 7 8# Python's standard lib 9import re 10import time 11import socket 12from typing import Dict, List, Optional 13 14# from ldap0 package 15import ldap0 16import ldap0.filter 17from ldap0.filter import escape_str as escape_filter_str 18from ldap0.functions import strf_secs as ldap0_strf_secs 19from ldap0.pw import random_string 20from ldap0.controls.readentry import PreReadControl 21from ldap0.controls.deref import DereferenceControl 22from ldap0.filter import compose_filter, map_filter_parts 23from ldap0.dn import DNObj 24from ldap0.res import SearchResultEntry 25from ldap0.base import decode_list 26 27import web2ldapcnf 28 29from ...log import logger 30from ...web.forms import HiddenInput, Field 31from ..searchform import ( 32 SEARCH_OPT_IS_EQUAL, 33 SEARCH_OPT_DN_SUBTREE, 34) 35from .nis import UidNumber, GidNumber, MemberUID, Shell 36from .inetorgperson import DisplayNameInetOrgPerson, CNInetOrgPerson 37from .groups import GroupEntryDN 38from .oath import OathHOTPToken 39from .opensshlpk import SshPublicKey 40from .posixautogen import HomeDirectory 41from .ppolicy import PwdPolicySubentry 42from .sudoers import SudoUserGroup 43from ..schema.syntaxes import ( 44 ComposedAttribute, 45 DirectoryString, 46 DistinguishedName, 47 DNSDomain, 48 DerefDynamicDNSelectList, 49 DynamicValueSelectList, 50 IA5String, 51 Integer, 52 NotAfter, 53 NotBefore, 54 RFC822Address, 55 SelectList, 56 syntax_registry, 57) 58from .. import ErrorExit 59 60 61# OID arc for AE-DIR, see stroeder.com-oid-macros.schema 62AE_OID_PREFIX = '1.3.6.1.4.1.5427.1.389.100' 63 64# OIDs of AE-DIR's structural object classes 65AE_USER_OID = AE_OID_PREFIX+'.6.2' 66AE_GROUP_OID = AE_OID_PREFIX+'.6.1' 67AE_MAILGROUP_OID = AE_OID_PREFIX+'.6.27' 68AE_SRVGROUP_OID = AE_OID_PREFIX+'.6.13' 69AE_SUDORULE_OID = AE_OID_PREFIX+'.6.7' 70AE_HOST_OID = AE_OID_PREFIX+'.6.6.1' 71AE_SERVICE_OID = AE_OID_PREFIX+'.6.4' 72AE_ZONE_OID = AE_OID_PREFIX+'.6.20' 73AE_PERSON_OID = AE_OID_PREFIX+'.6.8' 74AE_TAG_OID = AE_OID_PREFIX+'.6.24' 75AE_POLICY_OID = AE_OID_PREFIX+'.6.26' 76AE_AUTHCTOKEN_OID = AE_OID_PREFIX+'.6.25' 77AE_DEPT_OID = AE_OID_PREFIX+'.6.29' 78AE_CONTACT_OID = AE_OID_PREFIX+'.6.5' 79AE_LOCATION_OID = AE_OID_PREFIX+'.6.35' 80AE_NWDEVICE_OID = AE_OID_PREFIX+'.6.6.2' 81 82 83syntax_registry.reg_at( 84 DNSDomain.oid, [ 85 AE_OID_PREFIX+'.4.10', # aeFqdn 86 ] 87) 88 89 90def ae_validity_filter(secs=None): 91 if secs is None: 92 secs = time.time() 93 return ( 94 '(&' 95 '(|(!(aeNotBefore=*))(aeNotBefore<={0}))' 96 '(|(!(aeNotAfter=*))(aeNotAfter>={0}))' 97 ')' 98 ).format(ldap0_strf_secs(secs)) 99 100 101class AEObjectMixIn: 102 """ 103 utility mix-in class for all aeObject entries 104 """ 105 106 @property 107 def ae_status(self): 108 try: 109 ae_status = int(self._entry['aeStatus'][0]) 110 except (KeyError, ValueError, IndexError): 111 ae_status = None 112 return ae_status 113 114 def _zone_entry(self, attrlist=None): 115 zone_dn = 'cn={0},{1}'.format( 116 self._get_zone_name(), 117 self._app.naming_context, 118 ) 119 try: 120 zone = self._app.ls.l.read_s( 121 zone_dn, 122 attrlist=attrlist, 123 filterstr='(objectClass=aeZone)', 124 ) 125 except ldap0.LDAPError: 126 res = {} 127 else: 128 if zone is None: 129 res = {} 130 else: 131 res = zone.entry_s 132 return res 133 134 def _get_zone_dn(self) -> str: 135 return str(self.dn.slice(-len(DNObj.from_str(self._app.naming_context))-1, None)) 136 137 def _get_zone_name(self) -> str: 138 return self.dn[-len(DNObj.from_str(self._app.naming_context))-1][0][1] 139 140 141class AEHomeDirectory(HomeDirectory): 142 """ 143 Plugin for attribute 'homeDirectory' in aeUser and aeService entries 144 """ 145 oid: str = 'AEHomeDirectory-oid' 146 # all valid directory prefixes for attribute 'homeDirectory' 147 # but without trailing slash 148 homeDirectoryPrefixes = ( 149 '/home', 150 ) 151 homeDirectoryHidden = b'-/-' 152 153 def _validate(self, attr_value: bytes) -> bool: 154 av_u = self._app.ls.uc_decode(attr_value)[0] 155 if attr_value == self.homeDirectoryHidden: 156 return True 157 for prefix in self.homeDirectoryPrefixes: 158 if av_u.startswith(prefix): 159 uid = self._app.ls.uc_decode(self._entry.get('uid', [b''])[0])[0] 160 return av_u.endswith(uid) 161 return False 162 163 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 164 if attr_values == [self.homeDirectoryHidden]: 165 return attr_values 166 if 'uid' in self._entry: 167 uid = self._app.ls.uc_decode(self._entry['uid'][0])[0] 168 else: 169 uid = '' 170 if attr_values: 171 av_u = self._app.ls.uc_decode(attr_values[0])[0] 172 for prefix in self.homeDirectoryPrefixes: 173 if av_u.startswith(prefix): 174 break 175 else: 176 prefix = self.homeDirectoryPrefixes[0] 177 else: 178 prefix = self.homeDirectoryPrefixes[0] 179 return [self._app.ls.uc_encode('/'.join((prefix, uid)))[0]] 180 181 def input_field(self) -> Field: 182 input_field = HiddenInput( 183 self._at, 184 ': '.join([self._at, self.desc]), 185 self.max_len, 186 self.max_values, 187 None, 188 default=self.form_value() 189 ) 190 input_field.charset = self._app.form.accept_charset 191 return input_field 192 193syntax_registry.reg_at( 194 AEHomeDirectory.oid, [ 195 '1.3.6.1.1.1.1.3', # homeDirectory 196 ], 197 structural_oc_oids=[AE_USER_OID, AE_SERVICE_OID], # aeUser and aeService 198) 199 200 201class AEUIDNumber(UidNumber): 202 """ 203 Plugin for attribute 'uidNumber' in aeUser and aeService entries 204 """ 205 oid: str = 'AEUIDNumber-oid' 206 desc: str = 'numeric Unix-UID' 207 208 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 209 return self._entry.get('gidNumber', [b'']) 210 211 def input_field(self) -> Field: 212 input_field = HiddenInput( 213 self._at, 214 ': '.join([self._at, self.desc]), 215 self.max_len, self.max_values, None, 216 default=self.form_value() 217 ) 218 input_field.charset = self._app.form.accept_charset 219 return input_field 220 221syntax_registry.reg_at( 222 AEUIDNumber.oid, [ 223 '1.3.6.1.1.1.1.0', # uidNumber 224 ], 225 structural_oc_oids=[ 226 AE_USER_OID, # aeUser 227 AE_SERVICE_OID, # aeService 228 ], 229) 230 231 232class AEGIDNumber(GidNumber): 233 """ 234 Plugin for attribute 'gidNumber' in aeUser, aeGroup and aeService entries 235 """ 236 oid: str = 'AEGIDNumber-oid' 237 desc: str = 'numeric Unix-GID' 238 minNewValue = 30000 239 maxNewValue = 49999 240 id_pool_dn = None 241 242 def _get_id_pool_dn(self) -> str: 243 """ 244 determine which ID pool entry to use 245 """ 246 return self.id_pool_dn or str(self._app.naming_context) 247 248 def _get_next_gid(self) -> int: 249 """ 250 consumes next ID by sending MOD_INCREMENT modify operation with 251 pre-read entry control 252 """ 253 prc = PreReadControl(criticality=True, attrList=[self._at]) 254 ldap_result = self._app.ls.l.modify_s( 255 self._get_id_pool_dn(), 256 [(ldap0.MOD_INCREMENT, self._app.ls.uc_encode(self._at)[0], [b'1'])], 257 req_ctrls=[prc], 258 ) 259 return int(ldap_result.ctrls[0].res.entry_s[self._at][0]) 260 261 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 262 if attr_values and attr_values[0]: 263 return attr_values 264 # first try to re-read gidNumber from existing entry 265 try: 266 ldap_result = self._app.ls.l.read_s( 267 self._dn, 268 attrlist=[self._at], 269 filterstr='({0}=*)'.format(self._at), 270 ) 271 except ( 272 ldap0.NO_SUCH_OBJECT, 273 ldap0.INSUFFICIENT_ACCESS, 274 ): 275 # search failed => ignore 276 pass 277 else: 278 if ldap_result: 279 return ldap_result.entry_as[self._at] 280 # return next ID from pool entry 281 return [str(self._get_next_gid()).encode('ascii')] 282 283 def form_value(self) -> str: 284 return Integer.form_value(self) 285 286 def input_field(self) -> Field: 287 return Integer.input_field(self) 288 289syntax_registry.reg_at( 290 AEGIDNumber.oid, [ 291 '1.3.6.1.1.1.1.1', # gidNumber 292 ], 293 structural_oc_oids=[ 294 AE_USER_OID, # aeUser 295 AE_GROUP_OID, # aeGroup 296 AE_SERVICE_OID, # aeService 297 ], 298) 299 300 301class AEUid(IA5String): 302 """ 303 Base class for attribute 'uid' mainly for sanitizing input values 304 """ 305 oid: str = 'AEUid-oid' 306 sani_funcs = ( 307 bytes.strip, 308 bytes.lower, 309 ) 310 311 312class AEUserUid(AEUid): 313 """ 314 Class for auto-generating values for aeUser -> uid 315 """ 316 oid: str = 'AEUserUid-oid' 317 desc: str = 'AE-DIR: User name' 318 max_values = 1 319 min_len: int = 4 320 max_len: int = 4 321 maxCollisionChecks: int = 15 322 UID_LETTERS = 'abcdefghijklmnopqrstuvwxyz' 323 pattern = re.compile('^[{}]+$'.format(UID_LETTERS)) 324 genLen = 4 325 sani_funcs = ( 326 bytes.strip, 327 bytes.lower, 328 ) 329 330 def __init__(self, app, dn: str, schema, attrType: str, attr_value: bytes, entry=None): 331 IA5String.__init__(self, app, dn, schema, attrType, attr_value, entry=entry) 332 333 def _gen_uid(self): 334 uid_candidates = [] 335 while len(uid_candidates) < self.maxCollisionChecks: 336 # generate new random UID candidate 337 uid_candidate = random_string(alphabet=self.UID_LETTERS, length=self.genLen) 338 # check whether UID candidate already exists 339 uid_result = self._app.ls.l.search_s( 340 str(self._app.naming_context), 341 ldap0.SCOPE_SUBTREE, 342 '(uid=%s)' % (escape_filter_str(uid_candidate)), 343 attrlist=['1.1'], 344 ) 345 if not uid_result: 346 logger.info( 347 'Generated aeUser-uid after %d collisions: %r', 348 len(uid_candidates), 349 uid_candidate, 350 ) 351 return uid_candidate 352 uid_candidates.append(uid_candidate) 353 logger.error( 354 'Generating aeUser-uid stopped after %d collisions. Tried candidates: %r', 355 len(uid_candidates), 356 uid_candidates, 357 ) 358 raise ErrorExit( 359 'Gave up generating new unique <em>uid</em> after {0:d} attempts.'.format( 360 len(uid_candidates), 361 ) 362 ) 363 # end of _gen_uid() 364 365 def form_value(self) -> str: 366 fval = IA5String.form_value(self) 367 if not self._av: 368 fval = self._gen_uid() 369 return fval 370 371 def input_field(self) -> Field: 372 return HiddenInput( 373 self._at, 374 ': '.join([self._at, self.desc]), 375 self.max_len, self.max_values, None, 376 default=self.form_value() 377 ) 378 379 def sanitize(self, attr_value: bytes) -> bytes: 380 return attr_value.strip().lower() 381 382syntax_registry.reg_at( 383 AEUserUid.oid, [ 384 '0.9.2342.19200300.100.1.1', # uid 385 ], 386 structural_oc_oids=[ 387 AE_USER_OID, # aeUser 388 ], 389) 390 391 392class AEServiceUid(AEUid): 393 """ 394 Plugin for attribute 'uid' in aeService entries 395 """ 396 oid: str = 'AEServiceUid-oid' 397 398syntax_registry.reg_at( 399 AEServiceUid.oid, [ 400 '0.9.2342.19200300.100.1.1', # uid 401 ], 402 structural_oc_oids=[ 403 AE_SERVICE_OID, # aeService 404 ], 405) 406 407 408class AETicketId(IA5String): 409 """ 410 Plugin for attribute 'aeTicketId' in all aeObject entries 411 """ 412 oid: str = 'AETicketId-oid' 413 desc: str = 'AE-DIR: Ticket no. related to last change of entry' 414 sani_funcs = ( 415 bytes.upper, 416 bytes.strip, 417 ) 418 419syntax_registry.reg_at( 420 AETicketId.oid, [ 421 AE_OID_PREFIX+'.4.3', # aeTicketId 422 ] 423) 424 425 426class AERootDynamicDNSelectList(DerefDynamicDNSelectList): 427 """ 428 custom variant with smarter handling of search base 429 """ 430 oid: str = 'AERootDynamicDNSelectList-oid' 431 input_fallback = False # no fallback to normal input field 432 suffix_attr = 'aeRoot' 433 434 def _search_root(self) -> str: 435 if self.lu_obj.dn == self.suffix_attr: 436 try: 437 ae_suffix = self._app.ls.l.read_rootdse_s( 438 attrlist=['self.suffix_attr'] 439 ).entry_s[self.suffix_attr][0] 440 except (ldap0.LDAPError, KeyError): 441 pass 442 else: 443 return ae_suffix 444 return DerefDynamicDNSelectList._search_root(self) 445 446 447class AEZoneDN(AERootDynamicDNSelectList): 448 """ 449 Plugin for attributes holding DNs of aeZone entries 450 """ 451 oid: str = 'AEZoneDN-oid' 452 desc: str = 'AE-DIR: Zone' 453 ldap_url = 'ldap:///_?cn?sub?(&(objectClass=aeZone)(aeStatus=0))' 454 ref_attrs = ( 455 (None, 'Same zone', None, 'aeGroup', 'Search all groups constrained to same zone'), 456 ) 457 458syntax_registry.reg_at( 459 AEZoneDN.oid, [ 460 AE_OID_PREFIX+'.4.36', # aeMemberZone 461 ] 462) 463 464 465class AEHost(AERootDynamicDNSelectList): 466 """ 467 Plugin for attribute 'host' in aeHost entries 468 """ 469 oid: str = 'AEHost-oid' 470 desc: str = 'AE-DIR: Host' 471 ldap_url = 'ldap:///_?host?sub?(&(objectClass=aeHost)(aeStatus=0))' 472 ref_attrs = ( 473 (None, 'Same host', None, 'aeService', 'Search all services running on same host'), 474 ) 475 476syntax_registry.reg_at( 477 AEHost.oid, [ 478 AE_OID_PREFIX+'.4.28', # aeHost 479 ] 480) 481 482 483class AENwDevice(AERootDynamicDNSelectList): 484 """ 485 Plugin for attributes holding DNs of aeNwDevice entries 486 """ 487 oid: str = 'AENwDevice-oid' 488 desc: str = 'AE-DIR: network interface' 489 ldap_url = 'ldap:///..?cn?sub?(&(objectClass=aeNwDevice)(aeStatus=0))' 490 ref_attrs = ( 491 (None, 'Siblings', None, 'aeNwDevice', 'Search sibling network devices'), 492 ) 493 494 def _search_root(self) -> str: 495 if self._dn.startswith('host='): 496 return self._dn 497 return DerefDynamicDNSelectList._search_root(self) 498 499 def _filterstr(self): 500 orig_filter = DerefDynamicDNSelectList._filterstr(self) 501 try: 502 dev_name = self._app.ls.uc_decode(self._entry['cn'][0])[0] 503 except (KeyError, IndexError): 504 result_filter = orig_filter 505 else: 506 result_filter = '(&{0}(!(cn={1})))'.format(orig_filter, dev_name) 507 return result_filter 508 509syntax_registry.reg_at( 510 AENwDevice.oid, [ 511 AE_OID_PREFIX+'.4.34', # aeNwDevice 512 ] 513) 514 515 516class AEGroupMember(DerefDynamicDNSelectList, AEObjectMixIn): 517 """ 518 Plugin for attribute 'member' in aeGroup entries 519 """ 520 oid: str = 'AEGroupMember-oid' 521 desc: str = 'AE-DIR: Member of a group' 522 input_fallback = False # no fallback to normal input field 523 ldap_url = ( 524 'ldap:///_?displayName?sub?' 525 '(&(|(objectClass=aeUser)(objectClass=aeService))(aeStatus=0))' 526 ) 527 deref_person_attrs = ('aeDept', 'aeLocation') 528 529 def _zone_filter(self): 530 member_zones = [ 531 self._app.ls.uc_decode(mezo)[0] 532 for mezo in self._entry.get('aeMemberZone', []) 533 if mezo 534 ] 535 if member_zones: 536 member_zone_filter = compose_filter( 537 '|', 538 map_filter_parts('entryDN:dnSubordinateMatch:', member_zones), 539 ) 540 else: 541 member_zone_filter = '' 542 return member_zone_filter 543 544 def _deref_person_attrset(self): 545 result = {} 546 for attr_type in self.deref_person_attrs: 547 if attr_type in self._entry and list(filter(None, self._entry[attr_type])): 548 result[attr_type] = set(self._entry[attr_type]) 549 return result 550 551 def _filterstr(self): 552 return '(&{0}{1})'.format( 553 DerefDynamicDNSelectList._filterstr(self), 554 self._zone_filter(), 555 ) 556 557 def _extract_attr_value_dict(self, ldap_result, deref_person_attrset): 558 attr_value_dict: Dict[str, str] = SelectList.get_attr_value_dict(self) 559 for ldap_res in ldap_result: 560 if not isinstance(ldap_res, SearchResultEntry): 561 # ignore search continuations 562 continue 563 # process dn and entry 564 if ldap_res.ctrls: 565 deref_control = ldap_res.ctrls[0] 566 deref_entry = deref_control.derefRes['aePerson'][0].entry_as 567 elif deref_person_attrset: 568 # if we have constrained attributes, no deref response control 569 # means constraint not valid 570 continue 571 # check constrained values here 572 valid = True 573 for attr_type, attr_values in deref_person_attrset.items(): 574 if ( 575 attr_type not in deref_entry 576 or deref_entry[attr_type][0] not in attr_values 577 ): 578 valid = False 579 break 580 if valid: 581 option_value = ldap_res.dn_s 582 try: 583 option_text = ldap_res.entry_s['displayName'][0] 584 except KeyError: 585 option_text = option_value 586 try: 587 option_title = ldap_res.entry_s['description'][0] 588 except KeyError: 589 option_title = option_value 590 attr_value_dict[option_value] = (option_text, option_title) 591 return attr_value_dict 592 593 def get_attr_value_dict(self) -> Dict[str, str]: 594 deref_person_attrset = self._deref_person_attrset() 595 if not deref_person_attrset: 596 return DerefDynamicDNSelectList.get_attr_value_dict(self) 597 member_filter = self._filterstr() 598 try: 599 # Use the existing LDAP connection as current user 600 ldap_result = self._app.ls.l.search_s( 601 self._search_root(), 602 self.lu_obj.scope or ldap0.SCOPE_SUBTREE, 603 filterstr=member_filter, 604 attrlist=self.lu_obj.attrs+['description'], 605 req_ctrls=[ 606 DereferenceControl(True, {'aePerson': deref_person_attrset.keys()}) 607 ], 608 cache_ttl=min(30, 5*web2ldapcnf.ldap_cache_ttl), 609 ) 610 except self.ignored_errors as ldap_err: 611 logger.warning( 612 '%s.get_attr_value_dict() searching %r failed: %s', 613 self.__class__.__name__, 614 member_filter, 615 ldap_err, 616 ) 617 return SelectList.get_attr_value_dict(self) 618 return self._extract_attr_value_dict(ldap_result, deref_person_attrset) 619 # get_attr_value_dict() 620 621 def _validate(self, attr_value: bytes) -> bool: 622 if 'memberURL' in self._entry and self._entry['memberURL'] != [b'']: 623 # reduce to simple DN syntax check for dynamic groups 624 return DistinguishedName._validate(self, attr_value) 625 return SelectList._validate(self, attr_value) 626 627 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 628 if self.ae_status == 2: 629 return [] 630 return DerefDynamicDNSelectList.transmute(self, attr_values) 631 632syntax_registry.reg_at( 633 AEGroupMember.oid, [ 634 '2.5.4.31', # member 635 ], 636 structural_oc_oids=[ 637 AE_GROUP_OID, # aeGroup 638 ], 639) 640 641 642class AEMailGroupMember(AEGroupMember): 643 """ 644 Plugin for attribute 'member' in aeMailGroup entries 645 """ 646 oid: str = 'AEMailGroupMember-oid' 647 desc: str = 'AE-DIR: Member of a mail group' 648 input_fallback = False # no fallback to normal input field 649 ldap_url = ( 650 'ldap:///_?displayName?sub?' 651 '(&(|(objectClass=inetLocalMailRecipient)(objectClass=aeContact))(mail=*)(aeStatus=0))' 652 ) 653 654syntax_registry.reg_at( 655 AEMailGroupMember.oid, [ 656 '2.5.4.31', # member 657 ], 658 structural_oc_oids=[ 659 AE_MAILGROUP_OID, # aeMailGroup 660 ], 661) 662 663 664class AEMemberUid(MemberUID, AEObjectMixIn): 665 """ 666 Plugin for attribute 'memberUid' in aeGroup entries 667 """ 668 oid: str = 'AEMemberUid-oid' 669 desc: str = 'AE-DIR: username (uid) of member of a group' 670 ldap_url = None 671 show_val_button = False 672 673 def _member_uids_from_member(self): 674 return [ 675 dn[4:].split(b',')[0] 676 for dn in self._entry.get('member', []) 677 ] 678 679 def _validate(self, attr_value: bytes) -> bool: 680 """ 681 Because AEMemberUid.transmute() always resets all attribute values it's 682 ok to not validate values at all 683 """ 684 return True 685 686 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 687 if 'member' not in self._entry: 688 return [] 689 if self.ae_status == 2: 690 return [] 691 return list(filter(None, self._member_uids_from_member())) 692 693 def form_value(self) -> str: 694 return '' 695 696 def input_field(self) -> Field: 697 input_field = HiddenInput( 698 self._at, 699 ': '.join([self._at, self.desc]), 700 self.max_len, self.max_values, None, 701 ) 702 input_field.charset = self._app.form.accept_charset 703 input_field.set_default(self.form_value()) 704 return input_field 705 706 def display(self, vidx, links) -> str: 707 return IA5String.display(self, vidx, links) 708 709syntax_registry.reg_at( 710 AEMemberUid.oid, [ 711 '1.3.6.1.1.1.1.12', # memberUid 712 ], 713 structural_oc_oids=[ 714 AE_GROUP_OID, # aeGroup 715 ], 716) 717 718 719class AEGroupDN(AERootDynamicDNSelectList): 720 """ 721 Plugin for attribute 'memberOf' in group member entries 722 """ 723 oid: str = 'AEGroupDN-oid' 724 desc: str = 'AE-DIR: DN of user group entry' 725 ldap_url = 'ldap:///_??sub?(&(|(objectClass=aeGroup)(objectClass=aeMailGroup))(aeStatus=0))' 726 ref_attrs = ( 727 ('memberOf', 'Members', None, 'Search all member entries of this user group'), 728 ) 729 730 def display(self, vidx, links) -> str: 731 group_dn = DNObj.from_str(self.av_u) 732 group_cn = group_dn[0][0][1] 733 res = [ 734 'cn=<strong>{0}</strong>,{1}'.format( 735 self._app.form.s2d(group_cn), 736 self._app.form.s2d(str(group_dn.parent())), 737 ) 738 ] 739 if links: 740 res.extend(self._additional_links()) 741 return web2ldapcnf.command_link_separator.join(res) 742 743syntax_registry.reg_at( 744 AEGroupDN.oid, [ 745 '1.2.840.113556.1.2.102', # memberOf 746 ], 747 structural_oc_oids=[ 748 AE_USER_OID, # aeUser 749 AE_SERVICE_OID, # aeService 750 AE_CONTACT_OID, # aeContact 751 ], 752) 753 754 755class AEZoneAdminGroupDN(AEGroupDN): 756 """ 757 Plugin for attributes holding DNs of zone admin groups 758 """ 759 oid: str = 'AEZoneAdminGroupDN-oid' 760 desc: str = 'AE-DIR: DN of zone admin group entry' 761 ldap_url = ( 762 'ldap:///_??sub?' 763 '(&' 764 '(objectClass=aeGroup)' 765 '(aeStatus=0)' 766 '(cn=*-zone-admins)' 767 '(!' 768 '(|' 769 '(cn:dn:=pub)' 770 '(cn:dn:=ae)' 771 ')' 772 ')' 773 ')' 774 ) 775 776syntax_registry.reg_at( 777 AEZoneAdminGroupDN.oid, [ 778 AE_OID_PREFIX+'.4.31', # aeZoneAdmins 779 AE_OID_PREFIX+'.4.33', # aePasswordAdmins 780 ] 781) 782 783 784class AEZoneAuditorGroupDN(AEGroupDN): 785 """ 786 Plugin for attributes holding DNs of zone auditor groups 787 """ 788 oid: str = 'AEZoneAuditorGroupDN-oid' 789 desc: str = 'AE-DIR: DN of zone auditor group entry' 790 ldap_url = ( 791 'ldap:///_??sub?' 792 '(&' 793 '(objectClass=aeGroup)' 794 '(aeStatus=0)' 795 '(|' 796 '(cn=*-zone-admins)' 797 '(cn=*-zone-auditors)' 798 ')' 799 '(!' 800 '(|' 801 '(cn:dn:=pub)' 802 '(cn:dn:=ae)' 803 ')' 804 ')' 805 ')' 806 ) 807 808syntax_registry.reg_at( 809 AEZoneAuditorGroupDN.oid, [ 810 AE_OID_PREFIX+'.4.32', # aeZoneAuditors 811 ] 812) 813 814 815class AESrvGroupRightsGroupDN(AEGroupDN): 816 """ 817 Plugin class for attributes holding DNs of user groups 818 in aeSrvGroup entries 819 """ 820 oid: str = 'AESrvGroupRightsGroupDN-oid' 821 desc: str = 'AE-DIR: DN of user group entry' 822 ldap_url = ( 823 'ldap:///_??sub?' 824 '(&' 825 '(objectClass=aeGroup)' 826 '(aeStatus=0)' 827 '(!' 828 '(|' 829 '(cn:dn:=pub)' 830 '(cn=*-zone-admins)' 831 '(cn=*-zone-auditors)' 832 ')' 833 ')' 834 ')' 835 ) 836 837syntax_registry.reg_at( 838 AESrvGroupRightsGroupDN.oid, [ 839 AE_OID_PREFIX+'.4.4', # aeLoginGroups 840 AE_OID_PREFIX+'.4.6', # aeSetupGroups 841 AE_OID_PREFIX+'.4.7', # aeLogStoreGroups 842 AE_OID_PREFIX+'.4.37', # aeABAccessGroups 843 ] 844) 845 846 847class AEDisplayNameGroups(AESrvGroupRightsGroupDN): 848 """ 849 Plugin class for attribute 'aeDisplayNameGroups' in aeSrvGroup entries 850 """ 851 oid: str = 'AEDisplayNameGroups-oid' 852 desc: str = 'AE-DIR: DN of visible user group entry' 853 ldap_url = ( 854 'ldap:///_??sub?' 855 '(&' 856 '(|' 857 '(objectClass=aeGroup)' 858 '(objectClass=aeMailGroup)' 859 ')' 860 '(aeStatus=0)' 861 '(!' 862 '(|' 863 '(cn:dn:=pub)' 864 '(cn=*-zone-admins)' 865 '(cn=*-zone-auditors)' 866 ')' 867 ')' 868 ')' 869 ) 870 871syntax_registry.reg_at( 872 AEDisplayNameGroups.oid, [ 873 AE_OID_PREFIX+'.4.30', # aeDisplayNameGroups 874 ] 875) 876 877 878class AEVisibleGroups(AEDisplayNameGroups): 879 """ 880 Plugin class for attribute 'aeVisibleGroups' in aeSrvGroup entries 881 """ 882 oid: str = 'AEVisibleGroups-oid' 883 desc: str = 'AE-DIR: DN of visible user group entry' 884 always_add_groups = ( 885 'aeLoginGroups', 886 'aeDisplayNameGroups', 887 ) 888 889 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 890 attr_values = set(attr_values) 891 for attr_type in self.always_add_groups: 892 attr_values.update(self._entry.get(attr_type, [])) 893 return list(attr_values) 894 895syntax_registry.reg_at( 896 AEVisibleGroups.oid, [ 897 AE_OID_PREFIX+'.4.20', # aeVisibleGroups 898 ] 899) 900 901 902class AESameZoneObject(DerefDynamicDNSelectList, AEObjectMixIn): 903 """ 904 Plugin class for attributes storing DN references limited to reference 905 entries within the same zone 906 """ 907 oid: str = 'AESameZoneObject-oid' 908 desc: str = 'AE-DIR: DN of referenced aeSrvGroup entry this is proxy for' 909 input_fallback = False # no fallback to normal input field 910 ldap_url = 'ldap:///_?cn?sub?(&(objectClass=aeObject)(aeStatus=0))' 911 912 def _search_root(self): 913 return self._get_zone_dn() 914 915 916class AESrvGroupDN(AEGroupDN): 917 """ 918 Plugin for attributes holding DNs of aeSrvGroup entries 919 """ 920 oid: str = 'AESrvGroupDN-oid' 921 desc: str = 'AE-DIR: DN of a referenced aeSrvGroup entry' 922 ldap_url = 'ldap:///_?cn?sub?(&(objectClass=aeSrvGroup)(aeStatus=0))' 923 ref_attrs = DerefDynamicDNSelectList.ref_attrs 924 925 926class AESrvGroup(AESrvGroupDN, AESameZoneObject): 927 """ 928 Plugin class for attribute 'aeSrvGroup' in aeUser and aeService entries 929 """ 930 oid: str = 'AESrvGroup-oid' 931 desc: str = 'AE-DIR: DN of supplemental aeSrvGroup entry' 932 ldap_url = 'ldap:///_?cn?sub?(&(objectClass=aeSrvGroup)(aeStatus=0)(!(aeProxyFor=*)))' 933 934 def _filterstr(self): 935 filter_str = self.lu_obj.filterstr or '(objectClass=aeSrvGroup)' 936 return '(&%s(!(entryDN=%s)))' % ( 937 filter_str, 938 escape_filter_str(str(self.dn.parent())), 939 ) 940 941syntax_registry.reg_at( 942 AESrvGroup.oid, [ 943 AE_OID_PREFIX+'.4.27', # aeSrvGroup 944 ] 945) 946 947 948class AERequires(AESrvGroupDN): 949 """ 950 Plugin class for attribute 'aeRequires' in aeSrvGroup entries 951 """ 952 oid: str = 'AERequires-oid' 953 desc: str = 'AE-DIR: DN of required aeSrvGroup' 954 ldap_url = 'ldap:///_?cn?sub?(&(objectClass=aeSrvGroup)(aeStatus=0))' 955 ref_attrs = ( 956 ( 957 'aeRequires', 'Same require', None, 'aeSrvGroup', 958 'Search all service groups depending on this service group.' 959 ), 960 ) 961 962syntax_registry.reg_at( 963 AERequires.oid, [ 964 AE_OID_PREFIX+'.4.48', # aeRequires 965 ] 966) 967 968 969class AEProxyFor(AESrvGroupDN, AESameZoneObject): 970 """ 971 Plugin class for attribute 'aeProxyFor' in aeSrvGroup entries 972 """ 973 oid: str = 'AEProxyFor-oid' 974 desc: str = 'AE-DIR: DN of referenced aeSrvGroup entry this is proxy for' 975 ldap_url = 'ldap:///_?cn?sub?(&(objectClass=aeSrvGroup)(aeStatus=0)(!(aeProxyFor=*)))' 976 977 def _filterstr(self): 978 filter_str = self.lu_obj.filterstr or '(objectClass=*)' 979 return '(&%s(!(entryDN=%s)))' % ( 980 filter_str, 981 escape_filter_str(self._dn), 982 ) 983 984syntax_registry.reg_at( 985 AEProxyFor.oid, [ 986 AE_OID_PREFIX+'.4.25', # aeProxyFor 987 ] 988) 989 990 991class AETag(DynamicValueSelectList): 992 """ 993 Plugin class for attribute 'aeTag' in all aeObject entries 994 """ 995 oid: str = 'AETag-oid' 996 desc: str = 'AE-DIR: cn of referenced aeTag entry' 997 ldap_url = 'ldap:///_?cn,cn?sub?(&(objectClass=aeTag)(aeStatus=0))' 998 999syntax_registry.reg_at( 1000 AETag.oid, [ 1001 AE_OID_PREFIX+'.4.24', # aeTag 1002 ] 1003) 1004 1005 1006class AEEntryDNAEPerson(DistinguishedName): 1007 """ 1008 Plugin class for attribute 'entryDN' in aePerson entries 1009 """ 1010 oid: str = 'AEEntryDNAEPerson-oid' 1011 desc: str = 'AE-DIR: entryDN of aePerson entry' 1012 ref_attrs = ( 1013 ('manager', 'Manages', None, 'Search all entries managed by this person'), 1014 ( 1015 'aePerson', 'Users', None, 'aeUser', 1016 'Search all personal AE-DIR user accounts (aeUser entries) of this person.' 1017 ), 1018 ( 1019 'aeOwner', 'Devices', None, 'aeDevice', 1020 'Search all devices (aeDevice entries) assigned to this person.' 1021 ), 1022 ) 1023 1024syntax_registry.reg_at( 1025 AEEntryDNAEPerson.oid, [ 1026 '1.3.6.1.1.20', # entryDN 1027 ], 1028 structural_oc_oids=[ 1029 AE_PERSON_OID, # aePerson 1030 ], 1031) 1032 1033 1034class AEEntryDNAEUser(DistinguishedName): 1035 """ 1036 Plugin class for attribute 'entryDN' in aeUser entries 1037 """ 1038 oid: str = 'AEEntryDNAEUser-oid' 1039 desc: str = 'AE-DIR: entryDN of aeUser entry' 1040 1041 def _additional_links(self): 1042 res = DistinguishedName._additional_links(self) 1043 res.append(self._app.anchor( 1044 'searchform', 'Created/Modified', 1045 ( 1046 ('dn', self._dn), 1047 ('search_root', str(self._app.naming_context)), 1048 ('searchform_mode', 'adv'), 1049 ('search_mode', '(|%s)'), 1050 ('search_attr', 'creatorsName'), 1051 ('search_option', SEARCH_OPT_IS_EQUAL), 1052 ('search_string', self.av_u), 1053 ('search_attr', 'modifiersName'), 1054 ('search_option', SEARCH_OPT_IS_EQUAL), 1055 ('search_string', self.av_u), 1056 ), 1057 title='Search entries created or modified by %s' % (self.av_u), 1058 )) 1059 if self._app.audit_context: 1060 res.append(self._app.anchor( 1061 'search', 'Activity', 1062 ( 1063 ('dn', self._app.audit_context), 1064 ('searchform_mode', 'adv'), 1065 ('search_attr', 'objectClass'), 1066 ('search_option', SEARCH_OPT_IS_EQUAL), 1067 ('search_string', 'auditObject'), 1068 ('search_attr', 'reqAuthzID'), 1069 ('search_option', SEARCH_OPT_IS_EQUAL), 1070 ('search_string', self.av_u), 1071 ), 1072 title='Search modifications made by %s in accesslog DB' % (self.av_u), 1073 )) 1074 return res 1075 1076syntax_registry.reg_at( 1077 AEEntryDNAEUser.oid, [ 1078 '1.3.6.1.1.20', # entryDN 1079 ], 1080 structural_oc_oids=[ 1081 AE_USER_OID, # aeUser 1082 AE_SERVICE_OID, # aeService 1083 ], 1084) 1085 1086 1087class AEEntryDNAEHost(DistinguishedName): 1088 """ 1089 Plugin class for attribute 'entryDN' in aeHost entries 1090 """ 1091 oid: str = 'AEEntryDNAEHost-oid' 1092 desc: str = 'AE-DIR: entryDN of aeUser entry' 1093 ref_attrs = ( 1094 ('aeHost', 'Services', None, 'aeService', 'Search all services running on this host'), 1095 ) 1096 1097 def _additional_links(self): 1098 res = DistinguishedName._additional_links(self) 1099 srv_group_assertion_values = [escape_filter_str(str(self.dn.parent()))] 1100 srv_group_assertion_values.extend([ 1101 escape_filter_str(av.decode(self._app.ls.charset)) 1102 for av in self._entry.get('aeSrvGroup', []) 1103 ]) 1104 res.extend([ 1105 self._app.anchor( 1106 'search', 'Siblings', 1107 ( 1108 ('dn', self._dn), 1109 ('search_root', str(self._app.naming_context)), 1110 ('searchform_mode', 'exp'), 1111 ( 1112 'filterstr', 1113 '(&(|(objectClass=aeHost)(objectClass=aeService))(|{0}{1}))'.format( 1114 ''.join([ 1115 '(entryDN:dnSubordinateMatch:=%s)' % av 1116 for av in srv_group_assertion_values 1117 ]), 1118 ''.join([ 1119 '(aeSrvGroup=%s)' % av 1120 for av in srv_group_assertion_values 1121 ]), 1122 ) 1123 ), 1124 ), 1125 title=( 1126 'Search all host entries which are member in ' 1127 'at least one common server group(s) with this host' 1128 ), 1129 ), 1130 ]) 1131 return res 1132 1133syntax_registry.reg_at( 1134 AEEntryDNAEHost.oid, [ 1135 '1.3.6.1.1.20', # entryDN 1136 ], 1137 structural_oc_oids=[ 1138 AE_HOST_OID, # aeHost 1139 ], 1140) 1141 1142 1143class AEEntryDNAEZone(DistinguishedName): 1144 """ 1145 Plugin class for attribute 'entryDN' in aeZone entries 1146 """ 1147 oid: str = 'AEEntryDNAEZone-oid' 1148 desc: str = 'AE-DIR: entryDN of aeZone entry' 1149 1150 def _additional_links(self): 1151 res = DistinguishedName._additional_links(self) 1152 if self._app.audit_context: 1153 res.append(self._app.anchor( 1154 'search', 'Audit all', 1155 ( 1156 ('dn', self._app.audit_context), 1157 ('searchform_mode', 'adv'), 1158 ('search_attr', 'objectClass'), 1159 ('search_option', SEARCH_OPT_IS_EQUAL), 1160 ('search_string', 'auditObject'), 1161 ('search_attr', 'reqDN'), 1162 ('search_option', SEARCH_OPT_DN_SUBTREE), 1163 ('search_string', self.av_u), 1164 ), 1165 title='Search all audit log entries for sub-tree %s' % (self.av_u), 1166 )) 1167 res.append(self._app.anchor( 1168 'search', 'Audit writes', 1169 ( 1170 ('dn', self._app.audit_context), 1171 ('searchform_mode', 'adv'), 1172 ('search_attr', 'objectClass'), 1173 ('search_option', SEARCH_OPT_IS_EQUAL), 1174 ('search_string', 'auditObject'), 1175 ('search_attr', 'reqDN'), 1176 ('search_option', SEARCH_OPT_DN_SUBTREE), 1177 ('search_string', self.av_u), 1178 ), 1179 title='Search audit log entries for write operation within sub-tree %s' % ( 1180 self.av_u 1181 ), 1182 )) 1183 return res 1184 1185syntax_registry.reg_at( 1186 AEEntryDNAEZone.oid, [ 1187 '1.3.6.1.1.20', # entryDN 1188 ], 1189 structural_oc_oids=[ 1190 AE_ZONE_OID, # aeZone 1191 ], 1192) 1193 1194 1195class AEEntryDNAEMailGroup(GroupEntryDN): 1196 """ 1197 Plugin class for attribute 'entryDN' in aeMailGroup entries 1198 """ 1199 oid: str = 'AEEntryDNAEMailGroup-oid' 1200 desc: str = 'AE-DIR: entryDN of aeGroup entry' 1201 ref_attrs = ( 1202 ('memberOf', 'Members', None, 'Search all member entries of this mail group'), 1203 ( 1204 'aeVisibleGroups', 'Visible', None, 'aeSrvGroup', 1205 'Search all server/service groups (aeSrvGroup)\n' 1206 'on which this mail group is visible' 1207 ), 1208 ) 1209 1210syntax_registry.reg_at( 1211 AEEntryDNAEMailGroup.oid, [ 1212 '1.3.6.1.1.20', # entryDN 1213 ], 1214 structural_oc_oids=[ 1215 AE_MAILGROUP_OID, # aeMailGroup 1216 ], 1217) 1218 1219 1220class AEEntryDNAEGroup(GroupEntryDN): 1221 """ 1222 Plugin class for attribute 'entryDN' in aeGroup entries 1223 """ 1224 oid: str = 'AEEntryDNAEGroup-oid' 1225 desc: str = 'AE-DIR: entryDN of aeGroup entry' 1226 ref_attrs = ( 1227 ('memberOf', 'Members', None, 'Search all member entries of this user group'), 1228 ( 1229 'aeLoginGroups', 'Login', None, 'aeSrvGroup', 1230 'Search all server/service groups (aeSrvGroup)\n' 1231 'on which this user group has login right' 1232 ), 1233 ( 1234 'aeLogStoreGroups', 'View Logs', None, 'aeSrvGroup', 1235 'Search all server/service groups (aeSrvGroup)\n' 1236 'on which this user group has log view right' 1237 ), 1238 ( 1239 'aeSetupGroups', 'Setup', None, 'aeSrvGroup', 1240 'Search all server/service groups (aeSrvGroup)\n' 1241 'on which this user group has setup/installation rights' 1242 ), 1243 ( 1244 'aeVisibleGroups', 'Visible', None, 'aeSrvGroup', 1245 'Search all server/service groups (aeSrvGroup)\n' 1246 'on which this user group is at least visible' 1247 ), 1248 ) 1249 1250 def _additional_links(self): 1251 aegroup_cn = self._entry['cn'][0].decode(self._app.ls.charset) 1252 ref_attrs = list(AEEntryDNAEGroup.ref_attrs) 1253 if aegroup_cn.endswith('zone-admins'): 1254 ref_attrs.extend([ 1255 ( 1256 'aeZoneAdmins', 'Zone Admins', None, 1257 'Search all zones (aeZone)\n' 1258 'for which members of this user group act as zone admins' 1259 ), 1260 ( 1261 'aePasswordAdmins', 'Password Admins', None, 1262 'Search all zones (aeZone)\n' 1263 'for which members of this user group act as password admins' 1264 ), 1265 ]) 1266 if aegroup_cn.endswith('zone-auditors') or aegroup_cn.endswith('zone-admins'): 1267 ref_attrs.append( 1268 ( 1269 'aeZoneAuditors', 'Zone Auditors', None, 1270 'Search all zones (aeZone)\n' 1271 'for which members of this user group act as zone auditors' 1272 ), 1273 ) 1274 self.ref_attrs = tuple(ref_attrs) 1275 res = DistinguishedName._additional_links(self) 1276 res.append(self._app.anchor( 1277 'search', 'SUDO rules', 1278 ( 1279 ('dn', self._dn), 1280 ('search_root', str(self._app.naming_context)), 1281 ('searchform_mode', 'adv'), 1282 ('search_attr', 'sudoUser'), 1283 ('search_option', SEARCH_OPT_IS_EQUAL), 1284 ('search_string', '%'+self._entry['cn'][0].decode(self._app.ls.charset)), 1285 ), 1286 title='Search for SUDO rules\napplicable with this user group', 1287 )) 1288 return res 1289 1290syntax_registry.reg_at( 1291 AEEntryDNAEGroup.oid, [ 1292 '1.3.6.1.1.20', # entryDN 1293 ], 1294 structural_oc_oids=[ 1295 AE_GROUP_OID, # aeGroup 1296 ], 1297) 1298 1299 1300class AEEntryDNAESrvGroup(DistinguishedName): 1301 """ 1302 Plugin class for attribute 'entryDN' in aeSrvGroup entries 1303 """ 1304 oid: str = 'AEEntryDNAESrvGroup-oid' 1305 desc: str = 'AE-DIR: entryDN' 1306 ref_attrs = ( 1307 ( 1308 'aeProxyFor', 'Proxy', None, 'aeSrvGroup', 1309 'Search access gateway/proxy group for this server group' 1310 ), 1311 ( 1312 'aeRequires', 'Required by', None, 'aeSrvGroup', 1313 'Search all service groups depending on this service group.' 1314 ), 1315 ) 1316 1317 def _additional_links(self): 1318 res = DistinguishedName._additional_links(self) 1319 res.append( 1320 self._app.anchor( 1321 'search', 'All members', 1322 ( 1323 ('dn', self._dn), 1324 ('search_root', str(self._app.naming_context)), 1325 ('searchform_mode', 'exp'), 1326 ( 1327 'filterstr', 1328 ( 1329 '(&' 1330 '(|(objectClass=aeHost)(objectClass=aeService))' 1331 '(|(entryDN:dnSubordinateMatch:={0})(aeSrvGroup={0}))' 1332 ')' 1333 ).format(self.av_u) 1334 ), 1335 ), 1336 title=( 1337 'Search all service and host entries ' 1338 'which are member in this service/host group {0}' 1339 ).format(self.av_u), 1340 ) 1341 ) 1342 return res 1343 1344syntax_registry.reg_at( 1345 AEEntryDNAESrvGroup.oid, [ 1346 '1.3.6.1.1.20', # entryDN 1347 ], 1348 structural_oc_oids=[ 1349 AE_SRVGROUP_OID, # aeSrvGroup 1350 ], 1351) 1352 1353 1354class AEEntryDNSudoRule(DistinguishedName): 1355 """ 1356 Plugin class for attribute 'entryDN' in aeSudoRule entries 1357 """ 1358 oid: str = 'AEEntryDNSudoRule-oid' 1359 desc: str = 'AE-DIR: entryDN' 1360 ref_attrs = ( 1361 ( 1362 'aeVisibleSudoers', 'Used on', None, 'aeSrvGroup', 1363 'Search all server groups (aeSrvGroup) referencing this SUDO rule' 1364 ), 1365 ) 1366 1367syntax_registry.reg_at( 1368 AEEntryDNSudoRule.oid, [ 1369 '1.3.6.1.1.20', # entryDN 1370 ], 1371 structural_oc_oids=[ 1372 AE_SUDORULE_OID, # aeSudoRule 1373 ], 1374) 1375 1376 1377class AEEntryDNAELocation(DistinguishedName): 1378 """ 1379 Plugin class for attribute 'entryDN' in aeLocation entries 1380 """ 1381 oid: str = 'AEEntryDNAELocation-oid' 1382 desc: str = 'AE-DIR: entryDN of aeLocation entry' 1383 ref_attrs = ( 1384 ( 1385 'aeLocation', 'Persons', None, 'aePerson', 1386 'Search all persons assigned to this location.' 1387 ), 1388 ( 1389 'aeLocation', 'Zones', None, 'aeZone', 1390 'Search all location-based zones associated with this location.' 1391 ), 1392 ( 1393 'aeLocation', 'Groups', None, 'groupOfEntries', 1394 'Search all location-based zones associated with this location.' 1395 ), 1396 ) 1397 1398syntax_registry.reg_at( 1399 AEEntryDNAELocation.oid, [ 1400 '1.3.6.1.1.20', # entryDN 1401 ], 1402 structural_oc_oids=[ 1403 AE_LOCATION_OID, # aeLocation 1404 ], 1405) 1406 1407 1408class AELocation(AERootDynamicDNSelectList): 1409 """ 1410 Plugin class for attribute 'aeLocation' in various entries 1411 """ 1412 oid: str = 'AELocation-oid' 1413 desc: str = 'AE-DIR: DN of location entry' 1414 ldap_url = 'ldap:///_?displayName?sub?(&(objectClass=aeLocation)(aeStatus=0))' 1415 ref_attrs = AEEntryDNAELocation.ref_attrs 1416 desc_sep: str = '<br>' 1417 1418syntax_registry.reg_at( 1419 AELocation.oid, [ 1420 AE_OID_PREFIX+'.4.35', # aeLocation 1421 ] 1422) 1423 1424 1425class AEEntryDNAEDept(DistinguishedName): 1426 """ 1427 Plugin class for attribute 'entryDN' in aeDept entries 1428 """ 1429 oid: str = 'AEEntryDNAEDept-oid' 1430 desc: str = 'AE-DIR: entryDN of aePerson entry' 1431 ref_attrs = ( 1432 ( 1433 'aeDept', 'Persons', None, 'aePerson', 1434 'Search all persons assigned to this department.' 1435 ), 1436 ( 1437 'aeDept', 'Zones', None, 'aeZone', 1438 'Search all team-related zones associated with this department.' 1439 ), 1440 ( 1441 'aeDept', 'Groups', None, 'groupOfEntries', 1442 'Search all team-related groups associated with this department.' 1443 ), 1444 ) 1445 1446syntax_registry.reg_at( 1447 AEEntryDNAEDept.oid, [ 1448 '1.3.6.1.1.20', # entryDN 1449 ], 1450 structural_oc_oids=[ 1451 AE_DEPT_OID, # aeDept 1452 ], 1453) 1454 1455 1456class AEDept(AERootDynamicDNSelectList): 1457 """ 1458 Plugin class for attribute 'aeDept' in various entries 1459 """ 1460 oid: str = 'AEDept-oid' 1461 desc: str = 'AE-DIR: DN of department entry' 1462 ldap_url = 'ldap:///_?displayName?sub?(&(objectClass=aeDept)(aeStatus=0))' 1463 ref_attrs = AEEntryDNAEDept.ref_attrs 1464 desc_sep: str = '<br>' 1465 1466syntax_registry.reg_at( 1467 AEDept.oid, [ 1468 AE_OID_PREFIX+'.4.29', # aeDept 1469 ] 1470) 1471 1472 1473class AEOwner(AERootDynamicDNSelectList): 1474 """ 1475 Plugin class for attribute 'aeOwner' in aeDevice and aeSession entries 1476 """ 1477 oid: str = 'AEOwner-oid' 1478 desc: str = 'AE-DIR: DN of owner entry' 1479 ldap_url = 'ldap:///_?displayName?sub?(&(objectClass=aePerson)(aeStatus=0))' 1480 ref_attrs = ( 1481 ( 1482 'aeOwner', 'Devices', None, 'aeDevice', 1483 'Search all devices (aeDevice entries) assigned to same owner.' 1484 ), 1485 ) 1486 desc_sep: str = '<br>' 1487 1488syntax_registry.reg_at( 1489 AEOwner.oid, [ 1490 AE_OID_PREFIX+'.4.2', # aeOwner 1491 ] 1492) 1493 1494 1495class AEPerson(DerefDynamicDNSelectList, AEObjectMixIn): 1496 """ 1497 Plugin class for attribute 'aePerson' in aeUser entries 1498 """ 1499 oid: str = 'AEPerson-oid' 1500 desc: str = 'AE-DIR: DN of person entry' 1501 ldap_url = 'ldap:///_?displayName?sub?(objectClass=aePerson)' 1502 ref_attrs = ( 1503 ( 1504 'aePerson', 'Users', None, 'aeUser', 1505 'Search all personal AE-DIR user accounts (aeUser entries) of this person.' 1506 ), 1507 ) 1508 desc_sep: str = '<br>' 1509 ae_status_map = { 1510 -1: (-1, 0), 1511 0: (0,), 1512 1: (0, 1, 2), 1513 2: (0, 1, 2), 1514 } 1515 deref_attrs = ('aeDept', 'aeLocation') 1516 1517 def _status_filter(self): 1518 ae_status = self.ae_status or 0 1519 return compose_filter( 1520 '|', 1521 map_filter_parts( 1522 'aeStatus', 1523 map(str, self.ae_status_map.get(ae_status, [])), 1524 ), 1525 ) 1526 1527 def _filterstr(self): 1528 filter_components = [ 1529 DerefDynamicDNSelectList._filterstr(self), 1530 self._status_filter(), 1531 #ae_validity_filter(), 1532 ] 1533 zone_entry = self._zone_entry(attrlist=self.deref_attrs) 1534 for deref_attr_type in self.deref_attrs: 1535 deref_attr_values = [ 1536 z 1537 for z in zone_entry.get(deref_attr_type, []) 1538 if z 1539 ] 1540 if deref_attr_values: 1541 filter_components.append( 1542 compose_filter( 1543 '|', 1544 map_filter_parts(deref_attr_type, deref_attr_values), 1545 ) 1546 ) 1547 ocs = self._entry.object_class_oid_set() 1548 if 'inetLocalMailRecipient' not in ocs: 1549 filter_components.append('(mail=*)') 1550 filter_str = '(&{})'.format(''.join(filter_components)) 1551 return filter_str 1552 1553 def _validate(self, attr_value: bytes) -> bool: 1554 if self.ae_status == 2: 1555 return True 1556 return DerefDynamicDNSelectList._validate(self, attr_value) 1557 1558 1559syntax_registry.reg_at( 1560 AEPerson.oid, [ 1561 AE_OID_PREFIX+'.4.16', # aePerson 1562 ] 1563) 1564 1565 1566class AEManager(AERootDynamicDNSelectList): 1567 """ 1568 Plugin class for attribute 'aeManager' in aePerson and aeDept entries 1569 """ 1570 oid: str = 'AEManager-oid' 1571 desc: str = 'AE-DIR: Manager responsible for a person/department' 1572 ldap_url = 'ldap:///_?displayName?sub?(&(objectClass=aePerson)(aeStatus=0))' 1573 desc_sep: str = '<br>' 1574 1575syntax_registry.reg_at( 1576 AEManager.oid, [ 1577 '0.9.2342.19200300.100.1.10', # manager 1578 ], 1579 structural_oc_oids=[ 1580 AE_PERSON_OID, # aePerson 1581 AE_DEPT_OID, # aeDept 1582 ] 1583) 1584 1585 1586class AEDerefAttribute(DirectoryString): 1587 """ 1588 Plugin class for attributes referencing other entries 1589 """ 1590 oid: str = 'AEDerefAttribute-oid' 1591 max_values: int = 1 1592 deref_object_class: Optional[str] = None 1593 deref_attribute_type: Optional[str] = None 1594 deref_filter_tmpl: str = ( 1595 '(&(objectClass={deref_object_class})(aeStatus<=0)({attribute_type}=*))' 1596 ) 1597 1598 def _read_person_attr(self): 1599 try: 1600 sre = self._app.ls.l.read_s( 1601 self._entry[self.deref_attribute_type][0].decode(self._app.ls.charset), 1602 attrlist=[self._at], 1603 filterstr=self.deref_filter_tmpl.format( 1604 deref_object_class=self.deref_object_class, 1605 attribute_type=self._at, 1606 ), 1607 ) 1608 except ldap0.LDAPError: 1609 return None 1610 if sre is None: 1611 return None 1612 return sre.entry_s[self._at][0] 1613 1614 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 1615 if self.deref_attribute_type in self._entry: 1616 ae_person_attribute = self._read_person_attr() 1617 if ae_person_attribute is not None: 1618 result = [ae_person_attribute.encode(self._app.ls.charset)] 1619 else: 1620 result = [] 1621 else: 1622 result = attr_values 1623 return result 1624 1625 def form_value(self) -> str: 1626 return '' 1627 1628 def input_field(self) -> Field: 1629 input_field = HiddenInput( 1630 self._at, 1631 ': '.join([self._at, self.desc]), 1632 self.max_len, self.max_values, None, 1633 ) 1634 input_field.charset = self._app.form.accept_charset 1635 input_field.set_default(self.form_value()) 1636 return input_field 1637 1638 1639class AEPersonAttribute(AEDerefAttribute): 1640 """ 1641 Plugin class for aeUser attributes copied from referenced aePerson entries 1642 """ 1643 oid: str = 'AEPersonAttribute-oid' 1644 max_values = 1 1645 deref_object_class = 'aePerson' 1646 deref_attribute_type = 'aePerson' 1647 1648 1649class AEUserNames(AEPersonAttribute, DirectoryString): 1650 """ 1651 Plugin class for aeUser attributes 'sn' and 'givenName' copied 1652 from referenced aePerson entries 1653 """ 1654 oid: str = 'AEUserNames-oid' 1655 1656syntax_registry.reg_at( 1657 AEUserNames.oid, [ 1658 '2.5.4.4', # sn 1659 '2.5.4.42', # givenName 1660 ], 1661 structural_oc_oids=[ 1662 AE_USER_OID, # aeUser 1663 ], 1664) 1665 1666 1667class AEMailLocalAddress(RFC822Address): 1668 """ 1669 Plugin class for attribute 'mailLocalAddress' in aeUser and aeService entries 1670 """ 1671 oid: str = 'AEMailLocalAddress-oid' 1672 sani_funcs = ( 1673 bytes.strip, 1674 bytes.lower, 1675 ) 1676 1677syntax_registry.reg_at( 1678 AEMailLocalAddress.oid, [ 1679 '2.16.840.1.113730.3.1.13', # mailLocalAddress 1680 ], 1681 structural_oc_oids=[ 1682 AE_USER_OID, # aeUser 1683 AE_SERVICE_OID, # aeService 1684 ], 1685) 1686 1687 1688class AEUserMailaddress(AEPersonAttribute, RFC822Address, SelectList): 1689 """ 1690 Plugin class for attribute 'mail' in aeUser entries 1691 1692 For primary mail user accounts this contains one of 1693 the values in attribute 'mailLocalAddress'. 1694 """ 1695 oid: str = 'AEUserMailaddress-oid' 1696 max_values = 1 1697 input_fallback = False 1698 sani_funcs = ( 1699 bytes.strip, 1700 bytes.lower, 1701 ) 1702 1703 def get_attr_value_dict(self) -> Dict[str, str]: 1704 attr_value_dict: Dict[str, str] = { 1705 '': '-/-', 1706 } 1707 for addr in self._entry.get('mailLocalAddress', []): 1708 addr_u = addr.decode(self._app.ls.charset) 1709 attr_value_dict[addr_u] = addr_u 1710 return attr_value_dict 1711 1712 def _is_mail_account(self): 1713 return b'inetLocalMailRecipient' in self._entry['objectClass'] 1714 1715 def _validate(self, attr_value: bytes) -> bool: 1716 if self._is_mail_account(): 1717 return SelectList._validate(self, attr_value) 1718 return AEPersonAttribute._validate(self, attr_value) 1719 1720 def display(self, vidx, links) -> str: 1721 return RFC822Address.display(self, vidx, links) 1722 1723 def form_value(self) -> str: 1724 if self._is_mail_account(): 1725 return SelectList.form_value(self) 1726 return AEPersonAttribute.form_value(self) 1727 1728 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 1729 if self._is_mail_account(): 1730 # make sure only non-empty strings are in attribute value list 1731 if not list(filter(None, map(bytes.strip, attr_values))): 1732 try: 1733 attr_values = [self._entry['mailLocalAddress'][0]] 1734 except KeyError: 1735 attr_values = [] 1736 else: 1737 attr_values = AEPersonAttribute.transmute(self, attr_values) 1738 return attr_values 1739 1740 def input_field(self) -> Field: 1741 if self._is_mail_account(): 1742 return SelectList.input_field(self) 1743 return AEPersonAttribute.input_field(self) 1744 1745syntax_registry.reg_at( 1746 AEUserMailaddress.oid, [ 1747 '0.9.2342.19200300.100.1.3', # mail 1748 ], 1749 structural_oc_oids=[ 1750 AE_USER_OID, # aeUser 1751 ], 1752) 1753 1754 1755class AEPersonMailaddress(DynamicValueSelectList, RFC822Address): 1756 """ 1757 Plugin class for attribute 'mail' in aePerson entries 1758 1759 If there exists a primary mail user account for this person this 1760 contains one of the values in attribute 'mailLocalAddress' in that 1761 aeUser entry. 1762 """ 1763 oid: str = 'AEPersonMailaddress-oid' 1764 max_values = 1 1765 ldap_url = 'ldap:///_?mail,mail?sub?' 1766 input_fallback = True 1767 html_tmpl = RFC822Address.html_tmpl 1768 1769 def _validate(self, attr_value: bytes) -> bool: 1770 if not RFC822Address._validate(self, attr_value): 1771 return False 1772 attr_value_dict: Dict[str, str] = self.get_attr_value_dict() 1773 if ( 1774 not attr_value_dict 1775 or ( 1776 len(attr_value_dict) == 1 1777 and tuple(attr_value_dict.keys()) == ('',) 1778 ) 1779 ): 1780 return True 1781 return DynamicValueSelectList._validate(self, attr_value) 1782 1783 def _filterstr(self): 1784 return ( 1785 '(&' 1786 '(objectClass=aeUser)' 1787 '(objectClass=inetLocalMailRecipient)' 1788 '(aeStatus=0)' 1789 '(aePerson=%s)' 1790 '(mailLocalAddress=*)' 1791 ')' 1792 ) % escape_filter_str(self._dn) 1793 1794syntax_registry.reg_at( 1795 AEPersonMailaddress.oid, [ 1796 '0.9.2342.19200300.100.1.3', # mail 1797 ], 1798 structural_oc_oids=[ 1799 AE_PERSON_OID, # aePerson 1800 ], 1801) 1802 1803 1804class AEDeptAttribute(AEDerefAttribute, DirectoryString): 1805 """ 1806 Plugin class for aePerson attributes copied from referenced aeDept entries 1807 """ 1808 oid: str = 'AEDeptAttribute-oid' 1809 max_values = 1 1810 deref_object_class = 'aeDept' 1811 deref_attribute_type = 'aeDept' 1812 1813syntax_registry.reg_at( 1814 AEDeptAttribute.oid, [ 1815 '2.16.840.1.113730.3.1.2', # departmentNumber 1816 '2.5.4.11', # ou, organizationalUnitName 1817 ], 1818 structural_oc_oids=[ 1819 AE_PERSON_OID, # aePerson 1820 ], 1821) 1822 1823 1824class AEHostname(DNSDomain): 1825 """ 1826 Plugin class for attribute 'host' in aeHost entries 1827 """ 1828 oid: str = 'AEHostname-oid' 1829 desc: str = 'Canonical hostname / FQDN' 1830 host_lookup = 0 1831 1832 def _validate(self, attr_value: bytes) -> bool: 1833 if not DNSDomain._validate(self, attr_value): 1834 return False 1835 if self.host_lookup: 1836 try: 1837 ip_addr = socket.gethostbyname(self._app.ls.uc_decode(attr_value)[0]) 1838 except (socket.gaierror, socket.herror): 1839 return False 1840 if self.host_lookup >= 2: 1841 try: 1842 reverse_hostname = socket.gethostbyaddr(ip_addr)[0] 1843 except (socket.gaierror, socket.herror): 1844 return False 1845 else: 1846 return reverse_hostname == attr_value 1847 return True 1848 1849 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 1850 result = [] 1851 for attr_value in attr_values: 1852 attr_value.lower().strip() 1853 if self.host_lookup: 1854 try: 1855 ip_addr = socket.gethostbyname(self._app.ls.uc_decode(attr_value)[0]) 1856 reverse_hostname = socket.gethostbyaddr(ip_addr)[0] 1857 except (socket.gaierror, socket.herror): 1858 pass 1859 else: 1860 attr_value = reverse_hostname.encode(self._app.ls.charset) 1861 result.append(attr_value) 1862 return attr_values 1863 1864syntax_registry.reg_at( 1865 AEHostname.oid, [ 1866 '0.9.2342.19200300.100.1.9', # host 1867 ], 1868 structural_oc_oids=[ 1869 AE_HOST_OID, # aeHost 1870 ], 1871) 1872 1873 1874class AEDisplayNameUser(ComposedAttribute, DirectoryString): 1875 """ 1876 Plugin class for attribute 'displayName' in aeUser entries 1877 """ 1878 oid: str = 'AEDisplayNameUser-oid' 1879 desc: str = 'Attribute displayName in object class aeUser' 1880 compose_templates = ( 1881 '{givenName} {sn} ({uid}/{uidNumber})', 1882 '{givenName} {sn} ({uid})', 1883 ) 1884 1885syntax_registry.reg_at( 1886 AEDisplayNameUser.oid, [ 1887 '2.16.840.1.113730.3.1.241', # displayName 1888 ], 1889 structural_oc_oids=[AE_USER_OID], # aeUser 1890) 1891 1892 1893class AEDisplayNameContact(ComposedAttribute, DirectoryString): 1894 """ 1895 Plugin class for attribute 'displayName' in aeContact entries 1896 """ 1897 oid: str = 'AEDisplayNameContact-oid' 1898 desc: str = 'Attribute displayName in object class aeContact' 1899 compose_templates = ( 1900 '{cn} <{mail}>', 1901 '{cn}', 1902 ) 1903 1904syntax_registry.reg_at( 1905 AEDisplayNameContact.oid, [ 1906 '2.16.840.1.113730.3.1.241', # displayName 1907 ], 1908 structural_oc_oids=[AE_CONTACT_OID], # aeContact 1909) 1910 1911 1912class AEDisplayNameDept(ComposedAttribute, DirectoryString): 1913 """ 1914 Plugin class for attribute 'displayName' in aeDept entries 1915 """ 1916 oid: str = 'AEDisplayNameDept-oid' 1917 desc: str = 'Attribute displayName in object class aeDept' 1918 compose_templates = ( 1919 '{ou} ({departmentNumber})', 1920 '{ou}', 1921 '#{departmentNumber}', 1922 ) 1923 1924syntax_registry.reg_at( 1925 AEDisplayNameDept.oid, [ 1926 '2.16.840.1.113730.3.1.241', # displayName 1927 ], 1928 structural_oc_oids=[AE_DEPT_OID], # aeDept 1929) 1930 1931 1932class AEDisplayNameLocation(ComposedAttribute, DirectoryString): 1933 """ 1934 Plugin class for attribute 'displayName' in aeLocation entries 1935 """ 1936 oid: str = 'AEDisplayNameLocation-oid' 1937 desc: str = 'Attribute displayName in object class aeLocation' 1938 compose_templates = ( 1939 '{cn}: {l}, {street}', 1940 '{cn}: {l}', 1941 '{cn}: {street}', 1942 '{cn}: {st}', 1943 '{cn}', 1944 ) 1945 1946syntax_registry.reg_at( 1947 AEDisplayNameLocation.oid, [ 1948 '2.16.840.1.113730.3.1.241', # displayName 1949 ], 1950 structural_oc_oids=[AE_LOCATION_OID], # aeLocation 1951) 1952 1953 1954class AEDisplayNamePerson(DisplayNameInetOrgPerson): 1955 """ 1956 Plugin class for attribute 'displayName' in aePerson entries 1957 """ 1958 oid: str = 'AEDisplayNamePerson-oid' 1959 desc: str = 'Attribute displayName in object class aePerson' 1960 # do not stuff confidential employeeNumber herein! 1961 compose_templates = ( 1962 '{givenName} {sn} / {ou}', 1963 '{givenName} {sn} / #{departmentNumber}', 1964 '{givenName} {sn} ({uniqueIdentifier})', 1965 '{givenName} {sn}', 1966 ) 1967 1968syntax_registry.reg_at( 1969 AEDisplayNamePerson.oid, [ 1970 '2.16.840.1.113730.3.1.241', # displayName 1971 ], 1972 structural_oc_oids=[AE_PERSON_OID], # aePerson 1973) 1974 1975 1976class AEUniqueIdentifier(DirectoryString): 1977 """ 1978 Plugin class for attribute 'uniqueIdentifier' in aePerson entries 1979 """ 1980 oid: str = 'AEUniqueIdentifier-oid' 1981 max_values = 1 1982 gen_template = 'web2ldap-{timestamp}' 1983 1984 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 1985 if not attr_values or not attr_values[0].strip(): 1986 return [self.gen_template.format(timestamp=time.time()).encode(self._app.ls.charset)] 1987 return attr_values 1988 1989 def input_field(self) -> Field: 1990 input_field = HiddenInput( 1991 self._at, 1992 ': '.join([self._at, self.desc]), 1993 self.max_len, self.max_values, None, 1994 default=self.form_value(), 1995 ) 1996 input_field.charset = self._app.form.accept_charset 1997 return input_field 1998 1999syntax_registry.reg_at( 2000 AEUniqueIdentifier.oid, [ 2001 '0.9.2342.19200300.100.1.44', # uniqueIdentifier 2002 ], 2003 structural_oc_oids=[ 2004 AE_PERSON_OID, # aePerson 2005 ] 2006) 2007 2008 2009class AEDepartmentNumber(DirectoryString): 2010 """ 2011 Plugin class for attribute 'departmentNumber' in aeDept entries 2012 """ 2013 oid: str = 'AEDepartmentNumber-oid' 2014 max_values = 1 2015 2016syntax_registry.reg_at( 2017 AEDepartmentNumber.oid, [ 2018 '2.16.840.1.113730.3.1.2', # departmentNumber 2019 ], 2020 structural_oc_oids=[ 2021 AE_DEPT_OID, # aeDept 2022 ] 2023) 2024 2025 2026class AECommonName(DirectoryString): 2027 """ 2028 Base class for all plugin classes handling 'cn' in xC6-DIR plugin classes, 2029 not directly used 2030 """ 2031 oid: str = 'AECommonName-oid' 2032 desc: str = 'AE-DIR: common name of aeObject' 2033 max_values = 1 2034 sani_funcs = ( 2035 bytes.strip, 2036 ) 2037 2038 2039class AECommonNameAEZone(AECommonName): 2040 """ 2041 Plugin for attribute 'cn' in aeZone entries 2042 """ 2043 oid: str = 'AECommonNameAEZone-oid' 2044 desc: str = 'AE-DIR: common name of aeZone' 2045 sani_funcs = ( 2046 bytes.strip, 2047 bytes.lower, 2048 ) 2049 2050syntax_registry.reg_at( 2051 AECommonNameAEZone.oid, [ 2052 '2.5.4.3', # cn alias commonName 2053 ], 2054 structural_oc_oids=[ 2055 AE_ZONE_OID, # aeZone 2056 ], 2057) 2058 2059 2060class AECommonNameAELocation(AECommonName): 2061 """ 2062 Plugin for attribute 'cn' in aeLocation entries 2063 """ 2064 oid: str = 'AECommonNameAELocation-oid' 2065 desc: str = 'AE-DIR: common name of aeLocation' 2066 2067syntax_registry.reg_at( 2068 AECommonNameAELocation.oid, [ 2069 '2.5.4.3', # cn alias commonName 2070 ], 2071 structural_oc_oids=[ 2072 AE_LOCATION_OID, # aeLocation 2073 ], 2074) 2075 2076 2077class AECommonNameAEHost(AECommonName): 2078 """ 2079 Plugin for attribute 'cn' in aeHost entries 2080 """ 2081 oid: str = 'AECommonNameAEHost-oid' 2082 desc: str = 'Canonical hostname' 2083 derive_from_host = True 2084 host_begin_item = 0 2085 host_end_item = None 2086 2087 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 2088 if self.derive_from_host: 2089 return list({ 2090 b'.'.join(av.strip().lower().split(b'.')[self.host_begin_item:self.host_end_item]) 2091 for av in self._entry['host'] 2092 }) 2093 return attr_values 2094 2095syntax_registry.reg_at( 2096 AECommonNameAEHost.oid, [ 2097 '2.5.4.3', # cn alias commonName 2098 ], 2099 structural_oc_oids=[ 2100 AE_HOST_OID, # aeHost 2101 ], 2102) 2103 2104 2105class AEZonePrefixCommonName(AECommonName, AEObjectMixIn): 2106 """ 2107 Base class for handling 'cn' in entries which must have zone name as prefix 2108 """ 2109 oid: str = 'AEZonePrefixCommonName-oid' 2110 desc: str = 'AE-DIR: Attribute values have to be prefixed with zone name' 2111 pattern = re.compile(r'^[a-z0-9]+-[a-z0-9-]+$') 2112 special_names = { 2113 'zone-admins', 2114 'zone-auditors', 2115 } 2116 2117 def sanitize(self, attr_value: bytes) -> bytes: 2118 return attr_value.strip() 2119 2120 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 2121 attr_values = [attr_values[0].lower()] 2122 return attr_values 2123 2124 def _validate(self, attr_value: bytes) -> bool: 2125 result = DirectoryString._validate(self, attr_value) 2126 if result and attr_value: 2127 zone_cn = self._get_zone_name() 2128 result = ( 2129 zone_cn and 2130 ( 2131 zone_cn == 'pub' 2132 or attr_value.decode(self._app.ls.charset).startswith(zone_cn+'-') 2133 ) 2134 ) 2135 return result 2136 2137 def form_value(self) -> str: 2138 result = DirectoryString.form_value(self) 2139 zone_cn = self._get_zone_name() 2140 if zone_cn: 2141 if not self._av: 2142 result = zone_cn+'-' 2143 elif self._av_u in self.special_names: 2144 result = '-'.join((zone_cn, self.av_u)) 2145 return result 2146 2147 2148class AECommonNameAEGroup(AEZonePrefixCommonName): 2149 """ 2150 Plugin for attribute 'cn' in aeGroup entries 2151 """ 2152 oid: str = 'AECommonNameAEGroup-oid' 2153 2154syntax_registry.reg_at( 2155 AECommonNameAEGroup.oid, [ 2156 '2.5.4.3', # cn alias commonName 2157 ], 2158 structural_oc_oids=[ 2159 AE_GROUP_OID, # aeGroup 2160 AE_MAILGROUP_OID, # aeMailGroup 2161 ] 2162) 2163 2164 2165class AECommonNameAESrvGroup(AEZonePrefixCommonName): 2166 """ 2167 Plugin for attribute 'cn' in aeSrvGroup entries 2168 """ 2169 oid: str = 'AECommonNameAESrvGroup-oid' 2170 2171syntax_registry.reg_at( 2172 AECommonNameAESrvGroup.oid, [ 2173 '2.5.4.3', # cn alias commonName 2174 ], 2175 structural_oc_oids=[ 2176 AE_SRVGROUP_OID, # aeSrvGroup 2177 ] 2178) 2179 2180 2181class AECommonNameAETag(AEZonePrefixCommonName): 2182 """ 2183 Plugin for attribute 'cn' in aeTag entries 2184 """ 2185 oid: str = 'AECommonNameAETag-oid' 2186 2187 def display(self, vidx, links) -> str: 2188 display_value = AEZonePrefixCommonName.display(self, vidx, links) 2189 if links: 2190 search_anchor = self._app.anchor( 2191 'searchform', '»', 2192 ( 2193 ('dn', self._dn), 2194 ('search_root', str(self._app.naming_context)), 2195 ('searchform_mode', 'adv'), 2196 ('search_attr', 'aeTag'), 2197 ('search_option', SEARCH_OPT_IS_EQUAL), 2198 ('search_string', self.av_u), 2199 ), 2200 title='Search all entries tagged with this tag', 2201 ) 2202 else: 2203 search_anchor = '' 2204 return ''.join((display_value, search_anchor)) 2205 2206syntax_registry.reg_at( 2207 AECommonNameAETag.oid, [ 2208 '2.5.4.3', # cn alias commonName 2209 ], 2210 structural_oc_oids=[ 2211 AE_TAG_OID, # aeTag 2212 ] 2213) 2214 2215 2216class AECommonNameAESudoRule(AEZonePrefixCommonName): 2217 """ 2218 Plugin for attribute 'cn' in aeSudoRule entries 2219 """ 2220 oid: str = 'AECommonNameAESudoRule-oid' 2221 2222syntax_registry.reg_at( 2223 AECommonNameAESudoRule.oid, [ 2224 '2.5.4.3', # cn alias commonName 2225 ], 2226 structural_oc_oids=[ 2227 AE_SUDORULE_OID, # aeSudoRule 2228 ] 2229) 2230 2231syntax_registry.reg_at( 2232 CNInetOrgPerson.oid, [ 2233 '2.5.4.3', # commonName 2234 ], 2235 structural_oc_oids=[ 2236 AE_PERSON_OID, # aePerson 2237 AE_USER_OID, # aeUser 2238 ] 2239) 2240 2241 2242class AESudoRuleDN(AERootDynamicDNSelectList): 2243 """ 2244 Plugin for attribute 'aeVisibleSudoers' in aeSrvGroup entries 2245 """ 2246 oid: str = 'AESudoRuleDN-oid' 2247 desc: str = 'AE-DIR: DN(s) of visible SUDO rules' 2248 ldap_url = 'ldap:///_?cn?sub?(&(objectClass=aeSudoRule)(aeStatus=0))' 2249 2250syntax_registry.reg_at( 2251 AESudoRuleDN.oid, [ 2252 AE_OID_PREFIX+'.4.21', # aeVisibleSudoers 2253 ] 2254) 2255 2256 2257class AENotBefore(NotBefore): 2258 """ 2259 Plugin for attribute 'aeNotBefore' in all aeObject entries 2260 """ 2261 oid: str = 'AENotBefore-oid' 2262 desc: str = 'AE-DIR: begin of validity period' 2263 2264syntax_registry.reg_at( 2265 AENotBefore.oid, [ 2266 AE_OID_PREFIX+'.4.22', # aeNotBefore 2267 ] 2268) 2269 2270 2271class AENotAfter(NotAfter): 2272 """ 2273 Plugin for attribute 'aeNotAfter' in all aeObject entries 2274 """ 2275 oid: str = 'AENotAfter-oid' 2276 desc: str = 'AE-DIR: begin of validity period' 2277 2278 def _validate(self, attr_value: bytes) -> bool: 2279 result = NotAfter._validate(self, attr_value) 2280 if result: 2281 ae_not_after = time.strptime(attr_value.decode('ascii'), '%Y%m%d%H%M%SZ') 2282 if ( 2283 'aeNotBefore' not in self._entry 2284 or not self._entry['aeNotBefore'] 2285 or not self._entry['aeNotBefore'][0] 2286 ): 2287 return True 2288 try: 2289 ae_not_before = time.strptime( 2290 self._entry['aeNotBefore'][0].decode('ascii'), 2291 '%Y%m%d%H%M%SZ', 2292 ) 2293 except KeyError: 2294 result = True 2295 except (UnicodeDecodeError, ValueError): 2296 result = False 2297 else: 2298 result = (ae_not_before <= ae_not_after) 2299 return result 2300 2301syntax_registry.reg_at( 2302 AENotAfter.oid, [ 2303 AE_OID_PREFIX+'.4.23', # aeNotAfter 2304 ] 2305) 2306 2307 2308class AEStatus(SelectList, Integer): 2309 """ 2310 Plugin for attribute 'aeStatus' in all aeObject entries 2311 """ 2312 oid: str = 'AEStatus-oid' 2313 desc: str = 'AE-DIR: Status of object' 2314 attr_value_dict: Dict[str, str] = { 2315 '-1': 'requested', 2316 '0': 'active', 2317 '1': 'deactivated', 2318 '2': 'archived', 2319 } 2320 2321 def _validate(self, attr_value: bytes) -> bool: 2322 result = SelectList._validate(self, attr_value) 2323 if not result or not attr_value: 2324 return result 2325 ae_status = int(attr_value) 2326 current_time = time.gmtime(time.time()) 2327 try: 2328 ae_not_before = time.strptime( 2329 self._entry['aeNotBefore'][0].decode('ascii'), 2330 '%Y%m%d%H%M%SZ', 2331 ) 2332 except (KeyError, IndexError, ValueError, UnicodeDecodeError): 2333 ae_not_before = time.strptime('19700101000000Z', '%Y%m%d%H%M%SZ') 2334 try: 2335 ae_not_after = time.strptime( 2336 self._entry['aeNotAfter'][0].decode('ascii'), 2337 '%Y%m%d%H%M%SZ', 2338 ) 2339 except (KeyError, IndexError, ValueError, UnicodeDecodeError): 2340 ae_not_after = current_time 2341 # see https://www.ae-dir.com/docs.html#schema-validity-period 2342 if current_time > ae_not_after: 2343 result = ae_status >= 1 2344 elif current_time < ae_not_before: 2345 result = ae_status == -1 2346 else: 2347 result = ae_not_before <= current_time <= ae_not_after 2348 return result 2349 2350 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 2351 if not attr_values or not attr_values[0]: 2352 return attr_values 2353 ae_status = int(attr_values[0].decode('ascii')) 2354 current_time = time.gmtime(time.time()) 2355 try: 2356 ae_not_before = time.strptime( 2357 self._entry['aeNotBefore'][0].decode('ascii'), 2358 '%Y%m%d%H%M%SZ', 2359 ) 2360 except (KeyError, IndexError, ValueError): 2361 pass 2362 else: 2363 if ae_status == 0 and current_time < ae_not_before: 2364 ae_status = -1 2365 try: 2366 ae_not_after = time.strptime( 2367 self._entry['aeNotAfter'][0].decode('ascii'), 2368 '%Y%m%d%H%M%SZ', 2369 ) 2370 except (KeyError, IndexError, ValueError): 2371 ae_not_after = None 2372 else: 2373 if current_time > ae_not_after: 2374 try: 2375 ae_expiry_status = int( 2376 self._entry.get('aeExpiryStatus', ['1'])[0].decode('ascii') 2377 ) 2378 except (KeyError, IndexError, ValueError): 2379 pass 2380 else: 2381 ae_status = max(ae_status, ae_expiry_status) 2382 return [str(ae_status).encode('ascii')] 2383 2384 def display(self, vidx, links) -> str: 2385 if not links: 2386 return Integer.display(self, vidx, links) 2387 return SelectList.display(self, vidx, links) 2388 2389syntax_registry.reg_at( 2390 AEStatus.oid, [ 2391 AE_OID_PREFIX+'.4.5', # aeStatus 2392 ] 2393) 2394 2395 2396class AEExpiryStatus(SelectList): 2397 """ 2398 Plugin for attribute 'aeExpiryStatus' in all aeObject entries 2399 """ 2400 oid: str = 'AEExpiryStatus-oid' 2401 desc: str = 'AE-DIR: Expiry status of object' 2402 attr_value_dict: Dict[str, str] = { 2403 '-/-': '', 2404 '1': 'deactivated', 2405 '2': 'archived', 2406 } 2407 2408syntax_registry.reg_at( 2409 AEStatus.oid, [ 2410 AE_OID_PREFIX+'.4.46', # aeExpiryStatus 2411 ] 2412) 2413 2414 2415class AESudoUser(SudoUserGroup): 2416 """ 2417 Plugin for attribute 'sudoUser' in aeSudoRule entries 2418 """ 2419 oid: str = 'AESudoUser-oid' 2420 desc: str = 'AE-DIR: sudoUser' 2421 ldap_url = ( 2422 'ldap:///_?cn,cn?sub?' 2423 '(&' 2424 '(objectClass=aeGroup)' 2425 '(aeStatus=0)' 2426 '(!(|' 2427 '(cn=ae-admins)' 2428 '(cn=ae-auditors)' 2429 '(cn=ae-providers)' 2430 '(cn=ae-replicas)' 2431 '(cn=ae-login-proxies)' 2432 '(cn=*-zone-admins)' 2433 '(cn=*-zone-auditors)' 2434 '))' 2435 ')' 2436 ) 2437 2438syntax_registry.reg_at( 2439 AESudoUser.oid, [ 2440 '1.3.6.1.4.1.15953.9.1.1', # sudoUser 2441 ], 2442 structural_oc_oids=[ 2443 AE_SUDORULE_OID, # aeSudoRule 2444 ] 2445) 2446 2447 2448class AEServiceSshPublicKey(SshPublicKey): 2449 """ 2450 Plugin for attribute 'sshPublicKey' in aeService entries 2451 2452 Mainly this can be used to assign specific regex pattern 2453 e.g. for limiting values to certain OpenSSH key types 2454 in aeService entries. 2455 """ 2456 oid: str = 'AEServiceSshPublicKey-oid' 2457 desc: str = 'AE-DIR: aeService:sshPublicKey' 2458 2459syntax_registry.reg_at( 2460 AEServiceSshPublicKey.oid, [ 2461 '1.3.6.1.4.1.24552.500.1.1.1.13', # sshPublicKey 2462 ], 2463 structural_oc_oids=[ 2464 AE_SERVICE_OID, # aeService 2465 ] 2466) 2467 2468 2469class AEUserSshPublicKey(SshPublicKey): 2470 """ 2471 Plugin for attribute 'sshPublicKey' in aeUser entries 2472 2473 Mainly this can be used to assign specific regex pattern 2474 e.g. for limiting values to certain OpenSSH key types 2475 in aeUser entries. 2476 """ 2477 oid: str = 'AEUserSshPublicKey-oid' 2478 desc: str = 'AE-DIR: aeUser:sshPublicKey' 2479 2480syntax_registry.reg_at( 2481 AEUserSshPublicKey.oid, [ 2482 '1.3.6.1.4.1.24552.500.1.1.1.13', # sshPublicKey 2483 ], 2484 structural_oc_oids=[ 2485 AE_USER_OID, # aeUser 2486 ] 2487) 2488 2489 2490class AEEntryDNAEAuthcToken(DistinguishedName): 2491 """ 2492 Plugin for attribute 'entryDN' in aeAuthcToken entries 2493 """ 2494 oid: str = 'AEEntryDNAEAuthcToken-oid' 2495 desc: str = 'AE-DIR: entryDN of aeAuthcToken entry' 2496 ref_attrs = ( 2497 ( 2498 'oathToken', 'Users', None, 'aeUser', 2499 'Search all personal user accounts using this OATH token.' 2500 ), 2501 ) 2502 2503syntax_registry.reg_at( 2504 AEEntryDNAEAuthcToken.oid, [ 2505 '1.3.6.1.1.20', # entryDN 2506 ], 2507 structural_oc_oids=[ 2508 AE_AUTHCTOKEN_OID, # aeAuthcToken 2509 ], 2510) 2511 2512 2513class AEEntryDNAEPolicy(DistinguishedName): 2514 """ 2515 Plugin for attribute 'entryDN' in aePolicy entries 2516 """ 2517 oid: str = 'AEEntryDNAEPolicy-oid' 2518 desc: str = 'AE-DIR: entryDN of aePolicy entry' 2519 ref_attrs = ( 2520 ( 2521 'pwdPolicySubentry', 'Users', None, 'aeUser', 2522 'Search all personal user accounts restricted by this password policy.' 2523 ), 2524 ( 2525 'pwdPolicySubentry', 'Services', None, 'aeService', 2526 'Search all service accounts restricted by this password policy.' 2527 ), 2528 ( 2529 'pwdPolicySubentry', 'Tokens', None, 'aeAuthcToken', 2530 'Search all authentication tokens restricted by this password policy.' 2531 ), 2532 ( 2533 'oathHOTPParams', 'HOTP Tokens', None, 'oathHOTPToken', 2534 'Search all HOTP tokens affected by this HOTP parameters.' 2535 ), 2536 ( 2537 'oathTOTPParams', 'TOTP Tokens', None, 'oathTOTPToken', 2538 'Search all TOTP tokens affected by this TOTP parameters.' 2539 ), 2540 ) 2541 2542syntax_registry.reg_at( 2543 AEEntryDNAEPolicy.oid, [ 2544 '1.3.6.1.1.20', # entryDN 2545 ], 2546 structural_oc_oids=[ 2547 AE_POLICY_OID, # aePolicy 2548 ], 2549) 2550 2551 2552class AERFC822MailMember(DynamicValueSelectList, AEObjectMixIn): 2553 """ 2554 Plugin for attribute 'rfc822MailMember' in aeMailGroup entries 2555 """ 2556 oid: str = 'AERFC822MailMember-oid' 2557 desc: str = 'AE-DIR: rfc822MailMember' 2558 ldap_url = ( 2559 'ldap:///_?mail,displayName?sub?' 2560 '(&(|(objectClass=inetLocalMailRecipient)(objectClass=aeContact))(mail=*)(aeStatus=0))' 2561 ) 2562 html_tmpl = RFC822Address.html_tmpl 2563 show_val_button = False 2564 2565 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 2566 if 'member' not in self._entry: 2567 return [] 2568 if self.ae_status == 2: 2569 return [] 2570 entrydn_filter = compose_filter( 2571 '|', 2572 map_filter_parts( 2573 'entryDN', 2574 decode_list(self._entry['member'], encoding=self._app.ls.charset), 2575 ), 2576 ) 2577 ldap_result = self._app.ls.l.search_s( 2578 self._search_root(), 2579 ldap0.SCOPE_SUBTREE, 2580 entrydn_filter, 2581 attrlist=['mail'], 2582 ) 2583 mail_addresses = [] 2584 for res in ldap_result or []: 2585 mail_addresses.extend(res.entry_as['mail']) 2586 return sorted(mail_addresses) 2587 2588 def input_field(self) -> Field: 2589 input_field = HiddenInput( 2590 self._at, 2591 ': '.join([self._at, self.desc]), 2592 self.max_len, self.max_values, None, 2593 ) 2594 input_field.charset = self._app.form.accept_charset 2595 input_field.set_default(self.form_value()) 2596 return input_field 2597 2598syntax_registry.reg_at( 2599 AERFC822MailMember.oid, [ 2600 '1.3.6.1.4.1.42.2.27.2.1.15', # rfc822MailMember 2601 ], 2602 structural_oc_oids=[ 2603 AE_MAILGROUP_OID, # aeMailGroup 2604 ] 2605) 2606 2607 2608class AEPwdPolicy(PwdPolicySubentry): 2609 """ 2610 Plugin for attribute 'pwdPolicySubentry' in aeUser, aeService and aeHost entries 2611 """ 2612 oid: str = 'AEPwdPolicy-oid' 2613 desc: str = 'AE-DIR: pwdPolicySubentry' 2614 ldap_url = 'ldap:///_??sub?(&(objectClass=aePolicy)(objectClass=pwdPolicy)(aeStatus=0))' 2615 2616syntax_registry.reg_at( 2617 AEPwdPolicy.oid, [ 2618 '1.3.6.1.4.1.42.2.27.8.1.23', # pwdPolicySubentry 2619 ], 2620 structural_oc_oids=[ 2621 AE_USER_OID, # aeUser 2622 AE_SERVICE_OID, # aeService 2623 AE_HOST_OID, # aeHost 2624 ] 2625) 2626 2627 2628class AESudoHost(IA5String): 2629 """ 2630 Plugin for attribute 'sudoHost' in aeSudoRule entries 2631 """ 2632 oid: str = 'AESudoHost-oid' 2633 desc: str = 'AE-DIR: sudoHost' 2634 max_values = 1 2635 2636 def transmute(self, attr_values: List[bytes]) -> List[bytes]: 2637 return [b'ALL'] 2638 2639 def input_field(self) -> Field: 2640 input_field = HiddenInput( 2641 self._at, 2642 ': '.join([self._at, self.desc]), 2643 self.max_len, self.max_values, None, 2644 default=self.form_value() 2645 ) 2646 input_field.charset = self._app.form.accept_charset 2647 return input_field 2648 2649syntax_registry.reg_at( 2650 AESudoHost.oid, [ 2651 '1.3.6.1.4.1.15953.9.1.2', # sudoHost 2652 ], 2653 structural_oc_oids=[ 2654 AE_SUDORULE_OID, # aeSudoRule 2655 ] 2656) 2657 2658 2659class AELoginShell(Shell): 2660 """ 2661 Plugin for attribute 'loginShell' in aeUser and aeService entries 2662 """ 2663 oid: str = 'AELoginShell-oid' 2664 desc: str = 'AE-DIR: Login shell for POSIX users' 2665 attr_value_dict: Dict[str, str] = { 2666 '/bin/bash': '/bin/bash', 2667 '/bin/true': '/bin/true', 2668 '/bin/false': '/bin/false', 2669 } 2670 2671syntax_registry.reg_at( 2672 AELoginShell.oid, [ 2673 '1.3.6.1.1.1.1.4', # loginShell 2674 ], 2675 structural_oc_oids=[ 2676 AE_USER_OID, # aeUser 2677 AE_SERVICE_OID, # aeService 2678 ] 2679) 2680 2681 2682class AEOathHOTPToken(OathHOTPToken): 2683 """ 2684 Plugin for attribute 'oathHOTPToken' in aeUser entries 2685 """ 2686 oid: str = 'AEOathHOTPToken-oid' 2687 desc: str = 'DN of the associated oathHOTPToken entry in aeUser entry' 2688 ref_attrs = ( 2689 (None, 'Users', None, None), 2690 ) 2691 input_fallback = False 2692 2693 def _filterstr(self): 2694 if 'aePerson' in self._entry: 2695 return '(&{0}(aeOwner={1}))'.format( 2696 OathHOTPToken._filterstr(self), 2697 escape_filter_str( 2698 self._entry['aePerson'][0].decode(self._app.form.accept_charset) 2699 ), 2700 ) 2701 return OathHOTPToken._filterstr(self) 2702 2703syntax_registry.reg_at( 2704 AEOathHOTPToken.oid, [ 2705 '1.3.6.1.4.1.5427.1.389.4226.4.9.1', # oathHOTPToken 2706 ], 2707 structural_oc_oids=[AE_USER_OID], # aeUser 2708) 2709 2710 2711# see sshd(AUTHORIZED_KEYS FILE FORMAT 2712# and the -O option in ssh-keygen(1) 2713class AESSHPermissions(SelectList): 2714 """ 2715 Plugin for attribute 'aeSSHPermissions' in aeUser and aeService entries 2716 """ 2717 oid: str = 'AESSHPermissions-oid' 2718 desc: str = 'AE-DIR: Status of object' 2719 attr_value_dict: Dict[str, str] = { 2720 'pty': 'PTY allocation', 2721 'X11-forwarding': 'X11 forwarding', 2722 'agent-forwarding': 'Key agent forwarding', 2723 'port-forwarding': 'Port forwarding', 2724 'user-rc': 'Execute ~/.ssh/rc', 2725 } 2726 2727syntax_registry.reg_at( 2728 AESSHPermissions.oid, [ 2729 AE_OID_PREFIX+'.4.47', # aeSSHPermissions 2730 ] 2731) 2732 2733 2734class AERemoteHostAEHost(DynamicValueSelectList): 2735 """ 2736 Plugin for attribute 'aeRemoteHost' in aeHost entries 2737 """ 2738 oid: str = 'AERemoteHostAEHost-oid' 2739 desc: str = 'AE-DIR: aeRemoteHost in aeHost entry' 2740 ldap_url = 'ldap:///.?ipHostNumber,aeFqdn?one?(&(objectClass=aeNwDevice)(aeStatus=0))' 2741 input_fallback = True # fallback to normal input field 2742 2743syntax_registry.reg_at( 2744 AERemoteHostAEHost.oid, [ 2745 AE_OID_PREFIX+'.4.8', # aeRemoteHost 2746 ], 2747 structural_oc_oids=[AE_HOST_OID], # aeHost 2748) 2749 2750 2751class AEDescriptionAENwDevice(ComposedAttribute): 2752 """ 2753 Plugin for attribute 'description' in aeNwDevice entries 2754 """ 2755 oid: str = 'AEDescriptionAENwDevice-oid' 2756 desc: str = 'Attribute description in object class aeNwDevice' 2757 compose_templates = ( 2758 '{cn}: {aeFqdn} / {ipHostNumber}', 2759 '{cn}: {ipHostNumber}', 2760 ) 2761 2762syntax_registry.reg_at( 2763 AEDescriptionAENwDevice.oid, [ 2764 '2.5.4.13', # description 2765 ], 2766 structural_oc_oids=[AE_NWDEVICE_OID], # aeNwDevice 2767) 2768 2769 2770class AEChildClasses(SelectList): 2771 """ 2772 Plugin for attribute 'aeChildClasses' in aeZone entries 2773 """ 2774 oid = 'AEChildClasses-oid' 2775 desc = 'AE-DIR: Structural object classes allowed to be added in child entries' 2776 attr_value_dict: Dict[str, str] = { 2777 '-/-': '', 2778 'aeAuthcToken': 'Authentication Token (aeAuthcToken)', 2779 'aeContact': 'Contact (aeContact)', 2780 'aeDept': 'Department (aeDept)', 2781 'aeLocation': 'Location (aeLocation)', 2782 'aeMailGroup': 'Mail Group (aeMailGroup)', 2783 'aePerson': 'Person (aePerson)', 2784 'aePolicy': 'Policy (aePolicy)', 2785 'aeService': 'Service/tool Account (aeService)', 2786 'aeSrvGroup': 'Service Group (aeSrvGroup)', 2787 'aeSudoRule': 'Sudoers Rule (sudoRole)', 2788 'aeUser': 'User account (aeUser)', 2789 'aeGroup': 'User group (aeGroup)', 2790 'aeTag': 'Tag (aeTag)', 2791 } 2792 2793syntax_registry.reg_at( 2794 AEChildClasses.oid, [ 2795 AE_OID_PREFIX+'.4.49', # aeChildClasses 2796 ] 2797) 2798 2799 2800# Register all syntax classes in this module 2801syntax_registry.reg_syntaxes(__name__) 2802