No Description
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.

validator.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. #-*- coding: utf-8 -*-
  2. import sys
  3. import os.path
  4. import re
  5. import socket
  6. import inspect
  7. import copy
  8. from lodel.plugin.hooks import LodelHook
  9. ## @package lodel.settings.validator Lodel2 settings validators/cast module
  10. #
  11. # Validator are registered in the SettingValidator class.
  12. # @note to get a list of registered default validators just run
  13. # <pre>$ python scripts/settings_validator.py</pre>
  14. ##@brief Exception class that should be raised when a validation fails
  15. class SettingsValidationError(Exception):
  16. pass
  17. ##@brief Handles settings validators
  18. #
  19. # Class instance are callable objects that takes a value argument (the value to validate). It raises
  20. # a SettingsValidationError if validation fails, else it returns a properly
  21. # casted value.
  22. class SettingValidator(object):
  23. _validators = dict()
  24. _description = dict()
  25. ##@brief Instanciate a validator
  26. def __init__(self, name, none_is_valid = False):
  27. if name is not None and name not in self._validators:
  28. raise NameError("No validator named '%s'" % name)
  29. self.__none_is_valid = none_is_valid
  30. self.__name = name
  31. ##@brief Call the validator
  32. # @param value *
  33. # @return properly casted value
  34. # @throw SettingsValidationError
  35. def __call__(self, value):
  36. if self.__name is None:
  37. return value
  38. if self.__none_is_valid and value is None:
  39. return None
  40. try:
  41. return self._validators[self.__name](value)
  42. except Exception as e:
  43. raise SettingsValidationError(e)
  44. ##@brief Register a new validator
  45. # @param name str : validator name
  46. # @param callback callable : the function that will validate a value
  47. # @param description str
  48. @classmethod
  49. def register_validator(cls, name, callback, description=None):
  50. if name in cls._validators:
  51. raise NameError("A validator named '%s' allready exists" % name)
  52. # Broken test for callable
  53. if not inspect.isfunction(callback) and not inspect.ismethod(callback) and not hasattr(callback, '__call__'):
  54. raise TypeError("Callable expected but got %s" % type(callback))
  55. cls._validators[name] = callback
  56. cls._description[name] = description
  57. ##@brief Get the validator list associated with description
  58. @classmethod
  59. def validators_list(cls):
  60. return copy.copy(cls._description)
  61. ##@brief Create and register a list validator
  62. # @param elt_validator callable : The validator that will be used for validate each elt value
  63. # @param validator_name str
  64. # @param description None | str
  65. # @param separator str : The element separator
  66. # @return A SettingValidator instance
  67. @classmethod
  68. def create_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
  69. def list_validator(value):
  70. res = list()
  71. errors = list()
  72. for elt in value.split(separator):
  73. elt = elt_validator(elt)
  74. if len(elt) > 0:
  75. res.append(elt)
  76. return res
  77. description = "Convert value to an array" if description is None else description
  78. cls.register_validator(
  79. validator_name,
  80. list_validator,
  81. description)
  82. return cls(validator_name)
  83. ##@brief Create and register a list validator which reads an array and returns a string
  84. # @param elt_validator callable : The validator that will be used for validate each elt value
  85. # @param validator_name str
  86. # @param description None | str
  87. # @param separator str : The element separator
  88. # @return A SettingValidator instance
  89. @classmethod
  90. def create_write_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
  91. def write_list_validator(value):
  92. res = ''
  93. errors = list()
  94. for elt in value:
  95. res += elt_validator(elt) + ','
  96. return res[:len(res)-1]
  97. description = "Convert value to a string" if description is None else description
  98. cls.register_validator(
  99. validator_name,
  100. write_list_validator,
  101. description)
  102. return cls(validator_name)
  103. ##@brief Create and register a regular expression validator
  104. # @param pattern str : regex pattern
  105. # @param validator_name str : The validator name
  106. # @param description str : Validator description
  107. # @return a SettingValidator instance
  108. @classmethod
  109. def create_re_validator(cls, pattern, validator_name, description = None):
  110. def re_validator(value):
  111. if not re.match(pattern, value):
  112. raise SettingsValidationError("The value '%s' doesn't match the following pattern '%s'" % pattern)
  113. return value
  114. #registering the validator
  115. cls.register_validator(
  116. validator_name,
  117. re_validator,
  118. ("Match value to '%s'" % pattern) if description is None else description)
  119. return cls(validator_name)
  120. ## @return a list of registered validators
  121. @classmethod
  122. def validators_list_str(cls):
  123. result = ''
  124. for name in sorted(cls._validators.keys()):
  125. result += "\t%016s" % name
  126. if name in cls._description and cls._description[name] is not None:
  127. result += ": %s" % cls._description[name]
  128. result += "\n"
  129. return result
  130. ##@brief Integer value validator callback
  131. def int_val(value):
  132. return int(value)
  133. ##@brief Output file validator callback
  134. # @return A file object (if filename is '-' return sys.stderr)
  135. def file_err_output(value):
  136. if not isinstance(value, str):
  137. raise SettingsValidationError("A string was expected but got '%s' " % value)
  138. if value == '-':
  139. return None
  140. return value
  141. ##@brief Boolean value validator callback
  142. def boolean_val(value):
  143. if isinstance(value, bool):
  144. return value
  145. if value.strip().lower() == 'true' or value.strip() == '1':
  146. value = True
  147. elif value.strip().lower() == 'false' or value.strip() == '0':
  148. value = False
  149. else:
  150. raise SettingsValidationError("A boolean was expected but got '%s' " % value)
  151. return bool(value)
  152. def directory_val(value):
  153. res = SettingValidator('strip')(value)
  154. if not os.path.isdir(res):
  155. raise SettingsValidationError("Folowing path don't exists or is not a directory : '%s'"%res)
  156. return res
  157. def loglevel_val(value):
  158. valids = ['DEBUG', 'INFO', 'SECURITY', 'ERROR', 'CRITICAL']
  159. if value.upper() not in valids:
  160. raise SettingsValidationError(
  161. "The value '%s' is not a valid loglevel" % value)
  162. return value.upper()
  163. def path_val(value):
  164. if value is None or not os.path.exists(value):
  165. raise SettingsValidationError(
  166. "path '%s' doesn't exists" % value)
  167. return value
  168. def none_val(value):
  169. if value is None:
  170. return None
  171. raise SettingsValidationError("This settings cannot be set in configuration file")
  172. def str_val(value):
  173. try:
  174. return str(value)
  175. except Exception as e:
  176. raise SettingsValidationError("Not able to convert value to string : " + str(e))
  177. def host_val(value):
  178. if value == 'localhost':
  179. return value
  180. ok = False
  181. try:
  182. socket.inet_aton(value)
  183. return value
  184. except (TypeError,OSError):
  185. pass
  186. try:
  187. socket.inet_pton(socket.AF_INET6, value)
  188. return value
  189. except (TypeError,OSError):
  190. pass
  191. try:
  192. socket.getaddrinfo(value, 80)
  193. return value
  194. except (TypeError,socket.gaierrror):
  195. msg = "The value '%s' is not a valid host"
  196. raise SettingsValidationError(msg % value)
  197. def emfield_val(value):
  198. spl = value.split('.')
  199. if len(spl) != 2:
  200. msg = "Expected a value in the form CLASSNAME.FIELDNAME but got : %s"
  201. raise SettingsValidationError(msg % value)
  202. value = tuple(spl)
  203. #Late validation hook
  204. @LodelHook('lodel2_dyncode_bootstraped')
  205. def emfield_conf_check(hookname, caller, payload):
  206. from lodel import dyncode
  207. classnames = { cls.__name__.lower():cls for cls in dyncode.dynclasses}
  208. if value[0].lower() not in classnames:
  209. msg = "Following dynamic class do not exists in current EM : %s"
  210. raise SettingsValidationError(msg % value[0])
  211. ccls = classnames[value[0].lower()]
  212. if value[1].lower() not in ccls.fieldnames(True):
  213. msg = "Following field not found in class %s : %s"
  214. raise SettingsValidationError(msg % value)
  215. return value
  216. #
  217. # Default validators registration
  218. #
  219. SettingValidator.register_validator(
  220. 'dummy',
  221. lambda value:value,
  222. 'Validate anything')
  223. SettingValidator.register_validator(
  224. 'none',
  225. none_val,
  226. 'Validate None')
  227. SettingValidator.register_validator(
  228. 'string',
  229. str_val,
  230. 'Validate string values')
  231. SettingValidator.register_validator(
  232. 'strip',
  233. str.strip,
  234. 'String trim')
  235. SettingValidator.register_validator(
  236. 'int',
  237. int_val,
  238. 'Integer value validator')
  239. SettingValidator.register_validator(
  240. 'bool',
  241. boolean_val,
  242. 'Boolean value validator')
  243. SettingValidator.register_validator(
  244. 'errfile',
  245. file_err_output,
  246. 'Error output file validator (return stderr if filename is "-")')
  247. SettingValidator.register_validator(
  248. 'directory',
  249. directory_val,
  250. 'Directory path validator')
  251. SettingValidator.register_validator(
  252. 'loglevel',
  253. loglevel_val,
  254. 'Loglevel validator')
  255. SettingValidator.register_validator(
  256. 'path',
  257. path_val,
  258. 'path validator')
  259. SettingValidator.register_validator(
  260. 'host',
  261. host_val,
  262. 'host validator')
  263. SettingValidator.register_validator(
  264. 'emfield',
  265. emfield_val,
  266. 'EmField name validator')
  267. SettingValidator.create_list_validator(
  268. 'list',
  269. SettingValidator('strip'),
  270. description = "Simple list validator. Validate a list of values separated by ','",
  271. separator = ',')
  272. SettingValidator.create_list_validator(
  273. 'directory_list',
  274. SettingValidator('directory'),
  275. description = "Validator for a list of directory path separated with ','",
  276. separator = ',')
  277. SettingValidator.create_write_list_validator(
  278. 'write_list',
  279. SettingValidator('directory'),
  280. description = "Validator for an array of values which will be set in a string, separated by ','",
  281. separator = ',')
  282. SettingValidator.create_re_validator(
  283. r'^https?://[^\./]+.[^\./]+/?.*$',
  284. 'http_url',
  285. 'Url validator')
  286. #
  287. # Lodel 2 configuration specification
  288. #
  289. ##@brief Global specifications for lodel2 settings
  290. LODEL2_CONF_SPECS = {
  291. 'lodel2': {
  292. 'debug': ( True,
  293. SettingValidator('bool')),
  294. 'plugins_path': ( None,
  295. SettingValidator('list')),
  296. 'plugins': ( "",
  297. SettingValidator('list')),
  298. 'sitename': ( 'noname',
  299. SettingValidator('strip')),
  300. 'runtest': ( False,
  301. SettingValidator('bool')),
  302. },
  303. 'lodel2.logging.*' : {
  304. 'level': ( 'ERROR',
  305. SettingValidator('loglevel')),
  306. 'context': ( False,
  307. SettingValidator('bool')),
  308. 'filename': ( None,
  309. SettingValidator('errfile', none_is_valid = True)),
  310. 'backupcount': ( None,
  311. SettingValidator('int', none_is_valid = True)),
  312. 'maxbytes': ( None,
  313. SettingValidator('int', none_is_valid = True)),
  314. },
  315. 'lodel2.editorialmodel': {
  316. 'emfile': ( 'em.pickle', SettingValidator('strip')),
  317. 'emtranslator': ( 'picklefile', SettingValidator('strip')),
  318. 'dyncode': ( 'leapi_dyncode.py', SettingValidator('strip')),
  319. 'groups': ( '', SettingValidator('list')),
  320. 'editormode': ( False, SettingValidator('bool')),
  321. },
  322. 'lodel2.datasources.*': {
  323. 'read_only': (False, SettingValidator('bool')),
  324. 'identifier': ( None, SettingValidator('string')),
  325. },
  326. 'lodel2.auth': {
  327. 'login_classfield': ('user.login', SettingValidator('emfield')),
  328. 'pass_classfield': ('user.password', SettingValidator('emfield')),
  329. },
  330. }