Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

296 wiersze
11 KiB

  1. """
  2. """
  3. # Created on 2013.12.08
  4. #
  5. # Author: Giovanni Cannata
  6. #
  7. # Copyright 2013 - 2020 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 base64 import b64encode
  25. from datetime import datetime
  26. from .. import STRING_TYPES
  27. from ..core.exceptions import LDAPLDIFError, LDAPExtensionError
  28. from ..protocol.persistentSearch import EntryChangeNotificationControl
  29. from ..utils.asn1 import decoder
  30. from ..utils.config import get_config_parameter
  31. # LDIF converter RFC 2849 compliant
  32. conf_ldif_line_length = get_config_parameter('LDIF_LINE_LENGTH')
  33. def safe_ldif_string(bytes_value):
  34. if not bytes_value:
  35. return True
  36. # check SAFE-INIT-CHAR: < 127, not NUL, LF, CR, SPACE, COLON, LESS-THAN
  37. if bytes_value[0] > 127 or bytes_value[0] in [0, 10, 13, 32, 58, 60]:
  38. return False
  39. # check SAFE-CHAR: < 127 not NUL, LF, CR
  40. if 0 in bytes_value or 10 in bytes_value or 13 in bytes_value:
  41. return False
  42. # check last char for SPACE
  43. if bytes_value[-1] == 32:
  44. return False
  45. for byte in bytes_value:
  46. if byte > 127:
  47. return False
  48. return True
  49. def _convert_to_ldif(descriptor, value, base64):
  50. if not value:
  51. value = ''
  52. if isinstance(value, STRING_TYPES):
  53. value = bytearray(value, encoding='utf-8')
  54. if base64 or not safe_ldif_string(value):
  55. try:
  56. encoded = b64encode(value)
  57. except TypeError:
  58. encoded = b64encode(str(value)) # patch for Python 2.6
  59. if not isinstance(encoded, str): # in Python 3 b64encode returns bytes in Python 2 returns str
  60. encoded = str(encoded, encoding='ascii') # Python 3
  61. line = descriptor + ':: ' + encoded
  62. else:
  63. if str is not bytes: # Python 3
  64. value = str(value, encoding='ascii')
  65. else: # Python 2
  66. value = str(value)
  67. line = descriptor + ': ' + value
  68. return line
  69. def add_controls(controls, all_base64):
  70. lines = []
  71. if controls:
  72. for control in controls:
  73. line = 'control: ' + control[0]
  74. line += ' ' + ('true' if control[1] else 'false')
  75. if control[2]:
  76. lines.append(_convert_to_ldif(line, control[2], all_base64))
  77. return lines
  78. def add_attributes(attributes, all_base64):
  79. lines = []
  80. oc_attr = None
  81. # objectclass first, even if this is not specified in the RFC
  82. for attr in attributes:
  83. if attr.lower() == 'objectclass':
  84. for val in attributes[attr]:
  85. lines.append(_convert_to_ldif(attr, val, all_base64))
  86. oc_attr = attr
  87. break
  88. # remaining attributes
  89. for attr in attributes:
  90. if attr != oc_attr and attr in attributes:
  91. for val in attributes[attr]:
  92. lines.append(_convert_to_ldif(attr, val, all_base64))
  93. return lines
  94. def sort_ldif_lines(lines, sort_order):
  95. # sort lines as per custom sort_order
  96. # sort order is a list of descriptors, lines will be sorted following the same sequence
  97. return sorted(lines, key=lambda x: ldif_sort(x, sort_order)) if sort_order else lines
  98. def search_response_to_ldif(entries, all_base64, sort_order=None):
  99. lines = []
  100. if entries:
  101. for entry in entries:
  102. if not entry or entry['type'] != 'searchResEntry':
  103. continue
  104. if 'dn' in entry:
  105. lines.append(_convert_to_ldif('dn', entry['dn'], all_base64))
  106. lines.extend(add_attributes(entry['raw_attributes'], all_base64))
  107. else:
  108. raise LDAPLDIFError('unable to convert to LDIF-CONTENT - missing DN')
  109. if sort_order:
  110. lines = sort_ldif_lines(lines, sort_order)
  111. lines.append('')
  112. if lines:
  113. lines.append('# total number of entries: ' + str(len(entries)))
  114. return lines
  115. def add_request_to_ldif(entry, all_base64, sort_order=None):
  116. lines = []
  117. if 'entry' in entry:
  118. lines.append(_convert_to_ldif('dn', entry['entry'], all_base64))
  119. control_lines = add_controls(entry['controls'], all_base64)
  120. if control_lines:
  121. lines.extend(control_lines)
  122. lines.append('changetype: add')
  123. lines.extend(add_attributes(entry['attributes'], all_base64))
  124. if sort_order:
  125. lines = sort_ldif_lines(lines, sort_order)
  126. else:
  127. raise LDAPLDIFError('unable to convert to LDIF-CHANGE-ADD - missing DN ')
  128. return lines
  129. def delete_request_to_ldif(entry, all_base64, sort_order=None):
  130. lines = []
  131. if 'entry' in entry:
  132. lines.append(_convert_to_ldif('dn', entry['entry'], all_base64))
  133. control_lines = add_controls(entry['controls'], all_base64)
  134. if control_lines:
  135. lines.extend(control_lines)
  136. lines.append('changetype: delete')
  137. if sort_order:
  138. lines = sort_ldif_lines(lines, sort_order)
  139. else:
  140. raise LDAPLDIFError('unable to convert to LDIF-CHANGE-DELETE - missing DN ')
  141. return lines
  142. def modify_request_to_ldif(entry, all_base64, sort_order=None):
  143. lines = []
  144. if 'entry' in entry:
  145. lines.append(_convert_to_ldif('dn', entry['entry'], all_base64))
  146. control_lines = add_controls(entry['controls'], all_base64)
  147. if control_lines:
  148. lines.extend(control_lines)
  149. lines.append('changetype: modify')
  150. if 'changes' in entry:
  151. for change in entry['changes']:
  152. lines.append(['add', 'delete', 'replace', 'increment'][change['operation']] + ': ' + change['attribute']['type'])
  153. for value in change['attribute']['value']:
  154. lines.append(_convert_to_ldif(change['attribute']['type'], value, all_base64))
  155. lines.append('-')
  156. if sort_order:
  157. lines = sort_ldif_lines(lines, sort_order)
  158. return lines
  159. def modify_dn_request_to_ldif(entry, all_base64, sort_order=None):
  160. lines = []
  161. if 'entry' in entry:
  162. lines.append(_convert_to_ldif('dn', entry['entry'], all_base64))
  163. control_lines = add_controls(entry['controls'], all_base64)
  164. if control_lines:
  165. lines.extend(control_lines)
  166. lines.append('changetype: modrdn') if 'newSuperior' in entry and entry['newSuperior'] else lines.append('changetype: moddn')
  167. lines.append(_convert_to_ldif('newrdn', entry['newRdn'], all_base64))
  168. lines.append('deleteoldrdn: ' + ('1' if entry['deleteOldRdn'] else '0'))
  169. if 'newSuperior' in entry and entry['newSuperior']:
  170. lines.append(_convert_to_ldif('newsuperior', entry['newSuperior'], all_base64))
  171. if sort_order:
  172. lines = sort_ldif_lines(lines, sort_order)
  173. else:
  174. raise LDAPLDIFError('unable to convert to LDIF-CHANGE-MODDN - missing DN ')
  175. return lines
  176. def operation_to_ldif(operation_type, entries, all_base64=False, sort_order=None):
  177. if operation_type == 'searchResponse':
  178. lines = search_response_to_ldif(entries, all_base64, sort_order)
  179. elif operation_type == 'addRequest':
  180. lines = add_request_to_ldif(entries, all_base64, sort_order)
  181. elif operation_type == 'delRequest':
  182. lines = delete_request_to_ldif(entries, all_base64, sort_order)
  183. elif operation_type == 'modifyRequest':
  184. lines = modify_request_to_ldif(entries, all_base64, sort_order)
  185. elif operation_type == 'modDNRequest':
  186. lines = modify_dn_request_to_ldif(entries, all_base64, sort_order)
  187. else:
  188. lines = []
  189. ldif_record = []
  190. # check max line length and split as per note 2 of RFC 2849
  191. for line in lines:
  192. if line:
  193. ldif_record.append(line[0:conf_ldif_line_length])
  194. ldif_record.extend([' ' + line[i: i + conf_ldif_line_length - 1] for i in range(conf_ldif_line_length, len(line), conf_ldif_line_length - 1)] if len(line) > conf_ldif_line_length else [])
  195. else:
  196. ldif_record.append('')
  197. return ldif_record
  198. def add_ldif_header(ldif_lines):
  199. if ldif_lines:
  200. ldif_lines.insert(0, 'version: 1')
  201. return ldif_lines
  202. def ldif_sort(line, sort_order):
  203. for i, descriptor in enumerate(sort_order):
  204. if line and line.startswith(descriptor):
  205. return i
  206. return len(sort_order) + 1
  207. def decode_persistent_search_control(change):
  208. if 'controls' in change and '2.16.840.1.113730.3.4.7' in change['controls']:
  209. decoded = dict()
  210. decoded_control, unprocessed = decoder.decode(change['controls']['2.16.840.1.113730.3.4.7']['value'], asn1Spec=EntryChangeNotificationControl())
  211. if unprocessed:
  212. raise LDAPExtensionError('unprocessed value in EntryChangeNotificationControl')
  213. if decoded_control['changeType'] == 1: # add
  214. decoded['changeType'] = 'add'
  215. elif decoded_control['changeType'] == 2: # delete
  216. decoded['changeType'] = 'delete'
  217. elif decoded_control['changeType'] == 4: # modify
  218. decoded['changeType'] = 'modify'
  219. elif decoded_control['changeType'] == 8: # modify_dn
  220. decoded['changeType'] = 'modify dn'
  221. else:
  222. raise LDAPExtensionError('unknown Persistent Search changeType ' + str(decoded_control['changeType']))
  223. decoded['changeNumber'] = decoded_control['changeNumber'] if 'changeNumber' in decoded_control and decoded_control['changeNumber'] is not None and decoded_control['changeNumber'].hasValue() else None
  224. decoded['previousDN'] = decoded_control['previousDN'] if 'previousDN' in decoded_control and decoded_control['previousDN'] is not None and decoded_control['previousDN'].hasValue() else None
  225. return decoded
  226. return None
  227. def persistent_search_response_to_ldif(change):
  228. ldif_lines = ['# ' + datetime.now().isoformat()]
  229. control = decode_persistent_search_control(change)
  230. if control:
  231. if control['changeNumber']:
  232. ldif_lines.append('# change number: ' + str(control['changeNumber']))
  233. ldif_lines.append(control['changeType'])
  234. if control['previousDN']:
  235. ldif_lines.append('# previous dn: ' + str(control['previousDN']))
  236. ldif_lines += operation_to_ldif('searchResponse', [change])
  237. return ldif_lines[:-1] # removes "total number of entries"