123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- """
- """
-
- # Created on 2013.12.08
- #
- # Author: Giovanni Cannata
- #
- # Copyright 2013 - 2018 Giovanni Cannata
- #
- # This file is part of ldap3.
- #
- # ldap3 is free software: you can redistribute it and/or modify
- # it under the terms of the GNU Lesser General Public License as published
- # by the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # ldap3 is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public License
- # along with ldap3 in the COPYING and COPYING.LESSER files.
- # If not, see <http://www.gnu.org/licenses/>.
-
- from base64 import b64encode
- from datetime import datetime
-
- from .. import STRING_TYPES
- from ..core.exceptions import LDAPLDIFError, LDAPExtensionError
- from ..protocol.persistentSearch import EntryChangeNotificationControl
- from ..utils.asn1 import decoder
-
- # LDIF converter RFC 2849 compliant
-
- LDIF_LINE_LENGTH = 78
-
-
- def safe_ldif_string(bytes_value):
- if not bytes_value:
- return True
-
- # check SAFE-INIT-CHAR: < 127, not NUL, LF, CR, SPACE, COLON, LESS-THAN
- if bytes_value[0] > 127 or bytes_value[0] in [0, 10, 13, 32, 58, 60]:
- return False
-
- # check SAFE-CHAR: < 127 not NUL, LF, CR
- if 0 in bytes_value or 10 in bytes_value or 13 in bytes_value:
- return False
-
- # check last char for SPACE
- if bytes_value[-1] == 32:
- return False
-
- for byte in bytes_value:
- if byte > 127:
- return False
-
- return True
-
-
- def _convert_to_ldif(descriptor, value, base64):
- if not value:
- value = ''
- if isinstance(value, STRING_TYPES):
- value = bytearray(value, encoding='utf-8')
-
- if base64 or not safe_ldif_string(value):
- try:
- encoded = b64encode(value)
- except TypeError:
- encoded = b64encode(str(value)) # patch for Python 2.6
- if not isinstance(encoded, str): # in Python 3 b64encode returns bytes in Python 2 returns str
- encoded = str(encoded, encoding='ascii') # Python 3
- line = descriptor + ':: ' + encoded
- else:
- if str is not bytes: # Python 3
- value = str(value, encoding='ascii')
- else: # Python 2
- value = str(value)
- line = descriptor + ': ' + value
-
- return line
-
-
- def add_controls(controls, all_base64):
- lines = []
- if controls:
- for control in controls:
- line = 'control: ' + control[0]
- line += ' ' + ('true' if control[1] else 'false')
- if control[2]:
- lines.append(_convert_to_ldif(line, control[2], all_base64))
-
- return lines
-
-
- def add_attributes(attributes, all_base64):
- lines = []
- oc_attr = None
- # objectclass first, even if this is not specified in the RFC
- for attr in attributes:
- if attr.lower() == 'objectclass':
- for val in attributes[attr]:
- lines.append(_convert_to_ldif(attr, val, all_base64))
- oc_attr = attr
- break
-
- # remaining attributes
- for attr in attributes:
- if attr != oc_attr:
- for val in attributes[attr]:
- lines.append(_convert_to_ldif(attr, val, all_base64))
-
- return lines
-
-
- def sort_ldif_lines(lines, sort_order):
- # sort lines as per custom sort_order
- # sort order is a list of descriptors, lines will be sorted following the same sequence
- return sorted(lines, key=lambda x: ldif_sort(x, sort_order)) if sort_order else lines
-
-
- def search_response_to_ldif(entries, all_base64, sort_order=None):
- lines = []
- for entry in entries:
- if 'dn' in entry:
- lines.append(_convert_to_ldif('dn', entry['dn'], all_base64))
- lines.extend(add_attributes(entry['raw_attributes'], all_base64))
- else:
- raise LDAPLDIFError('unable to convert to LDIF-CONTENT - missing DN')
- if sort_order:
- lines = sort_ldif_lines(lines, sort_order)
- lines.append('')
-
- if lines:
- lines.append('# total number of entries: ' + str(len(entries)))
-
- return lines
-
-
- def add_request_to_ldif(entry, all_base64, sort_order=None):
- lines = []
- if 'entry' in entry:
- lines.append(_convert_to_ldif('dn', entry['entry'], all_base64))
- lines.extend(add_controls(entry['controls'], all_base64))
- lines.append('changetype: add')
- lines.extend(add_attributes(entry['attributes'], all_base64))
- if sort_order:
- lines = sort_ldif_lines(lines, sort_order)
-
- else:
- raise LDAPLDIFError('unable to convert to LDIF-CHANGE-ADD - missing DN ')
-
- return lines
-
-
- def delete_request_to_ldif(entry, all_base64, sort_order=None):
- lines = []
- if 'entry' in entry:
- lines.append(_convert_to_ldif('dn', entry['entry'], all_base64))
- lines.append(add_controls(entry['controls'], all_base64))
- lines.append('changetype: delete')
- if sort_order:
- lines = sort_ldif_lines(lines, sort_order)
- else:
- raise LDAPLDIFError('unable to convert to LDIF-CHANGE-DELETE - missing DN ')
-
- return lines
-
-
- def modify_request_to_ldif(entry, all_base64, sort_order=None):
- lines = []
- if 'entry' in entry:
- lines.append(_convert_to_ldif('dn', entry['entry'], all_base64))
- lines.extend(add_controls(entry['controls'], all_base64))
- lines.append('changetype: modify')
- if 'changes' in entry:
- for change in entry['changes']:
- lines.append(['add', 'delete', 'replace', 'increment'][change['operation']] + ': ' + change['attribute']['type'])
- for value in change['attribute']['value']:
- lines.append(_convert_to_ldif(change['attribute']['type'], value, all_base64))
- lines.append('-')
- if sort_order:
- lines = sort_ldif_lines(lines, sort_order)
- return lines
-
-
- def modify_dn_request_to_ldif(entry, all_base64, sort_order=None):
- lines = []
- if 'entry' in entry:
- lines.append(_convert_to_ldif('dn', entry['entry'], all_base64))
- lines.extend(add_controls(entry['controls'], all_base64))
- lines.append('changetype: modrdn') if 'newSuperior' in entry and entry['newSuperior'] else lines.append('changetype: moddn')
- lines.append(_convert_to_ldif('newrdn', entry['newRdn'], all_base64))
- lines.append('deleteoldrdn: ' + ('1' if entry['deleteOldRdn'] else '0'))
- if 'newSuperior' in entry and entry['newSuperior']:
- lines.append(_convert_to_ldif('newsuperior', entry['newSuperior'], all_base64))
- if sort_order:
- lines = sort_ldif_lines(lines, sort_order)
- else:
- raise LDAPLDIFError('unable to convert to LDIF-CHANGE-MODDN - missing DN ')
-
- return lines
-
-
- def operation_to_ldif(operation_type, entries, all_base64=False, sort_order=None):
- if operation_type == 'searchResponse':
- lines = search_response_to_ldif(entries, all_base64, sort_order)
- elif operation_type == 'addRequest':
- lines = add_request_to_ldif(entries, all_base64, sort_order)
- elif operation_type == 'delRequest':
- lines = delete_request_to_ldif(entries, all_base64, sort_order)
- elif operation_type == 'modifyRequest':
- lines = modify_request_to_ldif(entries, all_base64, sort_order)
- elif operation_type == 'modDNRequest':
- lines = modify_dn_request_to_ldif(entries, all_base64, sort_order)
- else:
- lines = []
-
- ldif_record = []
- # check max line length and split as per note 2 of RFC 2849
- for line in lines:
- if line:
- ldif_record.append(line[0:LDIF_LINE_LENGTH])
- ldif_record.extend([' ' + line[i: i + LDIF_LINE_LENGTH - 1] for i in range(LDIF_LINE_LENGTH, len(line), LDIF_LINE_LENGTH - 1)] if len(line) > LDIF_LINE_LENGTH else [])
- else:
- ldif_record.append('')
-
- return ldif_record
-
-
- def add_ldif_header(ldif_lines):
- if ldif_lines:
- ldif_lines.insert(0, 'version: 1')
-
- return ldif_lines
-
-
- def ldif_sort(line, sort_order):
- for i, descriptor in enumerate(sort_order):
-
- if line and line.startswith(descriptor):
- return i
-
- return len(sort_order) + 1
-
-
- def decode_persistent_search_control(change):
- if 'controls' in change and '2.16.840.1.113730.3.4.7' in change['controls']:
- decoded = dict()
- decoded_control, unprocessed = decoder.decode(change['controls']['2.16.840.1.113730.3.4.7']['value'], asn1Spec=EntryChangeNotificationControl())
- if unprocessed:
- raise LDAPExtensionError('unprocessed value in EntryChangeNotificationControl')
- if decoded_control['changeType'] == 1: # add
- decoded['changeType'] = 'add'
- elif decoded_control['changeType'] == 2: # delete
- decoded['changeType'] = 'delete'
- elif decoded_control['changeType'] == 4: # modify
- decoded['changeType'] = 'modify'
- elif decoded_control['changeType'] == 8: # modify_dn
- decoded['changeType'] = 'modify dn'
- else:
- raise LDAPExtensionError('unknown Persistent Search changeType ' + str(decoded_control['changeType']))
- decoded['changeNumber'] = decoded_control['changeNumber'] if 'changeNumber' in decoded_control else None
- decoded['previousDN'] = decoded_control['previousDN'] if 'previousDN' in decoded_control else None
- return decoded
-
- return None
-
-
- def persistent_search_response_to_ldif(change):
- ldif_lines = ['# ' + datetime.now().isoformat()]
- control = decode_persistent_search_control(change)
- if control:
- if control['changeNumber']:
- ldif_lines.append('# change number: ' + str(control['changeNumber']))
- ldif_lines.append(control['changeType'])
- if control['previousDN']:
- ldif_lines.append('# previous dn: ' + str(control['previousDN']))
- ldif_lines += operation_to_ldif('searchResponse', [change])
-
- return ldif_lines[:-1] # removes "total number of entries"
|