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.

objectDef.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. """
  2. """
  3. # Created on 2014.02.02
  4. #
  5. # Author: Giovanni Cannata
  6. #
  7. # Copyright 2014 - 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. from .attrDef import AttrDef
  26. from ..core.exceptions import LDAPKeyError, LDAPObjectError, LDAPAttributeError, LDAPSchemaError
  27. from .. import STRING_TYPES, SEQUENCE_TYPES, Server, Connection
  28. from ..protocol.rfc4512 import SchemaInfo, constant_to_class_kind
  29. from ..protocol.formatters.standard import find_attribute_validator
  30. from ..utils.ciDict import CaseInsensitiveWithAliasDict
  31. from ..utils.config import get_config_parameter
  32. from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED
  33. class ObjectDef(object):
  34. """Represent an object in the LDAP server. AttrDefs are stored in a dictionary; the key is the friendly name defined in AttrDef.
  35. AttrDefs can be added and removed using the += ad -= operators
  36. ObjectDef can be accessed either as a sequence and a dictionary. When accessed the whole AttrDef instance is returned
  37. """
  38. def __init__(self, object_class=None, schema=None, custom_validator=None, auxiliary_class=None):
  39. if object_class is None:
  40. object_class = []
  41. if not isinstance(object_class, SEQUENCE_TYPES):
  42. object_class = [object_class]
  43. if auxiliary_class is None:
  44. auxiliary_class = []
  45. if not isinstance(auxiliary_class, SEQUENCE_TYPES):
  46. auxiliary_class = [auxiliary_class]
  47. self.__dict__['_attributes'] = CaseInsensitiveWithAliasDict()
  48. self.__dict__['_custom_validator'] = custom_validator
  49. self.__dict__['_oid_info'] = []
  50. if isinstance(schema, Connection) and (schema._deferred_bind or schema._deferred_open): # probably a lazy connection, tries to bind
  51. schema._fire_deferred()
  52. if schema is not None:
  53. if isinstance(schema, Server):
  54. schema = schema.schema
  55. elif isinstance(schema, Connection):
  56. schema = schema.server.schema
  57. elif isinstance(schema, SchemaInfo):
  58. pass
  59. elif schema:
  60. error_message = 'unable to read schema'
  61. if log_enabled(ERROR):
  62. log(ERROR, '%s for <%s>', error_message, self)
  63. raise LDAPSchemaError(error_message)
  64. if schema is None:
  65. error_message = 'schema not present'
  66. if log_enabled(ERROR):
  67. log(ERROR, '%s for <%s>', error_message, self)
  68. raise LDAPSchemaError(error_message)
  69. self.__dict__['_schema'] = schema
  70. if self._schema:
  71. object_class = [schema.object_classes[name].name[0] for name in object_class] # uses object class names capitalized as in schema
  72. auxiliary_class = [schema.object_classes[name].name[0] for name in auxiliary_class]
  73. for object_name in object_class:
  74. if object_name:
  75. self._populate_attr_defs(object_name)
  76. for object_name in auxiliary_class:
  77. if object_name:
  78. self._populate_attr_defs(object_name)
  79. self.__dict__['_object_class'] = object_class
  80. self.__dict__['_auxiliary_class'] = auxiliary_class
  81. if log_enabled(BASIC):
  82. log(BASIC, 'instantiated ObjectDef: <%r>', self)
  83. def _populate_attr_defs(self, object_name):
  84. if object_name in self._schema.object_classes:
  85. object_schema = self._schema.object_classes[object_name]
  86. self.__dict__['_oid_info'].append(object_name + " (" + constant_to_class_kind(object_schema.kind) + ") " + str(object_schema.oid))
  87. if object_schema.superior:
  88. for sup in object_schema.superior:
  89. self._populate_attr_defs(sup)
  90. for attribute_name in object_schema.must_contain:
  91. self.add_from_schema(attribute_name, True)
  92. for attribute_name in object_schema.may_contain:
  93. if attribute_name not in self._attributes: # the attribute could already be defined as "mandatory" in a superclass
  94. self.add_from_schema(attribute_name, False)
  95. else:
  96. error_message = 'object class \'%s\' not defined in schema' % object_name
  97. if log_enabled(ERROR):
  98. log(ERROR, '%s for <%s>', error_message, self)
  99. raise LDAPObjectError(error_message)
  100. def __repr__(self):
  101. if self._object_class:
  102. r = 'OBJ : ' + ', '.join(self._object_class) + linesep
  103. else:
  104. r = 'OBJ : <None>' + linesep
  105. if self._auxiliary_class:
  106. r += 'AUX : ' + ', '.join(self._auxiliary_class) + linesep
  107. else:
  108. r += 'AUX : <None>' + linesep
  109. r += 'OID: ' + ', '.join([oid for oid in self._oid_info]) + linesep
  110. r += 'MUST: ' + ', '.join(sorted([attr for attr in self._attributes if self._attributes[attr].mandatory])) + linesep
  111. r += 'MAY : ' + ', '.join(sorted([attr for attr in self._attributes if not self._attributes[attr].mandatory])) + linesep
  112. return r
  113. def __str__(self):
  114. return self.__repr__()
  115. def __getitem__(self, item):
  116. return self.__getattr__(item)
  117. def __getattr__(self, item):
  118. item = ''.join(item.split()).lower()
  119. if '_attributes' in self.__dict__:
  120. try:
  121. return self._attributes[item]
  122. except KeyError:
  123. error_message = 'key \'%s\' not present' % item
  124. if log_enabled(ERROR):
  125. log(ERROR, '%s for <%s>', error_message, self)
  126. raise LDAPKeyError(error_message)
  127. else:
  128. error_message = 'internal _attributes property not defined'
  129. if log_enabled(ERROR):
  130. log(ERROR, '%s for <%s>', error_message, self)
  131. raise LDAPKeyError(error_message)
  132. def __setattr__(self, key, value):
  133. error_message = 'object \'%s\' is read only' % key
  134. if log_enabled(ERROR):
  135. log(ERROR, '%s for <%s>', error_message, self)
  136. raise LDAPObjectError(error_message)
  137. def __iadd__(self, other):
  138. self.add_attribute(other)
  139. return self
  140. def __isub__(self, other):
  141. if isinstance(other, AttrDef):
  142. self.remove_attribute(other.key)
  143. elif isinstance(other, STRING_TYPES):
  144. self.remove_attribute(other)
  145. return self
  146. def __iter__(self):
  147. for attribute in self._attributes:
  148. yield self._attributes[attribute]
  149. def __len__(self):
  150. return len(self._attributes)
  151. if str is not bytes: # Python 3
  152. def __bool__(self): # needed to make the objectDef appears as existing in "if cursor:" even if there are no entries
  153. return True
  154. else: # Python 2
  155. def __nonzero__(self):
  156. return True
  157. def __contains__(self, item):
  158. try:
  159. self.__getitem__(item)
  160. except KeyError:
  161. return False
  162. return True
  163. def add_from_schema(self, attribute_name, mandatory=False):
  164. attr_def = AttrDef(attribute_name)
  165. attr_def.validate = find_attribute_validator(self._schema, attribute_name, self._custom_validator)
  166. attr_def.mandatory = mandatory # in schema mandatory is specified in the object class, not in the attribute class
  167. if self._schema and self._schema.attribute_types and attribute_name in self._schema.attribute_types:
  168. attr_def.single_value = self._schema.attribute_types[attribute_name].single_value
  169. attr_def.oid_info = self._schema.attribute_types[attribute_name]
  170. self.add_attribute(attr_def)
  171. def add_attribute(self, definition=None):
  172. """Add an AttrDef to the ObjectDef. Can be called with the += operator.
  173. :param definition: the AttrDef object to add, can also be a string containing the name of attribute to add. Can be a list of both
  174. """
  175. conf_attributes_excluded_from_object_def = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF')]
  176. if isinstance(definition, STRING_TYPES):
  177. self.add_from_schema(definition)
  178. elif isinstance(definition, AttrDef):
  179. if definition.key.lower() not in conf_attributes_excluded_from_object_def:
  180. if definition.key not in self._attributes:
  181. self._attributes[definition.key] = definition
  182. if definition.name and definition.name != definition.key:
  183. self._attributes.set_alias(definition.key, definition.name)
  184. other_names = [name for name in definition.oid_info.name if definition.key.lower() != name.lower()] if definition.oid_info else None
  185. if other_names:
  186. self._attributes.set_alias(definition.key, other_names)
  187. if not definition.validate:
  188. validator = find_attribute_validator(self._schema, definition.key, self._custom_validator)
  189. self._attributes[definition.key].validate = validator
  190. elif isinstance(definition, SEQUENCE_TYPES):
  191. for element in definition:
  192. self.add_attribute(element)
  193. else:
  194. error_message = 'unable to add element to object definition'
  195. if log_enabled(ERROR):
  196. log(ERROR, '%s for <%s>', error_message, self)
  197. raise LDAPObjectError(error_message)
  198. def remove_attribute(self, item):
  199. """Remove an AttrDef from the ObjectDef. Can be called with the -= operator.
  200. :param item: the AttrDef to remove, can also be a string containing the name of attribute to remove
  201. """
  202. key = None
  203. if isinstance(item, STRING_TYPES):
  204. key = ''.join(item.split()).lower()
  205. elif isinstance(item, AttrDef):
  206. key = item.key.lower()
  207. if key:
  208. for attr in self._attributes:
  209. if key == attr.lower():
  210. del self._attributes[attr]
  211. break
  212. else:
  213. error_message = 'key \'%s\' not present' % key
  214. if log_enabled(ERROR):
  215. log(ERROR, '%s for <%s>', error_message, self)
  216. raise LDAPKeyError(error_message)
  217. else:
  218. error_message = 'key type must be str or AttrDef not ' + str(type(item))
  219. if log_enabled(ERROR):
  220. log(ERROR, '%s for <%s>', error_message, self)
  221. raise LDAPAttributeError(error_message)
  222. def clear_attributes(self):
  223. """Empty the ObjectDef attribute list
  224. """
  225. self.__dict__['object_class'] = None
  226. self.__dict__['auxiliary_class'] = None
  227. self.__dict__['_attributes'] = dict()