選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 

676 行
33 KiB

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