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.

entry.py 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. """
  2. """
  3. # Created on 2016.08.19
  4. #
  5. # Author: Giovanni Cannata
  6. #
  7. # Copyright 2016 - 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. import json
  25. try:
  26. from collections import OrderedDict
  27. except ImportError:
  28. from ..utils.ordDict import OrderedDict # for Python 2.6
  29. from os import linesep
  30. from .. import STRING_TYPES, SEQUENCE_TYPES, MODIFY_ADD, MODIFY_REPLACE
  31. from .attribute import WritableAttribute
  32. from .objectDef import ObjectDef
  33. from .attrDef import AttrDef
  34. from ..core.exceptions import LDAPKeyError, LDAPCursorError
  35. from ..utils.conv import check_json_dict, format_json, prepare_for_stream
  36. from ..protocol.rfc2849 import operation_to_ldif, add_ldif_header
  37. from ..utils.dn import safe_dn, safe_rdn, to_dn
  38. from ..utils.repr import to_stdout_encoding
  39. from ..utils.ciDict import CaseInsensitiveWithAliasDict
  40. from ..utils.config import get_config_parameter
  41. from . import STATUS_VIRTUAL, STATUS_WRITABLE, STATUS_PENDING_CHANGES, STATUS_COMMITTED, STATUS_DELETED,\
  42. STATUS_INIT, STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING, STATUS_MANDATORY_MISSING, STATUSES, INITIAL_STATUSES
  43. from ..core.results import RESULT_SUCCESS
  44. from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED
  45. class EntryState(object):
  46. """Contains data on the status of the entry. Does not pollute the Entry __dict__.
  47. """
  48. def __init__(self, dn, cursor):
  49. self.dn = dn
  50. self._initial_status = None
  51. self._to = None # used for move and rename
  52. self.status = STATUS_INIT
  53. self.attributes = CaseInsensitiveWithAliasDict()
  54. self.raw_attributes = CaseInsensitiveWithAliasDict()
  55. self.response = None
  56. self.cursor = cursor
  57. self.origin = None # reference to the original read-only entry (set when made writable). Needed to update attributes in read-only when modified (only if both refer the same server)
  58. self.read_time = None
  59. self.changes = OrderedDict() # includes changes to commit in a writable entry
  60. if cursor.definition:
  61. self.definition = cursor.definition
  62. else:
  63. self.definition = None
  64. def __repr__(self):
  65. if self.__dict__ and self.dn is not None:
  66. r = 'DN: ' + to_stdout_encoding(self.dn) + ' - STATUS: ' + ((self._initial_status + ', ') if self._initial_status != self.status else '') + self.status + ' - READ TIME: ' + (self.read_time.isoformat() if self.read_time else '<never>') + linesep
  67. r += 'attributes: ' + ', '.join(sorted(self.attributes.keys())) + linesep
  68. r += 'object def: ' + (', '.join(sorted(self.definition._object_class)) if self.definition._object_class else '<None>') + linesep
  69. r += 'attr defs: ' + ', '.join(sorted(self.definition._attributes.keys())) + linesep
  70. r += 'response: ' + ('present' if self.response else '<None>') + linesep
  71. r += 'cursor: ' + (self.cursor.__class__.__name__ if self.cursor else '<None>') + linesep
  72. return r
  73. else:
  74. return object.__repr__(self)
  75. def __str__(self):
  76. return self.__repr__()
  77. def set_status(self, status):
  78. conf_ignored_mandatory_attributes_in_object_def = [v.lower() for v in get_config_parameter('IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF')]
  79. if status not in STATUSES:
  80. error_message = 'invalid entry status ' + str(status)
  81. if log_enabled(ERROR):
  82. log(ERROR, '%s for <%s>', error_message, self)
  83. raise LDAPCursorError(error_message)
  84. if status in INITIAL_STATUSES:
  85. self._initial_status = status
  86. self.status = status
  87. if status == STATUS_DELETED:
  88. self._initial_status = STATUS_VIRTUAL
  89. if status == STATUS_COMMITTED:
  90. self._initial_status = STATUS_WRITABLE
  91. if self.status == STATUS_VIRTUAL or (self.status == STATUS_PENDING_CHANGES and self._initial_status == STATUS_VIRTUAL): # checks if all mandatory attributes are present in new entries
  92. for attr in self.definition._attributes:
  93. if self.definition._attributes[attr].mandatory and attr.lower() not in conf_ignored_mandatory_attributes_in_object_def:
  94. if (attr not in self.attributes or self.attributes[attr].virtual) and attr not in self.changes:
  95. self.status = STATUS_MANDATORY_MISSING
  96. break
  97. class EntryBase(object):
  98. """The Entry object contains a single LDAP entry.
  99. Attributes can be accessed either by sequence, by assignment
  100. or as dictionary keys. Keys are not case sensitive.
  101. The Entry object is read only
  102. - The DN is retrieved by _dn
  103. - The cursor reference is in _cursor
  104. - Raw attributes values are retrieved with _raw_attributes and the _raw_attribute() methods
  105. """
  106. def __init__(self, dn, cursor):
  107. self.__dict__['_state'] = EntryState(dn, cursor)
  108. def __repr__(self):
  109. if self.__dict__ and self.entry_dn is not None:
  110. r = 'DN: ' + to_stdout_encoding(self.entry_dn) + ' - STATUS: ' + ((self._state._initial_status + ', ') if self._state._initial_status != self.entry_status else '') + self.entry_status + ' - READ TIME: ' + (self.entry_read_time.isoformat() if self.entry_read_time else '<never>') + linesep
  111. if self._state.attributes:
  112. for attr in sorted(self._state.attributes):
  113. if self._state.attributes[attr] or (hasattr(self._state.attributes[attr], 'changes') and self._state.attributes[attr].changes):
  114. r += ' ' + repr(self._state.attributes[attr]) + linesep
  115. return r
  116. else:
  117. return object.__repr__(self)
  118. def __str__(self):
  119. return self.__repr__()
  120. def __iter__(self):
  121. for attribute in self._state.attributes:
  122. yield self._state.attributes[attribute]
  123. # raise StopIteration # deprecated in PEP 479
  124. return
  125. def __contains__(self, item):
  126. try:
  127. self.__getitem__(item)
  128. return True
  129. except LDAPKeyError:
  130. return False
  131. def __getattr__(self, item):
  132. if isinstance(item, STRING_TYPES):
  133. if item == '_state':
  134. return self.__dict__['_state']
  135. item = ''.join(item.split()).lower()
  136. attr_found = None
  137. for attr in self._state.attributes.keys():
  138. if item == attr.lower():
  139. attr_found = attr
  140. break
  141. if not attr_found:
  142. for attr in self._state.attributes.aliases():
  143. if item == attr.lower():
  144. attr_found = attr
  145. break
  146. if not attr_found:
  147. for attr in self._state.attributes.keys():
  148. if item + ';binary' == attr.lower():
  149. attr_found = attr
  150. break
  151. if not attr_found:
  152. for attr in self._state.attributes.aliases():
  153. if item + ';binary' == attr.lower():
  154. attr_found = attr
  155. break
  156. if not attr_found:
  157. for attr in self._state.attributes.keys():
  158. if item + ';range' in attr.lower():
  159. attr_found = attr
  160. break
  161. if not attr_found:
  162. for attr in self._state.attributes.aliases():
  163. if item + ';range' in attr.lower():
  164. attr_found = attr
  165. break
  166. if not attr_found:
  167. error_message = 'attribute \'%s\' not found' % item
  168. if log_enabled(ERROR):
  169. log(ERROR, '%s for <%s>', error_message, self)
  170. raise LDAPCursorError(error_message)
  171. return self._state.attributes[attr]
  172. error_message = 'attribute name must be a string'
  173. if log_enabled(ERROR):
  174. log(ERROR, '%s for <%s>', error_message, self)
  175. raise LDAPCursorError(error_message)
  176. def __setattr__(self, item, value):
  177. if item in self._state.attributes:
  178. error_message = 'attribute \'%s\' is read only' % item
  179. if log_enabled(ERROR):
  180. log(ERROR, '%s for <%s>', error_message, self)
  181. raise LDAPCursorError(error_message)
  182. else:
  183. error_message = 'entry is read only, cannot add \'%s\'' % item
  184. if log_enabled(ERROR):
  185. log(ERROR, '%s for <%s>', error_message, self)
  186. raise LDAPCursorError(error_message)
  187. def __getitem__(self, item):
  188. if isinstance(item, STRING_TYPES):
  189. item = ''.join(item.split()).lower()
  190. attr_found = None
  191. for attr in self._state.attributes.keys():
  192. if item == attr.lower():
  193. attr_found = attr
  194. break
  195. if not attr_found:
  196. for attr in self._state.attributes.aliases():
  197. if item == attr.lower():
  198. attr_found = attr
  199. break
  200. if not attr_found:
  201. for attr in self._state.attributes.keys():
  202. if item + ';binary' == attr.lower():
  203. attr_found = attr
  204. break
  205. if not attr_found:
  206. for attr in self._state.attributes.aliases():
  207. if item + ';binary' == attr.lower():
  208. attr_found = attr
  209. break
  210. if not attr_found:
  211. error_message = 'key \'%s\' not found' % item
  212. if log_enabled(ERROR):
  213. log(ERROR, '%s for <%s>', error_message, self)
  214. raise LDAPKeyError(error_message)
  215. return self._state.attributes[attr]
  216. error_message = 'key must be a string'
  217. if log_enabled(ERROR):
  218. log(ERROR, '%s for <%s>', error_message, self)
  219. raise LDAPKeyError(error_message)
  220. def __eq__(self, other):
  221. if isinstance(other, EntryBase):
  222. return self.entry_dn == other.entry_dn
  223. return False
  224. def __lt__(self, other):
  225. if isinstance(other, EntryBase):
  226. return self.entry_dn <= other.entry_dn
  227. return False
  228. @property
  229. def entry_dn(self):
  230. return self._state.dn
  231. @property
  232. def entry_cursor(self):
  233. return self._state.cursor
  234. @property
  235. def entry_status(self):
  236. return self._state.status
  237. @property
  238. def entry_definition(self):
  239. return self._state.definition
  240. @property
  241. def entry_raw_attributes(self):
  242. return self._state.entry_raw_attributes
  243. def entry_raw_attribute(self, name):
  244. """
  245. :param name: name of the attribute
  246. :return: raw (unencoded) value of the attribute, None if attribute is not found
  247. """
  248. return self._state.entry_raw_attributes[name] if name in self._state.entry_raw_attributes else None
  249. @property
  250. def entry_mandatory_attributes(self):
  251. return [attribute for attribute in self.entry_definition._attributes if self.entry_definition._attributes[attribute].mandatory]
  252. @property
  253. def entry_attributes(self):
  254. return list(self._state.attributes.keys())
  255. @property
  256. def entry_attributes_as_dict(self):
  257. return dict((attribute_key, attribute_value.values) for (attribute_key, attribute_value) in self._state.attributes.items())
  258. @property
  259. def entry_read_time(self):
  260. return self._state.read_time
  261. @property
  262. def _changes(self):
  263. return self._state.changes
  264. def entry_to_json(self, raw=False, indent=4, sort=True, stream=None, checked_attributes=True, include_empty=True):
  265. json_entry = dict()
  266. json_entry['dn'] = self.entry_dn
  267. if checked_attributes:
  268. if not include_empty:
  269. # needed for python 2.6 compatibility
  270. json_entry['attributes'] = dict((key, self.entry_attributes_as_dict[key]) for key in self.entry_attributes_as_dict if self.entry_attributes_as_dict[key])
  271. else:
  272. json_entry['attributes'] = self.entry_attributes_as_dict
  273. if raw:
  274. if not include_empty:
  275. # needed for python 2.6 compatibility
  276. json_entry['raw'] = dict((key, self.entry_raw_attributes[key]) for key in self.entry_raw_attributes if self.entry_raw_attributes[key])
  277. else:
  278. json_entry['raw'] = dict(self.entry_raw_attributes)
  279. if str is bytes: # Python 2
  280. check_json_dict(json_entry)
  281. json_output = json.dumps(json_entry,
  282. ensure_ascii=True,
  283. sort_keys=sort,
  284. indent=indent,
  285. check_circular=True,
  286. default=format_json,
  287. separators=(',', ': '))
  288. if stream:
  289. stream.write(json_output)
  290. return json_output
  291. def entry_to_ldif(self, all_base64=False, line_separator=None, sort_order=None, stream=None):
  292. ldif_lines = operation_to_ldif('searchResponse', [self._state.response], all_base64, sort_order=sort_order)
  293. ldif_lines = add_ldif_header(ldif_lines)
  294. line_separator = line_separator or linesep
  295. ldif_output = line_separator.join(ldif_lines)
  296. if stream:
  297. if stream.tell() == 0:
  298. header = add_ldif_header(['-'])[0]
  299. stream.write(prepare_for_stream(header + line_separator + line_separator))
  300. stream.write(prepare_for_stream(ldif_output + line_separator + line_separator))
  301. return ldif_output
  302. class Entry(EntryBase):
  303. """The Entry object contains a single LDAP entry.
  304. Attributes can be accessed either by sequence, by assignment
  305. or as dictionary keys. Keys are not case sensitive.
  306. The Entry object is read only
  307. - The DN is retrieved by _dn()
  308. - The Reader reference is in _cursor()
  309. - Raw attributes values are retrieved by the _ra_attributes and
  310. _raw_attribute() methods
  311. """
  312. def entry_writable(self, object_def=None, writer_cursor=None, attributes=None, custom_validator=None, auxiliary_class=None):
  313. if not self.entry_cursor.schema:
  314. error_message = 'schema must be available to make an entry writable'
  315. if log_enabled(ERROR):
  316. log(ERROR, '%s for <%s>', error_message, self)
  317. raise LDAPCursorError(error_message)
  318. # returns a new WritableEntry and its Writer cursor
  319. if object_def is None:
  320. if self.entry_cursor.definition._object_class:
  321. object_def = self.entry_definition._object_class
  322. auxiliary_class = self.entry_definition._auxiliary_class + (auxiliary_class if isinstance(auxiliary_class, SEQUENCE_TYPES) else [])
  323. elif 'objectclass' in self:
  324. object_def = self.objectclass.values
  325. if not object_def:
  326. error_message = 'object class must be specified to make an entry writable'
  327. if log_enabled(ERROR):
  328. log(ERROR, '%s for <%s>', error_message, self)
  329. raise LDAPCursorError(error_message)
  330. if not isinstance(object_def, ObjectDef):
  331. object_def = ObjectDef(object_def, self.entry_cursor.schema, custom_validator, auxiliary_class)
  332. if attributes:
  333. if isinstance(attributes, STRING_TYPES):
  334. attributes = [attributes]
  335. if isinstance(attributes, SEQUENCE_TYPES):
  336. for attribute in attributes:
  337. if attribute not in object_def._attributes:
  338. error_message = 'attribute \'%s\' not in schema for \'%s\'' % (attribute, object_def)
  339. if log_enabled(ERROR):
  340. log(ERROR, '%s for <%s>', error_message, self)
  341. raise LDAPCursorError(error_message)
  342. else:
  343. attributes = []
  344. if not writer_cursor:
  345. from .cursor import Writer # local import to avoid circular reference in import at startup
  346. writable_cursor = Writer(self.entry_cursor.connection, object_def)
  347. else:
  348. writable_cursor = writer_cursor
  349. if attributes: # force reading of attributes
  350. writable_entry = writable_cursor._refresh_object(self.entry_dn, list(attributes) + self.entry_attributes)
  351. else:
  352. writable_entry = writable_cursor._create_entry(self._state.response)
  353. writable_cursor.entries.append(writable_entry)
  354. writable_entry._state.read_time = self.entry_read_time
  355. writable_entry._state.origin = self # reference to the original read-only entry
  356. # checks original entry for custom definitions in AttrDefs
  357. for attr in writable_entry._state.origin.entry_definition._attributes:
  358. original_attr = writable_entry._state.origin.entry_definition._attributes[attr]
  359. if attr != original_attr.name and attr not in writable_entry._state.attributes:
  360. old_attr_def = writable_entry.entry_definition._attributes[original_attr.name]
  361. new_attr_def = AttrDef(original_attr.name,
  362. key=attr,
  363. validate=original_attr.validate,
  364. pre_query=original_attr.pre_query,
  365. post_query=original_attr.post_query,
  366. default=original_attr.default,
  367. dereference_dn=original_attr.dereference_dn,
  368. description=original_attr.description,
  369. mandatory=old_attr_def.mandatory, # keeps value read from schema
  370. single_value=old_attr_def.single_value, # keeps value read from schema
  371. alias=original_attr.other_names)
  372. object_def = writable_entry.entry_definition
  373. object_def -= old_attr_def
  374. object_def += new_attr_def
  375. # updates attribute name in entry attributes
  376. new_attr = WritableAttribute(new_attr_def, writable_entry, writable_cursor)
  377. if original_attr.name in writable_entry._state.attributes:
  378. new_attr.other_names = writable_entry._state.attributes[original_attr.name].other_names
  379. new_attr.raw_values = writable_entry._state.attributes[original_attr.name].raw_values
  380. new_attr.values = writable_entry._state.attributes[original_attr.name].values
  381. new_attr.response = writable_entry._state.attributes[original_attr.name].response
  382. writable_entry._state.attributes[attr] = new_attr
  383. # writable_entry._state.attributes.set_alias(attr, new_attr.other_names)
  384. del writable_entry._state.attributes[original_attr.name]
  385. writable_entry._state.set_status(STATUS_WRITABLE)
  386. return writable_entry
  387. class WritableEntry(EntryBase):
  388. def __setitem__(self, key, value):
  389. if value is not Ellipsis: # hack for using implicit operators in writable attributes
  390. self.__setattr__(key, value)
  391. def __setattr__(self, item, value):
  392. conf_attributes_excluded_from_object_def = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF')]
  393. if item == '_state' and isinstance(value, EntryState):
  394. self.__dict__['_state'] = value
  395. return
  396. if value is not Ellipsis: # hack for using implicit operators in writable attributes
  397. # checks if using an alias
  398. if item in self.entry_cursor.definition._attributes or item.lower() in conf_attributes_excluded_from_object_def:
  399. if item not in self._state.attributes: # setting value to an attribute still without values
  400. new_attribute = WritableAttribute(self.entry_cursor.definition._attributes[item], self, cursor=self.entry_cursor)
  401. self._state.attributes[str(item)] = new_attribute # force item to a string for key in attributes dict
  402. self._state.attributes[item].set(value) # try to add to new_values
  403. else:
  404. error_message = 'attribute \'%s\' not defined' % item
  405. if log_enabled(ERROR):
  406. log(ERROR, '%s for <%s>', error_message, self)
  407. raise LDAPCursorError(error_message)
  408. def __getattr__(self, item):
  409. if isinstance(item, STRING_TYPES):
  410. if item == '_state':
  411. return self.__dict__['_state']
  412. item = ''.join(item.split()).lower()
  413. for attr in self._state.attributes.keys():
  414. if item == attr.lower():
  415. return self._state.attributes[attr]
  416. for attr in self._state.attributes.aliases():
  417. if item == attr.lower():
  418. return self._state.attributes[attr]
  419. if item in self.entry_definition._attributes: # item is a new attribute to commit, creates the AttrDef and add to the attributes to retrive
  420. self._state.attributes[item] = WritableAttribute(self.entry_definition._attributes[item], self, self.entry_cursor)
  421. self.entry_cursor.attributes.add(item)
  422. return self._state.attributes[item]
  423. error_message = 'attribute \'%s\' not defined' % item
  424. if log_enabled(ERROR):
  425. log(ERROR, '%s for <%s>', error_message, self)
  426. raise LDAPCursorError(error_message)
  427. else:
  428. error_message = 'attribute name must be a string'
  429. if log_enabled(ERROR):
  430. log(ERROR, '%s for <%s>', error_message, self)
  431. raise LDAPCursorError(error_message)
  432. @property
  433. def entry_virtual_attributes(self):
  434. return [attr for attr in self.entry_attributes if self[attr].virtual]
  435. def entry_commit_changes(self, refresh=True, controls=None, clear_history=True):
  436. if clear_history:
  437. self.entry_cursor._reset_history()
  438. if self.entry_status == STATUS_READY_FOR_DELETION:
  439. result = self.entry_cursor.connection.delete(self.entry_dn, controls)
  440. if not self.entry_cursor.connection.strategy.sync:
  441. response, result, request = self.entry_cursor.connection.get_response(result, get_request=True)
  442. else:
  443. response = self.entry_cursor.connection.response
  444. result = self.entry_cursor.connection.result
  445. request = self.entry_cursor.connection.request
  446. self.entry_cursor._store_operation_in_history(request, result, response)
  447. if result['result'] == RESULT_SUCCESS:
  448. dn = self.entry_dn
  449. if self._state.origin and self.entry_cursor.connection.server == self._state.origin.entry_cursor.connection.server: # deletes original read-only Entry
  450. cursor = self._state.origin.entry_cursor
  451. self._state.origin.__dict__.clear()
  452. self._state.origin.__dict__['_state'] = EntryState(dn, cursor)
  453. self._state.origin._state.set_status(STATUS_DELETED)
  454. cursor = self.entry_cursor
  455. self.__dict__.clear()
  456. self._state = EntryState(dn, cursor)
  457. self._state.set_status(STATUS_DELETED)
  458. return True
  459. return False
  460. elif self.entry_status == STATUS_READY_FOR_MOVING:
  461. result = self.entry_cursor.connection.modify_dn(self.entry_dn, '+'.join(safe_rdn(self.entry_dn)), new_superior=self._state._to)
  462. if not self.entry_cursor.connection.strategy.sync:
  463. response, result, request = self.entry_cursor.connection.get_response(result, get_request=True)
  464. else:
  465. response = self.entry_cursor.connection.response
  466. result = self.entry_cursor.connection.result
  467. request = self.entry_cursor.connection.request
  468. self.entry_cursor._store_operation_in_history(request, result, response)
  469. if result['result'] == RESULT_SUCCESS:
  470. self._state.dn = safe_dn('+'.join(safe_rdn(self.entry_dn)) + ',' + self._state._to)
  471. if refresh:
  472. if self.entry_refresh():
  473. if self._state.origin and self.entry_cursor.connection.server == self._state.origin.entry_cursor.connection.server: # refresh dn of origin
  474. self._state.origin._state.dn = self.entry_dn
  475. self._state.set_status(STATUS_COMMITTED)
  476. self._state._to = None
  477. return True
  478. return False
  479. elif self.entry_status == STATUS_READY_FOR_RENAMING:
  480. rdn = '+'.join(safe_rdn(self._state._to))
  481. result = self.entry_cursor.connection.modify_dn(self.entry_dn, rdn)
  482. if not self.entry_cursor.connection.strategy.sync:
  483. response, result, request = self.entry_cursor.connection.get_response(result, get_request=True)
  484. else:
  485. response = self.entry_cursor.connection.response
  486. result = self.entry_cursor.connection.result
  487. request = self.entry_cursor.connection.request
  488. self.entry_cursor._store_operation_in_history(request, result, response)
  489. if result['result'] == RESULT_SUCCESS:
  490. self._state.dn = rdn + ',' + ','.join(to_dn(self.entry_dn)[1:])
  491. if refresh:
  492. if self.entry_refresh():
  493. if self._state.origin and self.entry_cursor.connection.server == self._state.origin.entry_cursor.connection.server: # refresh dn of origin
  494. self._state.origin._state.dn = self.entry_dn
  495. self._state.set_status(STATUS_COMMITTED)
  496. self._state._to = None
  497. return True
  498. return False
  499. elif self.entry_status in [STATUS_VIRTUAL, STATUS_MANDATORY_MISSING]:
  500. missing_attributes = []
  501. for attr in self.entry_mandatory_attributes:
  502. if (attr not in self._state.attributes or self._state.attributes[attr].virtual) and attr not in self._changes:
  503. missing_attributes.append('\'' + attr + '\'')
  504. error_message = 'mandatory attributes %s missing in entry %s' % (', '.join(missing_attributes), self.entry_dn)
  505. if log_enabled(ERROR):
  506. log(ERROR, '%s for <%s>', error_message, self)
  507. raise LDAPCursorError(error_message)
  508. elif self.entry_status == STATUS_PENDING_CHANGES:
  509. if self._changes:
  510. if self.entry_definition._auxiliary_class: # checks if an attribute is from an auxiliary class and adds it to the objectClass attribute if not present
  511. for attr in self._changes:
  512. # checks schema to see if attribute is defined in one of the already present object classes
  513. attr_classes = self.entry_cursor.schema.attribute_types[attr].mandatory_in + self.entry_cursor.schema.attribute_types[attr].optional_in
  514. for object_class in self.objectclass:
  515. if object_class in attr_classes:
  516. break
  517. else: # executed only if the attribute class is not present in the objectClass attribute
  518. # checks if attribute is defined in one of the possible auxiliary classes
  519. for aux_class in self.entry_definition._auxiliary_class:
  520. if aux_class in attr_classes:
  521. if self._state._initial_status == STATUS_VIRTUAL: # entry is new, there must be a pending objectClass MODIFY_REPLACE
  522. self._changes['objectClass'][0][1].append(aux_class)
  523. else:
  524. self.objectclass += aux_class
  525. if self._state._initial_status == STATUS_VIRTUAL:
  526. new_attributes = dict()
  527. for attr in self._changes:
  528. new_attributes[attr] = self._changes[attr][0][1]
  529. result = self.entry_cursor.connection.add(self.entry_dn, None, new_attributes, controls)
  530. else:
  531. result = self.entry_cursor.connection.modify(self.entry_dn, self._changes, controls)
  532. if not self.entry_cursor.connection.strategy.sync: # asynchronous request
  533. response, result, request = self.entry_cursor.connection.get_response(result, get_request=True)
  534. else:
  535. response = self.entry_cursor.connection.response
  536. result = self.entry_cursor.connection.result
  537. request = self.entry_cursor.connection.request
  538. self.entry_cursor._store_operation_in_history(request, result, response)
  539. if result['result'] == RESULT_SUCCESS:
  540. if refresh:
  541. if self.entry_refresh():
  542. if self._state.origin and self.entry_cursor.connection.server == self._state.origin.entry_cursor.connection.server: # updates original read-only entry if present
  543. for attr in self: # adds AttrDefs from writable entry to origin entry definition if some is missing
  544. if attr.key in self.entry_definition._attributes and attr.key not in self._state.origin.entry_definition._attributes:
  545. self._state.origin.entry_cursor.definition.add_attribute(self.entry_cursor.definition._attributes[attr.key]) # adds AttrDef from writable entry to original entry if missing
  546. temp_entry = self._state.origin.entry_cursor._create_entry(self._state.response)
  547. self._state.origin.__dict__.clear()
  548. self._state.origin.__dict__['_state'] = temp_entry._state
  549. for attr in self: # returns the whole attribute object
  550. if not attr.virtual:
  551. self._state.origin.__dict__[attr.key] = self._state.origin._state.attributes[attr.key]
  552. self._state.origin._state.read_time = self.entry_read_time
  553. else:
  554. self.entry_discard_changes() # if not refreshed remove committed changes
  555. self._state.set_status(STATUS_COMMITTED)
  556. return True
  557. return False
  558. def entry_discard_changes(self):
  559. self._changes.clear()
  560. self._state.set_status(self._state._initial_status)
  561. def entry_delete(self):
  562. if self.entry_status not in [STATUS_WRITABLE, STATUS_COMMITTED, STATUS_READY_FOR_DELETION]:
  563. error_message = 'cannot delete entry, invalid status: ' + self.entry_status
  564. if log_enabled(ERROR):
  565. log(ERROR, '%s for <%s>', error_message, self)
  566. raise LDAPCursorError(error_message)
  567. self._state.set_status(STATUS_READY_FOR_DELETION)
  568. def entry_refresh(self, tries=4, seconds=2):
  569. """
  570. Refreshes the entry from the LDAP Server
  571. """
  572. if self.entry_cursor.connection:
  573. if self.entry_cursor.refresh_entry(self, tries, seconds):
  574. return True
  575. return False
  576. def entry_move(self, destination_dn):
  577. if self.entry_status not in [STATUS_WRITABLE, STATUS_COMMITTED, STATUS_READY_FOR_MOVING]:
  578. error_message = 'cannot move entry, invalid status: ' + self.entry_status
  579. if log_enabled(ERROR):
  580. log(ERROR, '%s for <%s>', error_message, self)
  581. raise LDAPCursorError(error_message)
  582. self._state._to = safe_dn(destination_dn)
  583. self._state.set_status(STATUS_READY_FOR_MOVING)
  584. def entry_rename(self, new_name):
  585. if self.entry_status not in [STATUS_WRITABLE, STATUS_COMMITTED, STATUS_READY_FOR_RENAMING]:
  586. error_message = 'cannot rename entry, invalid status: ' + self.entry_status
  587. if log_enabled(ERROR):
  588. log(ERROR, '%s for <%s>', error_message, self)
  589. raise LDAPCursorError(error_message)
  590. self._state._to = new_name
  591. self._state.set_status(STATUS_READY_FOR_RENAMING)
  592. @property
  593. def entry_changes(self):
  594. return self._changes