Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 

273 rindas
9.5 KiB

  1. """
  2. """
  3. # Created on 2014.04.26
  4. #
  5. # Author: Giovanni Cannata
  6. #
  7. # Copyright 2014 - 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, b64decode
  25. import datetime
  26. import re
  27. from .. import SEQUENCE_TYPES, STRING_TYPES, NUMERIC_TYPES, get_config_parameter
  28. from ..utils.ciDict import CaseInsensitiveDict
  29. from ..core.exceptions import LDAPDefinitionError
  30. def to_unicode(obj, encoding=None, from_server=False):
  31. """Try to convert bytes (and str in python2) to unicode.
  32. Return object unmodified if python3 string, else raise an exception
  33. """
  34. conf_default_client_encoding = get_config_parameter('DEFAULT_CLIENT_ENCODING')
  35. conf_default_server_encoding = get_config_parameter('DEFAULT_SERVER_ENCODING')
  36. conf_additional_server_encodings = get_config_parameter('ADDITIONAL_SERVER_ENCODINGS')
  37. conf_additional_client_encodings = get_config_parameter('ADDITIONAL_CLIENT_ENCODINGS')
  38. if isinstance(obj, NUMERIC_TYPES):
  39. obj = str(obj)
  40. if isinstance(obj, (bytes, bytearray)):
  41. if from_server: # data from server
  42. if encoding is None:
  43. encoding = conf_default_server_encoding
  44. try:
  45. return obj.decode(encoding)
  46. except UnicodeDecodeError:
  47. for encoding in conf_additional_server_encodings: # AD could have DN not encoded in utf-8 (even if this is not allowed by RFC4510)
  48. try:
  49. return obj.decode(encoding)
  50. except UnicodeDecodeError:
  51. pass
  52. raise UnicodeError("Unable to convert server data to unicode: %r" % obj)
  53. else: # data from client
  54. if encoding is None:
  55. encoding = conf_default_client_encoding
  56. try:
  57. return obj.decode(encoding)
  58. except UnicodeDecodeError:
  59. for encoding in conf_additional_client_encodings: # tries additional encodings
  60. try:
  61. return obj.decode(encoding)
  62. except UnicodeDecodeError:
  63. pass
  64. raise UnicodeError("Unable to convert client data to unicode: %r" % obj)
  65. if isinstance(obj, STRING_TYPES): # python3 strings, python 2 unicode
  66. return obj
  67. raise UnicodeError("Unable to convert type %s to unicode: %r" % (obj.__class__.__name__, obj))
  68. def to_raw(obj, encoding='utf-8'):
  69. """Tries to convert to raw bytes from unicode"""
  70. if isinstance(obj, NUMERIC_TYPES):
  71. obj = str(obj)
  72. if not (isinstance(obj, bytes)):
  73. if isinstance(obj, SEQUENCE_TYPES):
  74. return [to_raw(element) for element in obj]
  75. elif isinstance(obj, STRING_TYPES):
  76. return obj.encode(encoding)
  77. return obj
  78. def escape_filter_chars(text, encoding=None):
  79. """ Escape chars mentioned in RFC4515. """
  80. if encoding is None:
  81. encoding = get_config_parameter('DEFAULT_ENCODING')
  82. try:
  83. text = to_unicode(text, encoding)
  84. escaped = text.replace('\\', '\\5c')
  85. escaped = escaped.replace('*', '\\2a')
  86. escaped = escaped.replace('(', '\\28')
  87. escaped = escaped.replace(')', '\\29')
  88. escaped = escaped.replace('\x00', '\\00')
  89. except Exception: # probably raw bytes values, return escaped bytes value
  90. escaped = to_unicode(escape_bytes(text))
  91. # escape all octets greater than 0x7F that are not part of a valid UTF-8
  92. # escaped = ''.join(c if c <= ord(b'\x7f') else escape_bytes(to_raw(to_unicode(c, encoding))) for c in escaped)
  93. return escaped
  94. def unescape_filter_chars(text, encoding=None):
  95. """ unescape chars mentioned in RFC4515. """
  96. if encoding is None:
  97. encoding = get_config_parameter('DEFAULT_ENCODING')
  98. unescaped = to_raw(text, encoding)
  99. unescaped = unescaped.replace(b'\\5c', b'\\')
  100. unescaped = unescaped.replace(b'\\5C', b'\\')
  101. unescaped = unescaped.replace(b'\\2a', b'*')
  102. unescaped = unescaped.replace(b'\\2A', b'*')
  103. unescaped = unescaped.replace(b'\\28', b'(')
  104. unescaped = unescaped.replace(b'\\29', b')')
  105. unescaped = unescaped.replace(b'\\00', b'\x00')
  106. return unescaped
  107. def escape_bytes(bytes_value):
  108. """ Convert a byte sequence to a properly escaped for LDAP (format BACKSLASH HEX HEX) string"""
  109. if bytes_value:
  110. if str is not bytes: # Python 3
  111. if isinstance(bytes_value, str):
  112. bytes_value = bytearray(bytes_value, encoding='utf-8')
  113. escaped = '\\'.join([('%02x' % int(b)) for b in bytes_value])
  114. else: # Python 2
  115. if isinstance(bytes_value, unicode):
  116. bytes_value = bytes_value.encode('utf-8')
  117. escaped = '\\'.join([('%02x' % ord(b)) for b in bytes_value])
  118. else:
  119. escaped = ''
  120. return ('\\' + escaped) if escaped else ''
  121. def prepare_for_stream(value):
  122. if str is not bytes: # Python 3
  123. return value
  124. else: # Python 2
  125. return value.decode()
  126. def json_encode_b64(obj):
  127. try:
  128. return dict(encoding='base64', encoded=b64encode(obj))
  129. except Exception as e:
  130. raise LDAPDefinitionError('unable to encode ' + str(obj) + ' - ' + str(e))
  131. # noinspection PyProtectedMember
  132. def check_json_dict(json_dict):
  133. # needed for python 2
  134. for k, v in json_dict.items():
  135. if isinstance(v, dict):
  136. check_json_dict(v)
  137. elif isinstance(v, CaseInsensitiveDict):
  138. check_json_dict(v._store)
  139. elif isinstance(v, SEQUENCE_TYPES):
  140. for i, e in enumerate(v):
  141. if isinstance(e, dict):
  142. check_json_dict(e)
  143. elif isinstance(e, CaseInsensitiveDict):
  144. check_json_dict(e._store)
  145. else:
  146. v[i] = format_json(e)
  147. else:
  148. json_dict[k] = format_json(v)
  149. def json_hook(obj):
  150. if hasattr(obj, 'keys') and len(list(obj.keys())) == 2 and 'encoding' in obj.keys() and 'encoded' in obj.keys():
  151. return b64decode(obj['encoded'])
  152. return obj
  153. # noinspection PyProtectedMember
  154. def format_json(obj, iso_format=False):
  155. if isinstance(obj, CaseInsensitiveDict):
  156. return obj._store
  157. if isinstance(obj, datetime.datetime):
  158. return str(obj)
  159. if isinstance(obj, int):
  160. return obj
  161. if isinstance(obj, datetime.timedelta):
  162. if iso_format:
  163. return obj.isoformat()
  164. return str(obj)
  165. if str is bytes: # Python 2
  166. if isinstance(obj, long): # long exists only in python2
  167. return obj
  168. try:
  169. if str is not bytes: # Python 3
  170. if isinstance(obj, bytes):
  171. # return check_escape(str(obj, 'utf-8', errors='strict'))
  172. return str(obj, 'utf-8', errors='strict')
  173. raise LDAPDefinitionError('unable to serialize ' + str(obj))
  174. else: # Python 2
  175. if isinstance(obj, unicode):
  176. return obj
  177. else:
  178. # return unicode(check_escape(obj))
  179. return unicode(obj)
  180. except (TypeError, UnicodeDecodeError):
  181. pass
  182. try:
  183. return json_encode_b64(bytes(obj))
  184. except Exception:
  185. pass
  186. raise LDAPDefinitionError('unable to serialize ' + str(obj))
  187. def is_filter_escaped(text):
  188. if not type(text) == ((str is not bytes) and str or unicode): # requires str for Python 3 and unicode for Python 2
  189. raise ValueError('unicode input expected')
  190. return all(c not in text for c in '()*\0') and not re.search('\\\\([^0-9a-fA-F]|(.[^0-9a-fA-F]))', text)
  191. def ldap_escape_to_bytes(text):
  192. bytesequence = bytearray()
  193. i = 0
  194. try:
  195. if isinstance(text, STRING_TYPES):
  196. while i < len(text):
  197. if text[i] == '\\':
  198. if len(text) > i + 2:
  199. try:
  200. bytesequence.append(int(text[i+1:i+3], 16))
  201. i += 3
  202. continue
  203. except ValueError:
  204. pass
  205. bytesequence.append(92) # "\" ASCII code
  206. else:
  207. raw = to_raw(text[i])
  208. for c in raw:
  209. bytesequence.append(c)
  210. i += 1
  211. elif isinstance(text, (bytes, bytearray)):
  212. while i < len(text):
  213. if text[i] == 92: # "\" ASCII code
  214. if len(text) > i + 2:
  215. try:
  216. bytesequence.append(int(text[i + 1:i + 3], 16))
  217. i += 3
  218. continue
  219. except ValueError:
  220. pass
  221. bytesequence.append(92) # "\" ASCII code
  222. else:
  223. bytesequence.append(text[i])
  224. i += 1
  225. except Exception:
  226. raise LDAPDefinitionError('badly formatted LDAP byte escaped sequence')
  227. return bytes(bytesequence)