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.

validators.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. """
  2. """
  3. # Created on 2016.08.09
  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. from binascii import a2b_hex
  25. from datetime import datetime
  26. from calendar import timegm
  27. from uuid import UUID
  28. from ... import SEQUENCE_TYPES, STRING_TYPES, NUMERIC_TYPES, INTEGER_TYPES
  29. from .formatters import format_time, format_ad_timestamp
  30. from ...utils.conv import to_raw, to_unicode, ldap_escape_to_bytes
  31. # Validators return True if value is valid, False if value is not valid,
  32. # or a value different from True and False that is a valid value to substitute to the input value
  33. def check_type(input_value, value_type):
  34. if isinstance(input_value, value_type):
  35. return True
  36. if isinstance(input_value, SEQUENCE_TYPES):
  37. for value in input_value:
  38. if not isinstance(value, value_type):
  39. return False
  40. return True
  41. return False
  42. # noinspection PyUnusedLocal
  43. def always_valid(input_value):
  44. return True
  45. def validate_generic_single_value(input_value):
  46. if not isinstance(input_value, SEQUENCE_TYPES):
  47. return True
  48. try: # object couldn't have a __len__ method
  49. if len(input_value) == 1:
  50. return True
  51. except Exception:
  52. pass
  53. return False
  54. def validate_zero_and_minus_one(input_value):
  55. """Accept -1 only (used by pwdLastSet in AD)
  56. """
  57. if not isinstance(input_value, SEQUENCE_TYPES):
  58. if input_value == 0 or input_value == '0' or input_value == -1 or input_value == '-1':
  59. return True
  60. try: # object couldn't have a __len__ method
  61. if len(input_value) == 1 and (input_value == 0 or input_value == '0' or input_value == -1 or input_value == '-1'):
  62. return True
  63. except Exception:
  64. pass
  65. return False
  66. def validate_integer(input_value):
  67. if check_type(input_value, (float, bool)):
  68. return False
  69. if check_type(input_value, INTEGER_TYPES):
  70. return True
  71. if not isinstance(input_value, SEQUENCE_TYPES):
  72. sequence = False
  73. input_value = [input_value]
  74. else:
  75. sequence = True # indicates if a sequence must be returned
  76. valid_values = [] # builds a list of valid int values
  77. from decimal import Decimal, InvalidOperation
  78. for element in input_value:
  79. try: # try to convert any type to int, an invalid conversion raise TypeError or ValueError, doublecheck with Decimal type, if both are valid and equal then then int() value is used
  80. value = to_unicode(element) if isinstance(element, bytes) else element
  81. decimal_value = Decimal(value)
  82. int_value = int(value)
  83. if decimal_value == int_value:
  84. valid_values.append(int_value)
  85. else:
  86. return False
  87. except (ValueError, TypeError, InvalidOperation):
  88. return False
  89. if sequence:
  90. return valid_values
  91. else:
  92. return valid_values[0]
  93. def validate_bytes(input_value):
  94. return check_type(input_value, bytes)
  95. def validate_boolean(input_value):
  96. # it could be a real bool or the string TRUE or FALSE, # only a single valued is allowed
  97. if validate_generic_single_value(input_value): # valid only if a single value or a sequence with a single element
  98. if isinstance(input_value, SEQUENCE_TYPES):
  99. input_value = input_value[0]
  100. if isinstance(input_value, bool):
  101. if input_value:
  102. return 'TRUE'
  103. else:
  104. return 'FALSE'
  105. if str != bytes and isinstance(input_value, bytes): # python3 try to converts bytes to string
  106. input_value = to_unicode(input_value)
  107. if isinstance(input_value, STRING_TYPES):
  108. if input_value.lower() == 'true':
  109. return 'TRUE'
  110. elif input_value.lower() == 'false':
  111. return 'FALSE'
  112. return False
  113. def validate_time_with_0_year(input_value):
  114. # validates generalized time but accept a 0000 year too
  115. # if datetime object doesn't have a timezone it's considered local time and is adjusted to UTC
  116. if not isinstance(input_value, SEQUENCE_TYPES):
  117. sequence = False
  118. input_value = [input_value]
  119. else:
  120. sequence = True # indicates if a sequence must be returned
  121. valid_values = []
  122. changed = False
  123. for element in input_value:
  124. if str != bytes and isinstance(element, bytes): # python3 try to converts bytes to string
  125. element = to_unicode(element)
  126. if isinstance(element, STRING_TYPES): # tries to check if it is already be a Generalized Time
  127. if element.startswith('0000') or isinstance(format_time(to_raw(element)), datetime): # valid Generalized Time string
  128. valid_values.append(element)
  129. else:
  130. return False
  131. elif isinstance(element, datetime):
  132. changed = True
  133. if element.tzinfo: # a datetime with a timezone
  134. valid_values.append(element.strftime('%Y%m%d%H%M%S%z'))
  135. else: # datetime without timezone, assumed local and adjusted to UTC
  136. offset = datetime.now() - datetime.utcnow()
  137. valid_values.append((element - offset).strftime('%Y%m%d%H%M%SZ'))
  138. else:
  139. return False
  140. if changed:
  141. if sequence:
  142. return valid_values
  143. else:
  144. return valid_values[0]
  145. else:
  146. return True
  147. def validate_time(input_value):
  148. # if datetime object doesn't have a timezone it's considered local time and is adjusted to UTC
  149. if not isinstance(input_value, SEQUENCE_TYPES):
  150. sequence = False
  151. input_value = [input_value]
  152. else:
  153. sequence = True # indicates if a sequence must be returned
  154. valid_values = []
  155. changed = False
  156. for element in input_value:
  157. if str != bytes and isinstance(element, bytes): # python3 try to converts bytes to string
  158. element = to_unicode(element)
  159. if isinstance(element, STRING_TYPES): # tries to check if it is already be a Generalized Time
  160. if isinstance(format_time(to_raw(element)), datetime): # valid Generalized Time string
  161. valid_values.append(element)
  162. else:
  163. return False
  164. elif isinstance(element, datetime):
  165. changed = True
  166. if element.tzinfo: # a datetime with a timezone
  167. valid_values.append(element.strftime('%Y%m%d%H%M%S%z'))
  168. else: # datetime without timezone, assumed local and adjusted to UTC
  169. offset = datetime.now() - datetime.utcnow()
  170. valid_values.append((element - offset).strftime('%Y%m%d%H%M%SZ'))
  171. else:
  172. return False
  173. if changed:
  174. if sequence:
  175. return valid_values
  176. else:
  177. return valid_values[0]
  178. else:
  179. return True
  180. def validate_ad_timestamp(input_value):
  181. """
  182. Active Directory stores date/time values as the number of 100-nanosecond intervals
  183. that have elapsed since the 0 hour on January 1, 1601 till the date/time that is being stored.
  184. The time is always stored in Greenwich Mean Time (GMT) in the Active Directory.
  185. """
  186. if not isinstance(input_value, SEQUENCE_TYPES):
  187. sequence = False
  188. input_value = [input_value]
  189. else:
  190. sequence = True # indicates if a sequence must be returned
  191. valid_values = []
  192. changed = False
  193. for element in input_value:
  194. if str != bytes and isinstance(element, bytes): # python3 try to converts bytes to string
  195. element = to_unicode(element)
  196. if isinstance(element, NUMERIC_TYPES):
  197. if 0 <= element <= 9223372036854775807: # min and max for the AD timestamp starting from 12:00 AM January 1, 1601
  198. valid_values.append(element)
  199. else:
  200. return False
  201. elif isinstance(element, STRING_TYPES): # tries to check if it is already be a AD timestamp
  202. if isinstance(format_ad_timestamp(to_raw(element)), datetime): # valid Generalized Time string
  203. valid_values.append(element)
  204. else:
  205. return False
  206. elif isinstance(element, datetime):
  207. changed = True
  208. if element.tzinfo: # a datetime with a timezone
  209. valid_values.append(to_raw((timegm(element.utctimetuple()) + 11644473600) * 10000000, encoding='ascii'))
  210. else: # datetime without timezone, assumed local and adjusted to UTC
  211. offset = datetime.now() - datetime.utcnow()
  212. valid_values.append(to_raw((timegm((element - offset).timetuple()) + 11644473600) * 10000000, encoding='ascii'))
  213. else:
  214. return False
  215. if changed:
  216. if sequence:
  217. return valid_values
  218. else:
  219. return valid_values[0]
  220. else:
  221. return True
  222. def validate_guid(input_value):
  223. """
  224. object guid in uuid format (Novell eDirectory)
  225. """
  226. if not isinstance(input_value, SEQUENCE_TYPES):
  227. sequence = False
  228. input_value = [input_value]
  229. else:
  230. sequence = True # indicates if a sequence must be returned
  231. valid_values = []
  232. changed = False
  233. for element in input_value:
  234. if isinstance(element, STRING_TYPES):
  235. try:
  236. valid_values.append(UUID(element).bytes)
  237. changed = True
  238. except ValueError: # try if the value is an escaped byte sequence
  239. try:
  240. valid_values.append(UUID(element.replace('\\', '')).bytes)
  241. changed = True
  242. continue
  243. except ValueError:
  244. if str != bytes: # python 3
  245. pass
  246. else:
  247. valid_values.append(element)
  248. continue
  249. return False
  250. elif isinstance(element, (bytes, bytearray)): # assumes bytes are valid
  251. valid_values.append(element)
  252. else:
  253. return False
  254. if changed:
  255. if sequence:
  256. return valid_values
  257. else:
  258. return valid_values[0]
  259. else:
  260. return True
  261. def validate_uuid(input_value):
  262. """
  263. object entryUUID in uuid format
  264. """
  265. if not isinstance(input_value, SEQUENCE_TYPES):
  266. sequence = False
  267. input_value = [input_value]
  268. else:
  269. sequence = True # indicates if a sequence must be returned
  270. valid_values = []
  271. changed = False
  272. for element in input_value:
  273. if isinstance(element, STRING_TYPES):
  274. try:
  275. valid_values.append(str(UUID(element)))
  276. changed = True
  277. except ValueError: # try if the value is an escaped byte sequence
  278. try:
  279. valid_values.append(str(UUID(element.replace('\\', ''))))
  280. changed = True
  281. continue
  282. except ValueError:
  283. if str != bytes: # python 3
  284. pass
  285. else:
  286. valid_values.append(element)
  287. continue
  288. return False
  289. elif isinstance(element, (bytes, bytearray)): # assumes bytes are valid
  290. valid_values.append(element)
  291. else:
  292. return False
  293. if changed:
  294. if sequence:
  295. return valid_values
  296. else:
  297. return valid_values[0]
  298. else:
  299. return True
  300. def validate_uuid_le(input_value):
  301. """
  302. Active Directory stores objectGUID in uuid_le format, follows RFC4122 and MS-DTYP:
  303. "{07039e68-4373-264d-a0a7-07039e684373}": string representation big endian, converted to little endian (with or without brace curles)
  304. "689e030773434d26a7a007039e684373": packet representation, already in little endian
  305. "\68\9e\03\07\73\43\4d\26\a7\a0\07\03\9e\68\43\73": bytes representation, already in little endian
  306. byte sequence: already in little endian
  307. """
  308. if not isinstance(input_value, SEQUENCE_TYPES):
  309. sequence = False
  310. input_value = [input_value]
  311. else:
  312. sequence = True # indicates if a sequence must be returned
  313. valid_values = []
  314. changed = False
  315. for element in input_value:
  316. if isinstance(element, STRING_TYPES):
  317. if element[0] == '{' and element[-1] == '}':
  318. valid_values.append(UUID(hex=element).bytes_le) # string representation, value in big endian, converts to little endian
  319. changed = True
  320. elif '-' in element:
  321. valid_values.append(UUID(hex=element).bytes_le) # string representation, value in big endian, converts to little endian
  322. changed = True
  323. elif '\\' in element:
  324. valid_values.append(UUID(bytes_le=ldap_escape_to_bytes(element)).bytes_le) # byte representation, value in little endian
  325. changed = True
  326. elif '-' not in element: # value in little endian
  327. valid_values.append(UUID(bytes_le=a2b_hex(element)).bytes_le) # packet representation, value in little endian, converts to little endian
  328. changed = True
  329. elif isinstance(element, (bytes, bytearray)): # assumes bytes are valid uuid
  330. valid_values.append(element) # value is untouched, must be in little endian
  331. else:
  332. return False
  333. if changed:
  334. if sequence:
  335. return valid_values
  336. else:
  337. return valid_values[0]
  338. else:
  339. return True