Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rfc4512.py 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  1. """
  2. """
  3. # Created on 2013.09.11
  4. #
  5. # Author: Giovanni Cannata
  6. #
  7. # Copyright 2013 - 2018 Giovanni Cannata
  8. #
  9. # This file is part of ldap3.
  10. #
  11. # ldap3 is free software: you can redistribute it and/or modify
  12. # it under the terms of the GNU Lesser General Public License as published
  13. # by the Free Software Foundation, either version 3 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # ldap3 is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU Lesser General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU Lesser General Public License
  22. # along with ldap3 in the COPYING and COPYING.LESSER files.
  23. # If not, see <http://www.gnu.org/licenses/>.
  24. from os import linesep
  25. import re
  26. import json
  27. from .oid import CLASS_ABSTRACT, CLASS_STRUCTURAL, CLASS_AUXILIARY, ATTRIBUTE_USER_APPLICATION, \
  28. ATTRIBUTE_DIRECTORY_OPERATION, ATTRIBUTE_DISTRIBUTED_OPERATION, ATTRIBUTE_DSA_OPERATION
  29. from .. import SEQUENCE_TYPES, STRING_TYPES, get_config_parameter
  30. from ..utils.conv import escape_bytes, json_hook, check_json_dict, format_json, to_unicode
  31. from ..utils.ciDict import CaseInsensitiveDict
  32. from ..protocol.formatters.standard import format_attribute_values
  33. from .oid import Oids, decode_oids, decode_syntax, oid_to_string
  34. from ..core.exceptions import LDAPSchemaError, LDAPDefinitionError
  35. def constant_to_class_kind(value):
  36. if value == CLASS_STRUCTURAL:
  37. return 'Structural'
  38. elif value == CLASS_ABSTRACT:
  39. return 'Abstract'
  40. elif value == CLASS_AUXILIARY:
  41. return 'Auxiliary'
  42. else:
  43. return '<unknown>'
  44. def constant_to_attribute_usage(value):
  45. if value == ATTRIBUTE_USER_APPLICATION:
  46. return 'User Application'
  47. elif value == ATTRIBUTE_DIRECTORY_OPERATION:
  48. return "Directory operation"
  49. elif value == ATTRIBUTE_DISTRIBUTED_OPERATION:
  50. return 'Distributed operation'
  51. elif value == ATTRIBUTE_DSA_OPERATION:
  52. return 'DSA operation'
  53. else:
  54. return 'unknown'
  55. def attribute_usage_to_constant(value):
  56. if value == 'userApplications':
  57. return ATTRIBUTE_USER_APPLICATION
  58. elif value == 'directoryOperation':
  59. return ATTRIBUTE_DIRECTORY_OPERATION
  60. elif value == 'distributedOperation':
  61. return ATTRIBUTE_DISTRIBUTED_OPERATION
  62. elif value == 'dsaOperation':
  63. return ATTRIBUTE_DSA_OPERATION
  64. else:
  65. return 'unknown'
  66. def quoted_string_to_list(quoted_string):
  67. string = quoted_string.strip()
  68. if not string:
  69. return list()
  70. if string[0] == '(' and string[-1] == ')':
  71. string = string[1:-1]
  72. elements = string.split("'")
  73. # return [check_escape(element.strip("'").strip()) for element in elements if element.strip()]
  74. return [element.strip("'").strip() for element in elements if element.strip()]
  75. def oids_string_to_list(oid_string):
  76. string = oid_string.strip()
  77. if string[0] == '(' and string[-1] == ')':
  78. string = string[1:-1]
  79. elements = string.split('$')
  80. return [element.strip() for element in elements if element.strip()]
  81. def extension_to_tuple(extension_string):
  82. string = extension_string.strip()
  83. name, _, values = string.partition(' ')
  84. return name, quoted_string_to_list(values)
  85. def list_to_string(list_object):
  86. if not isinstance(list_object, SEQUENCE_TYPES):
  87. return list_object
  88. r = ''
  89. for element in list_object:
  90. r += (list_to_string(element) if isinstance(element, SEQUENCE_TYPES) else str(element)) + ', '
  91. return r[:-2] if r else ''
  92. class BaseServerInfo(object):
  93. def __init__(self, raw_attributes):
  94. self.raw = dict(raw_attributes)
  95. @classmethod
  96. def from_json(cls, json_definition, schema=None, custom_formatter=None):
  97. conf_case_insensitive_schema = get_config_parameter('CASE_INSENSITIVE_SCHEMA_NAMES')
  98. definition = json.loads(json_definition, object_hook=json_hook)
  99. if 'raw' not in definition or 'type' not in definition:
  100. raise LDAPDefinitionError('invalid JSON definition')
  101. if conf_case_insensitive_schema:
  102. attributes = CaseInsensitiveDict()
  103. else:
  104. attributes = dict()
  105. if schema:
  106. for attribute in definition['raw']:
  107. # attributes[attribute] = format_attribute_values(schema, check_escape(attribute), [check_escape(value) for value in definition['raw'][attribute]], custom_formatter)
  108. attributes[attribute] = format_attribute_values(schema, attribute, [value for value in definition['raw'][attribute]], custom_formatter)
  109. else:
  110. for attribute in definition['raw']:
  111. # attributes[attribute] = [check_escape(value) for value in definition['raw'][attribute]]
  112. attributes[attribute] = [value for value in definition['raw'][attribute]]
  113. if cls.__name__ != definition['type']:
  114. raise LDAPDefinitionError('JSON info not of type ' + cls.__name__)
  115. if definition['type'] == 'DsaInfo':
  116. return DsaInfo(attributes, definition['raw'])
  117. elif definition['type'] == 'SchemaInfo':
  118. if 'schema_entry' not in definition:
  119. raise LDAPDefinitionError('invalid schema in JSON')
  120. return SchemaInfo(definition['schema_entry'], attributes, definition['raw'])
  121. raise LDAPDefinitionError('invalid Info type ' + str(definition['type']) + ' in JSON definition')
  122. @classmethod
  123. def from_file(cls, target, schema=None, custom_formatter=None):
  124. if isinstance(target, STRING_TYPES):
  125. target = open(target, 'r')
  126. new = cls.from_json(target.read(), schema=schema, custom_formatter=custom_formatter)
  127. target.close()
  128. return new
  129. def to_file(self,
  130. target,
  131. indent=4,
  132. sort=True):
  133. if isinstance(target, STRING_TYPES):
  134. target = open(target, 'w+')
  135. target.writelines(self.to_json(indent=indent, sort=sort))
  136. target.close()
  137. def __str__(self):
  138. return self.__repr__()
  139. def to_json(self,
  140. indent=4,
  141. sort=True):
  142. json_dict = dict()
  143. json_dict['type'] = self.__class__.__name__
  144. json_dict['raw'] = self.raw
  145. if isinstance(self, SchemaInfo):
  146. json_dict['schema_entry'] = self.schema_entry
  147. elif isinstance(self, DsaInfo):
  148. pass
  149. else:
  150. raise LDAPDefinitionError('unable to convert ' + str(self) + ' to JSON')
  151. if str is bytes: # Python 2
  152. check_json_dict(json_dict)
  153. return json.dumps(json_dict, ensure_ascii=False, sort_keys=sort, indent=indent, check_circular=True, default=format_json, separators=(',', ': '))
  154. class DsaInfo(BaseServerInfo):
  155. """
  156. This class contains info about the ldap server (DSA) read from DSE
  157. as defined in RFC4512 and RFC3045. Unknown attributes are stored in the "other" dict
  158. """
  159. def __init__(self, attributes, raw_attributes):
  160. BaseServerInfo.__init__(self, raw_attributes)
  161. self.alt_servers = attributes.pop('altServer', None)
  162. self.naming_contexts = attributes.pop('namingContexts', None)
  163. self.supported_controls = decode_oids(attributes.pop('supportedControl', None))
  164. self.supported_extensions = decode_oids(attributes.pop('supportedExtension', None))
  165. self.supported_features = decode_oids(attributes.pop('supportedFeatures', None)) + decode_oids(attributes.pop('supportedCapabilities', None))
  166. self.supported_ldap_versions = attributes.pop('supportedLDAPVersion', None)
  167. self.supported_sasl_mechanisms = attributes.pop('supportedSASLMechanisms', None)
  168. self.vendor_name = attributes.pop('vendorName', None)
  169. self.vendor_version = attributes.pop('vendorVersion', None)
  170. self.schema_entry = attributes.pop('subschemaSubentry', None)
  171. self.other = attributes # remaining schema definition attributes not in RFC4512
  172. def __repr__(self):
  173. r = 'DSA info (from DSE):' + linesep
  174. if self.supported_ldap_versions:
  175. if isinstance(self.supported_ldap_versions, SEQUENCE_TYPES):
  176. r += (' Supported LDAP versions: ' + ', '.join([str(s) for s in self.supported_ldap_versions])) if self.supported_ldap_versions else ''
  177. else:
  178. r += (' Supported LDAP versions: ' + str(self.supported_ldap_versions))
  179. r += linesep
  180. if self.naming_contexts:
  181. if isinstance(self.naming_contexts, SEQUENCE_TYPES):
  182. r += (' Naming contexts: ' + linesep + linesep.join([' ' + str(s) for s in self.naming_contexts])) if self.naming_contexts else ''
  183. else:
  184. r += (' Naming contexts: ' + str(self.naming_contexts))
  185. r += linesep
  186. if self.alt_servers:
  187. if isinstance(self.alt_servers, SEQUENCE_TYPES):
  188. r += (' Alternative servers: ' + linesep + linesep.join([' ' + str(s) for s in self.alt_servers])) if self.alt_servers else ''
  189. else:
  190. r += (' Alternative servers: ' + str(self.alt_servers))
  191. r += linesep
  192. if self.supported_controls:
  193. if isinstance(self.supported_controls, SEQUENCE_TYPES):
  194. r += (' Supported controls: ' + linesep + linesep.join([' ' + oid_to_string(s) for s in self.supported_controls])) if self.supported_controls else ''
  195. else:
  196. r += (' Supported controls: ' + str(self.supported_controls))
  197. r += linesep
  198. if self.supported_extensions:
  199. if isinstance(self.supported_extensions, SEQUENCE_TYPES):
  200. r += (' Supported extensions: ' + linesep + linesep.join([' ' + oid_to_string(s) for s in self.supported_extensions])) if self.supported_extensions else ''
  201. else:
  202. r += (' Supported extensions: ' + str(self.supported_extensions))
  203. r += linesep
  204. if self.supported_features:
  205. if self.supported_features:
  206. if isinstance(self.supported_features, SEQUENCE_TYPES):
  207. r += (' Supported features: ' + linesep + linesep.join([' ' + oid_to_string(s) for s in self.supported_features])) if self.supported_features else ''
  208. else:
  209. r += (' Supported features: ' + str(self.supported_features))
  210. r += linesep
  211. if self.supported_sasl_mechanisms:
  212. if isinstance(self.supported_sasl_mechanisms, SEQUENCE_TYPES):
  213. r += (' Supported SASL mechanisms: ' + linesep + ' ' + ', '.join([str(s) for s in self.supported_sasl_mechanisms])) if self.supported_sasl_mechanisms else ''
  214. else:
  215. r += (' Supported SASL mechanisms: ' + str(self.supported_sasl_mechanisms))
  216. r += linesep
  217. if self.schema_entry:
  218. if isinstance(self.schema_entry, SEQUENCE_TYPES):
  219. r += (' Schema entry: ' + linesep + linesep.join([' ' + str(s) for s in self.schema_entry])) if self.schema_entry else ''
  220. else:
  221. r += (' Schema entry: ' + str(self.schema_entry))
  222. r += linesep
  223. if self.vendor_name:
  224. if isinstance(self.vendor_name, SEQUENCE_TYPES) and len(self.vendor_name) == 1:
  225. r += 'Vendor name: ' + self.vendor_name[0]
  226. else:
  227. r += 'Vendor name: ' + str(self.vendor_name)
  228. r += linesep
  229. if self.vendor_version:
  230. if isinstance(self.vendor_version, SEQUENCE_TYPES) and len(self.vendor_version) == 1:
  231. r += 'Vendor version: ' + self.vendor_version[0]
  232. else:
  233. r += 'Vendor version: ' + str(self.vendor_version)
  234. r += linesep
  235. r += 'Other:' + linesep
  236. for k, v in self.other.items():
  237. r += ' ' + str(k) + ': ' + linesep
  238. try:
  239. r += (linesep.join([' ' + str(s) for s in v])) if isinstance(v, SEQUENCE_TYPES) else str(v)
  240. except UnicodeDecodeError:
  241. r += (linesep.join([' ' + str(escape_bytes(s)) for s in v])) if isinstance(v, SEQUENCE_TYPES) else str(escape_bytes(v))
  242. r += linesep
  243. return r
  244. class SchemaInfo(BaseServerInfo):
  245. """
  246. This class contains info about the ldap server schema read from an entry (default entry is DSE)
  247. as defined in RFC4512. Unknown attributes are stored in the "other" dict
  248. """
  249. def __init__(self, schema_entry, attributes, raw_attributes):
  250. BaseServerInfo.__init__(self, raw_attributes)
  251. self.schema_entry = schema_entry
  252. self.create_time_stamp = attributes.pop('createTimestamp', None)
  253. self.modify_time_stamp = attributes.pop('modifyTimestamp', None)
  254. self.attribute_types = AttributeTypeInfo.from_definition(attributes.pop('attributeTypes', []))
  255. self.object_classes = ObjectClassInfo.from_definition(attributes.pop('objectClasses', []))
  256. self.matching_rules = MatchingRuleInfo.from_definition(attributes.pop('matchingRules', []))
  257. self.matching_rule_uses = MatchingRuleUseInfo.from_definition(attributes.pop('matchingRuleUse', []))
  258. self.dit_content_rules = DitContentRuleInfo.from_definition(attributes.pop('dITContentRules', []))
  259. self.dit_structure_rules = DitStructureRuleInfo.from_definition(attributes.pop('dITStructureRules', []))
  260. self.name_forms = NameFormInfo.from_definition(attributes.pop('nameForms', []))
  261. self.ldap_syntaxes = LdapSyntaxInfo.from_definition(attributes.pop('ldapSyntaxes', []))
  262. self.other = attributes # remaining schema definition attributes not in RFC4512
  263. # links attributes to class objects
  264. if self.object_classes and self.attribute_types:
  265. for object_class in self.object_classes: # CaseInsensitiveDict return keys while iterating
  266. for attribute in self.object_classes[object_class].must_contain:
  267. try:
  268. self.attribute_types[attribute].mandatory_in.append(object_class)
  269. except KeyError:
  270. pass
  271. for attribute in self.object_classes[object_class].may_contain:
  272. try:
  273. self.attribute_types[attribute].optional_in.append(object_class)
  274. except KeyError:
  275. pass
  276. def is_valid(self):
  277. if self.object_classes or self.attribute_types or self.matching_rules or self.matching_rule_uses or self.dit_content_rules or self.dit_structure_rules or self.name_forms or self.ldap_syntaxes:
  278. return True
  279. return False
  280. def __repr__(self):
  281. r = 'DSA Schema from: ' + self.schema_entry
  282. r += linesep
  283. if isinstance(self.attribute_types, SEQUENCE_TYPES):
  284. r += (' Attribute types:' + linesep + ' ' + ', '.join([str(self.attribute_types[s]) for s in self.attribute_types])) if self.attribute_types else ''
  285. else:
  286. r += (' Attribute types:' + str(self.attribute_types))
  287. r += linesep
  288. if isinstance(self.object_classes, SEQUENCE_TYPES):
  289. r += (' Object classes:' + linesep + ' ' + ', '.join([str(self.object_classes[s]) for s in self.object_classes])) if self.object_classes else ''
  290. else:
  291. r += (' Object classes:' + str(self.object_classes))
  292. r += linesep
  293. if isinstance(self.matching_rules, SEQUENCE_TYPES):
  294. r += (' Matching rules:' + linesep + ' ' + ', '.join([str(self.matching_rules[s]) for s in self.matching_rules])) if self.matching_rules else ''
  295. else:
  296. r += (' Matching rules:' + str(self.matching_rules))
  297. r += linesep
  298. if isinstance(self.matching_rule_uses, SEQUENCE_TYPES):
  299. r += (' Matching rule uses:' + linesep + ' ' + ', '.join([str(self.matching_rule_uses[s]) for s in self.matching_rule_uses])) if self.matching_rule_uses else ''
  300. else:
  301. r += (' Matching rule uses:' + str(self.matching_rule_uses))
  302. r += linesep
  303. if isinstance(self.dit_content_rules, SEQUENCE_TYPES):
  304. r += (' DIT content rules:' + linesep + ' ' + ', '.join([str(self.dit_content_rules[s]) for s in self.dit_content_rules])) if self.dit_content_rules else ''
  305. else:
  306. r += (' DIT content rules:' + str(self.dit_content_rules))
  307. r += linesep
  308. if isinstance(self.dit_structure_rules, SEQUENCE_TYPES):
  309. r += (' DIT structure rules:' + linesep + ' ' + ', '.join([str(self.dit_structure_rules[s]) for s in self.dit_structure_rules])) if self.dit_structure_rules else ''
  310. else:
  311. r += (' DIT structure rules:' + str(self.dit_structure_rules))
  312. r += linesep
  313. if isinstance(self.name_forms, SEQUENCE_TYPES):
  314. r += (' Name forms:' + linesep + ' ' + ', '.join([str(self.name_forms[s]) for s in self.name_forms])) if self.name_forms else ''
  315. else:
  316. r += (' Name forms:' + str(self.name_forms))
  317. r += linesep
  318. if isinstance(self.ldap_syntaxes, SEQUENCE_TYPES):
  319. r += (' LDAP syntaxes:' + linesep + ' ' + ', '.join([str(self.ldap_syntaxes[s]) for s in self.ldap_syntaxes])) if self.ldap_syntaxes else ''
  320. else:
  321. r += (' LDAP syntaxes:' + str(self.ldap_syntaxes))
  322. r += linesep
  323. r += 'Other:' + linesep
  324. for k, v in self.other.items():
  325. r += ' ' + str(k) + ': ' + linesep
  326. try:
  327. r += (linesep.join([' ' + str(s) for s in v])) if isinstance(v, SEQUENCE_TYPES) else str(v)
  328. except UnicodeDecodeError:
  329. r += (linesep.join([' ' + str(escape_bytes(s)) for s in v])) if isinstance(v, SEQUENCE_TYPES) else str(escape_bytes(v))
  330. r += linesep
  331. return r
  332. class BaseObjectInfo(object):
  333. """
  334. Base class for objects defined in the schema as per RFC4512
  335. """
  336. def __init__(self,
  337. oid=None,
  338. name=None,
  339. description=None,
  340. obsolete=False,
  341. extensions=None,
  342. experimental=None,
  343. definition=None):
  344. self.oid = oid
  345. self.name = name
  346. self.description = description
  347. self.obsolete = obsolete
  348. self.extensions = extensions
  349. self.experimental = experimental
  350. self.raw_definition = definition
  351. self._oid_info = None
  352. @property
  353. def oid_info(self):
  354. if self._oid_info is None and self.oid:
  355. self._oid_info = Oids.get(self.oid, '')
  356. return self._oid_info if self._oid_info else None
  357. def __str__(self):
  358. return self.__repr__()
  359. def __repr__(self):
  360. r = ': ' + self.oid
  361. r += ' [OBSOLETE]' if self.obsolete else ''
  362. r += (linesep + ' Short name: ' + list_to_string(self.name)) if self.name else ''
  363. r += (linesep + ' Description: ' + self.description) if self.description else ''
  364. r += '<__desc__>'
  365. r += (linesep + ' Extensions:' + linesep + linesep.join([' ' + s[0] + ': ' + list_to_string(s[1]) for s in self.extensions])) if self.extensions else ''
  366. r += (linesep + ' Experimental:' + linesep + linesep.join([' ' + s[0] + ': ' + list_to_string(s[1]) for s in self.experimental])) if self.experimental else ''
  367. r += (linesep + ' OidInfo: ' + str(self.oid_info)) if self.oid_info else ''
  368. r += linesep
  369. return r
  370. @classmethod
  371. def from_definition(cls, definitions):
  372. conf_case_insensitive_schema = get_config_parameter('CASE_INSENSITIVE_SCHEMA_NAMES')
  373. conf_ignore_malformed_schema = get_config_parameter('IGNORE_MALFORMED_SCHEMA')
  374. ret_dict = CaseInsensitiveDict() if conf_case_insensitive_schema else dict()
  375. if not definitions:
  376. return CaseInsensitiveDict() if conf_case_insensitive_schema else dict()
  377. for object_definition in definitions:
  378. object_definition = to_unicode(object_definition.strip(), from_server=True)
  379. if object_definition[0] == '(' and object_definition[-1] == ')':
  380. if cls is MatchingRuleInfo:
  381. pattern = '| SYNTAX '
  382. elif cls is ObjectClassInfo:
  383. pattern = '| SUP | ABSTRACT| STRUCTURAL| AUXILIARY| MUST | MAY '
  384. elif cls is AttributeTypeInfo:
  385. pattern = '| SUP | EQUALITY | ORDERING | SUBSTR | SYNTAX | SINGLE-VALUE| COLLECTIVE| NO-USER-MODIFICATION| USAGE '
  386. elif cls is MatchingRuleUseInfo:
  387. pattern = '| APPLIES '
  388. elif cls is LdapSyntaxInfo:
  389. pattern = ''
  390. elif cls is DitContentRuleInfo:
  391. pattern = '| AUX | MUST | MAY | NOT '
  392. elif cls is DitStructureRuleInfo:
  393. pattern = '| FORM | SUP '
  394. elif cls is NameFormInfo:
  395. pattern = '| OC | MUST | MAY '
  396. else:
  397. raise LDAPSchemaError('unknown schema definition class')
  398. splitted = re.split('( NAME | DESC | OBSOLETE| X-| E-' + pattern + ')', object_definition[1:-1])
  399. values = splitted[::2]
  400. separators = splitted[1::2]
  401. separators.insert(0, 'OID')
  402. defs = list(zip(separators, values))
  403. object_def = cls()
  404. for d in defs:
  405. key = d[0].strip()
  406. value = d[1].strip()
  407. if key == 'OID':
  408. object_def.oid = value
  409. elif key == 'NAME':
  410. object_def.name = quoted_string_to_list(value)
  411. elif key == 'DESC':
  412. object_def.description = value.strip("'")
  413. elif key == 'OBSOLETE':
  414. object_def.obsolete = True
  415. elif key == 'SYNTAX':
  416. object_def.syntax = oids_string_to_list(value)
  417. elif key == 'SUP':
  418. object_def.superior = oids_string_to_list(value)
  419. elif key == 'ABSTRACT':
  420. object_def.kind = CLASS_ABSTRACT
  421. elif key == 'STRUCTURAL':
  422. object_def.kind = CLASS_STRUCTURAL
  423. elif key == 'AUXILIARY':
  424. object_def.kind = CLASS_AUXILIARY
  425. elif key == 'MUST':
  426. object_def.must_contain = oids_string_to_list(value)
  427. elif key == 'MAY':
  428. object_def.may_contain = oids_string_to_list(value)
  429. elif key == 'EQUALITY':
  430. object_def.equality = oids_string_to_list(value)
  431. elif key == 'ORDERING':
  432. object_def.ordering = oids_string_to_list(value)
  433. elif key == 'SUBSTR':
  434. object_def.substr = oids_string_to_list(value)
  435. elif key == 'SINGLE-VALUE':
  436. object_def.single_value = True
  437. elif key == 'COLLECTIVE':
  438. object_def.collective = True
  439. elif key == 'NO-USER-MODIFICATION':
  440. object_def.no_user_modification = True
  441. elif key == 'USAGE':
  442. object_def.usage = attribute_usage_to_constant(value)
  443. elif key == 'APPLIES':
  444. object_def.apply_to = oids_string_to_list(value)
  445. elif key == 'AUX':
  446. object_def.auxiliary_classes = oids_string_to_list(value)
  447. elif key == 'FORM':
  448. object_def.name_form = oids_string_to_list(value)
  449. elif key == 'OC':
  450. object_def.object_class = oids_string_to_list(value)
  451. elif key == 'NOT':
  452. object_def.not_contains = oids_string_to_list(value)
  453. elif key == 'X-':
  454. if not object_def.extensions:
  455. object_def.extensions = []
  456. object_def.extensions.append(extension_to_tuple('X-' + value))
  457. elif key == 'E-':
  458. if not object_def.experimental:
  459. object_def.experimental = []
  460. object_def.experimental.append(extension_to_tuple('E-' + value))
  461. else:
  462. if not conf_ignore_malformed_schema:
  463. raise LDAPSchemaError('malformed schema definition key:' + key + ' - use get_info=NONE in Server definition')
  464. else:
  465. return CaseInsensitiveDict() if conf_case_insensitive_schema else dict()
  466. object_def.raw_definition = object_definition
  467. if hasattr(object_def, 'syntax') and object_def.syntax and len(object_def.syntax) == 1:
  468. object_def.min_length = None
  469. if object_def.syntax[0].endswith('}'):
  470. try:
  471. object_def.min_length = int(object_def.syntax[0][object_def.syntax[0].index('{') + 1:-1])
  472. object_def.syntax[0] = object_def.syntax[0][:object_def.syntax[0].index('{')]
  473. except Exception:
  474. pass
  475. else:
  476. object_def.min_length = None
  477. object_def.syntax[0] = object_def.syntax[0].strip("'")
  478. object_def.syntax = object_def.syntax[0]
  479. if hasattr(object_def, 'name') and object_def.name:
  480. for name in object_def.name:
  481. ret_dict[name] = object_def
  482. else:
  483. ret_dict[object_def.oid] = object_def
  484. else:
  485. if not conf_ignore_malformed_schema:
  486. raise LDAPSchemaError('malformed schema definition, use get_info=NONE in Server definition')
  487. else:
  488. return CaseInsensitiveDict() if conf_case_insensitive_schema else dict()
  489. return ret_dict
  490. class MatchingRuleInfo(BaseObjectInfo):
  491. """
  492. As per RFC 4512 (4.1.3)
  493. """
  494. def __init__(self,
  495. oid=None,
  496. name=None,
  497. description=None,
  498. obsolete=False,
  499. syntax=None,
  500. extensions=None,
  501. experimental=None,
  502. definition=None):
  503. BaseObjectInfo.__init__(self,
  504. oid=oid,
  505. name=name,
  506. description=description,
  507. obsolete=obsolete,
  508. extensions=extensions,
  509. experimental=experimental,
  510. definition=definition)
  511. self.syntax = syntax
  512. def __repr__(self):
  513. r = (linesep + ' Syntax: ' + list_to_string(self.syntax)) if self.syntax else ''
  514. return 'Matching rule' + BaseObjectInfo.__repr__(self).replace('<__desc__>', r)
  515. class MatchingRuleUseInfo(BaseObjectInfo):
  516. """
  517. As per RFC 4512 (4.1.4)
  518. """
  519. def __init__(self,
  520. oid=None,
  521. name=None,
  522. description=None,
  523. obsolete=False,
  524. apply_to=None,
  525. extensions=None,
  526. experimental=None,
  527. definition=None):
  528. BaseObjectInfo.__init__(self,
  529. oid=oid,
  530. name=name,
  531. description=description,
  532. obsolete=obsolete,
  533. extensions=extensions,
  534. experimental=experimental,
  535. definition=definition)
  536. self.apply_to = apply_to
  537. def __repr__(self):
  538. r = (linesep + ' Apply to: ' + list_to_string(self.apply_to)) if self.apply_to else ''
  539. return 'Matching rule use' + BaseObjectInfo.__repr__(self).replace('<__desc__>', r)
  540. class ObjectClassInfo(BaseObjectInfo):
  541. """
  542. As per RFC 4512 (4.1.1)
  543. """
  544. def __init__(self,
  545. oid=None,
  546. name=None,
  547. description=None,
  548. obsolete=False,
  549. superior=None,
  550. kind=None,
  551. must_contain=None,
  552. may_contain=None,
  553. extensions=None,
  554. experimental=None,
  555. definition=None):
  556. BaseObjectInfo.__init__(self,
  557. oid=oid,
  558. name=name,
  559. description=description,
  560. obsolete=obsolete,
  561. extensions=extensions,
  562. experimental=experimental,
  563. definition=definition)
  564. self.superior = superior
  565. self.kind = kind
  566. self.must_contain = must_contain or []
  567. self.may_contain = may_contain or []
  568. def __repr__(self):
  569. r = ''
  570. r += (linesep + ' Type: ' + constant_to_class_kind(self.kind)) if self.kind else ''
  571. r += (linesep + ' Superior: ' + list_to_string(self.superior)) if self.superior else ''
  572. r += (linesep + ' Must contain attributes: ' + list_to_string(self.must_contain)) if self.must_contain else ''
  573. r += (linesep + ' May contain attributes: ' + list_to_string(self.may_contain)) if self.may_contain else ''
  574. return 'Object class' + BaseObjectInfo.__repr__(self).replace('<__desc__>', r)
  575. class AttributeTypeInfo(BaseObjectInfo):
  576. """
  577. As per RFC 4512 (4.1.2)
  578. """
  579. def __init__(self,
  580. oid=None,
  581. name=None,
  582. description=None,
  583. obsolete=False,
  584. superior=None,
  585. equality=None,
  586. ordering=None,
  587. substring=None,
  588. syntax=None,
  589. min_length=None,
  590. single_value=False,
  591. collective=False,
  592. no_user_modification=False,
  593. usage=None,
  594. extensions=None,
  595. experimental=None,
  596. definition=None):
  597. BaseObjectInfo.__init__(self,
  598. oid=oid,
  599. name=name,
  600. description=description,
  601. obsolete=obsolete,
  602. extensions=extensions,
  603. experimental=experimental,
  604. definition=definition)
  605. self.superior = superior
  606. self.equality = equality
  607. self.ordering = ordering
  608. self.substring = substring
  609. self.syntax = syntax
  610. self.min_length = min_length
  611. self.single_value = single_value
  612. self.collective = collective
  613. self.no_user_modification = no_user_modification
  614. self.usage = usage
  615. self.mandatory_in = []
  616. self.optional_in = []
  617. def __repr__(self):
  618. r = ''
  619. r += linesep + ' Single value: ' + str(self.single_value)
  620. r += linesep + ' Collective: True' if self.collective else ''
  621. r += (linesep + ' Superior: ' + list_to_string(self.superior)) if self.superior else ''
  622. r += linesep + ' No user modification: True' if self.no_user_modification else ''
  623. r += (linesep + ' Usage: ' + constant_to_attribute_usage(self.usage)) if self.usage else ''
  624. r += (linesep + ' Equality rule: ' + list_to_string(self.equality)) if self.equality else ''
  625. r += (linesep + ' Ordering rule: ' + list_to_string(self.ordering)) if self.ordering else ''
  626. r += (linesep + ' Substring rule: ' + list_to_string(self.substring)) if self.substring else ''
  627. r += (linesep + ' Syntax: ' + (self.syntax + (' [' + str(decode_syntax(self.syntax)))) + ']') if self.syntax else ''
  628. r += (linesep + ' Minimum length: ' + str(self.min_length)) if isinstance(self.min_length, int) else ''
  629. r += linesep + ' Mandatory in: ' + list_to_string(self.mandatory_in) if self.mandatory_in else ''
  630. r += linesep + ' Optional in: ' + list_to_string(self.optional_in) if self.optional_in else ''
  631. return 'Attribute type' + BaseObjectInfo.__repr__(self).replace('<__desc__>', r)
  632. class LdapSyntaxInfo(BaseObjectInfo):
  633. """
  634. As per RFC 4512 (4.1.5)
  635. """
  636. def __init__(self,
  637. oid=None,
  638. description=None,
  639. extensions=None,
  640. experimental=None,
  641. definition=None):
  642. BaseObjectInfo.__init__(self,
  643. oid=oid,
  644. name=None,
  645. description=description,
  646. obsolete=False,
  647. extensions=extensions,
  648. experimental=experimental,
  649. definition=definition)
  650. def __repr__(self):
  651. return 'LDAP syntax' + BaseObjectInfo.__repr__(self).replace('<__desc__>', '')
  652. class DitContentRuleInfo(BaseObjectInfo):
  653. """
  654. As per RFC 4512 (4.1.6)
  655. """
  656. def __init__(self,
  657. oid=None,
  658. name=None,
  659. description=None,
  660. obsolete=False,
  661. auxiliary_classes=None,
  662. must_contain=None,
  663. may_contain=None,
  664. not_contains=None,
  665. extensions=None,
  666. experimental=None,
  667. definition=None):
  668. BaseObjectInfo.__init__(self,
  669. oid=oid,
  670. name=name,
  671. description=description,
  672. obsolete=obsolete,
  673. extensions=extensions,
  674. experimental=experimental,
  675. definition=definition)
  676. self.auxiliary_classes = auxiliary_classes
  677. self.must_contain = must_contain
  678. self.may_contain = may_contain
  679. self.not_contains = not_contains
  680. def __repr__(self):
  681. r = (linesep + ' Auxiliary classes: ' + list_to_string(self.auxiliary_classes)) if self.auxiliary_classes else ''
  682. r += (linesep + ' Must contain: ' + list_to_string(self.must_contain)) if self.must_contain else ''
  683. r += (linesep + ' May contain: ' + list_to_string(self.may_contain)) if self.may_contain else ''
  684. r += (linesep + ' Not contains: ' + list_to_string(self.not_contains)) if self.not_contains else ''
  685. return 'DIT content rule' + BaseObjectInfo.__repr__(self).replace('<__desc__>', r)
  686. class DitStructureRuleInfo(BaseObjectInfo):
  687. """
  688. As per RFC 4512 (4.1.7.1)
  689. """
  690. def __init__(self,
  691. oid=None,
  692. name=None,
  693. description=None,
  694. obsolete=False,
  695. name_form=None,
  696. superior=None,
  697. extensions=None,
  698. experimental=None,
  699. definition=None):
  700. BaseObjectInfo.__init__(self,
  701. oid=oid,
  702. name=name,
  703. description=description,
  704. obsolete=obsolete,
  705. extensions=extensions,
  706. experimental=experimental,
  707. definition=definition)
  708. self.superior = superior
  709. self.name_form = name_form
  710. def __repr__(self):
  711. r = (linesep + ' Superior rules: ' + list_to_string(self.superior)) if self.superior else ''
  712. r += (linesep + ' Name form: ' + list_to_string(self.name_form)) if self.name_form else ''
  713. return 'DIT content rule' + BaseObjectInfo.__repr__(self).replace('<__desc__>', r)
  714. class NameFormInfo(BaseObjectInfo):
  715. """
  716. As per RFC 4512 (4.1.7.2)
  717. """
  718. def __init__(self,
  719. oid=None,
  720. name=None,
  721. description=None,
  722. obsolete=False,
  723. object_class=None,
  724. must_contain=None,
  725. may_contain=None,
  726. extensions=None,
  727. experimental=None,
  728. definition=None):
  729. BaseObjectInfo.__init__(self,
  730. oid=oid,
  731. name=name,
  732. description=description,
  733. obsolete=obsolete,
  734. extensions=extensions,
  735. experimental=experimental,
  736. definition=definition)
  737. self.object_class = object_class
  738. self.must_contain = must_contain
  739. self.may_contain = may_contain
  740. def __repr__(self):
  741. r = (linesep + ' Object class: ' + list_to_string(self.object_class)) if self.object_class else ''
  742. r += (linesep + ' Must contain: ' + list_to_string(self.must_contain)) if self.must_contain else ''
  743. r += (linesep + ' May contain: ' + list_to_string(self.may_contain)) if self.may_contain else ''
  744. return 'DIT content rule' + BaseObjectInfo.__repr__(self).replace('<__desc__>', r)