config.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. # -*- coding: utf-8 -*-
  2. from urllib.parse import urlparse
  3. from paste.deploy.converters import asbool
  4. from tracim_backend.lib.utils.logger import logger
  5. from depot.manager import DepotManager
  6. from tracim_backend.models.data import ActionDescription, ContentType
  7. class CFG(object):
  8. """Object used for easy access to config file parameters."""
  9. def __setattr__(self, key, value):
  10. """
  11. Log-ready setter.
  12. Logs all configuration parameters except password.
  13. :param key:
  14. :param value:
  15. :return:
  16. """
  17. if 'PASSWORD' not in key and \
  18. ('URL' not in key or type(value) == str) and \
  19. 'CONTENT' not in key:
  20. # We do not show PASSWORD for security reason
  21. # we do not show URL because At the time of configuration setup,
  22. # it can't be evaluated
  23. # We do not show CONTENT in order not to pollute log files
  24. logger.info(self, 'CONFIG: [ {} | {} ]'.format(key, value))
  25. else:
  26. logger.info(self, 'CONFIG: [ {} | <value not shown> ]'.format(key))
  27. self.__dict__[key] = value
  28. def __init__(self, settings):
  29. """Parse configuration file."""
  30. ###
  31. # General
  32. ###
  33. mandatory_msg = \
  34. 'ERROR: {} configuration is mandatory. Set it before continuing.'
  35. self.DEPOT_STORAGE_DIR = settings.get(
  36. 'depot_storage_dir',
  37. )
  38. if not self.DEPOT_STORAGE_DIR:
  39. raise Exception(
  40. mandatory_msg.format('depot_storage_dir')
  41. )
  42. self.DEPOT_STORAGE_NAME = settings.get(
  43. 'depot_storage_name',
  44. )
  45. if not self.DEPOT_STORAGE_NAME:
  46. raise Exception(
  47. mandatory_msg.format('depot_storage_name')
  48. )
  49. self.PREVIEW_CACHE_DIR = settings.get(
  50. 'preview_cache_dir',
  51. )
  52. if not self.PREVIEW_CACHE_DIR:
  53. raise Exception(
  54. 'ERROR: preview_cache_dir configuration is mandatory. '
  55. 'Set it before continuing.'
  56. )
  57. self.DATA_UPDATE_ALLOWED_DURATION = int(settings.get(
  58. 'content.update.allowed.duration',
  59. 0,
  60. ))
  61. self.WEBSITE_TITLE = settings.get(
  62. 'website.title',
  63. 'TRACIM',
  64. )
  65. self.WEBSITE_BASE_URL = settings.get(
  66. 'website.base_url',
  67. '',
  68. )
  69. # TODO - G.M - 26-03-2018 - [Cleanup] These params seems deprecated for tracimv2, # nopep8
  70. # Verify this
  71. #
  72. # self.WEBSITE_HOME_TITLE_COLOR = settings.get(
  73. # 'website.title.color',
  74. # '#555',
  75. # )
  76. # self.WEBSITE_HOME_IMAGE_PATH = settings.get(
  77. # '/assets/img/home_illustration.jpg',
  78. # )
  79. # self.WEBSITE_HOME_BACKGROUND_IMAGE_PATH = settings.get(
  80. # '/assets/img/bg.jpg',
  81. # )
  82. #
  83. self.WEBSITE_SERVER_NAME = settings.get(
  84. 'website.server_name',
  85. None,
  86. )
  87. if not self.WEBSITE_SERVER_NAME:
  88. self.WEBSITE_SERVER_NAME = urlparse(self.WEBSITE_BASE_URL).hostname
  89. logger.warning(
  90. self,
  91. 'NOTE: Generated website.server_name parameter from '
  92. 'website.base_url parameter -> {0}'
  93. .format(self.WEBSITE_SERVER_NAME)
  94. )
  95. self.WEBSITE_HOME_TAG_LINE = settings.get(
  96. 'website.home.tag_line',
  97. '',
  98. )
  99. self.WEBSITE_SUBTITLE = settings.get(
  100. 'website.home.subtitle',
  101. '',
  102. )
  103. self.WEBSITE_HOME_BELOW_LOGIN_FORM = settings.get(
  104. 'website.home.below_login_form',
  105. '',
  106. )
  107. self.WEBSITE_TREEVIEW_CONTENT = settings.get(
  108. 'website.treeview.content',
  109. )
  110. self.USER_AUTH_TOKEN_VALIDITY = int(settings.get(
  111. 'user.auth_token.validity',
  112. '604800',
  113. ))
  114. self.DEBUG = asbool(settings.get('debug', False))
  115. # TODO - G.M - 27-03-2018 - [Email] Restore email config
  116. ###
  117. # EMAIL related stuff (notification, reply)
  118. ##
  119. self.EMAIL_NOTIFICATION_NOTIFIED_EVENTS = [
  120. ActionDescription.COMMENT,
  121. ActionDescription.CREATION,
  122. ActionDescription.EDITION,
  123. ActionDescription.REVISION,
  124. ActionDescription.STATUS_UPDATE
  125. ]
  126. self.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS = [
  127. ContentType.Page,
  128. ContentType.Thread,
  129. ContentType.File,
  130. ContentType.Comment,
  131. # ContentType.Folder -- Folder is skipped
  132. ]
  133. if settings.get('email.notification.from'):
  134. raise Exception(
  135. 'email.notification.from configuration is deprecated. '
  136. 'Use instead email.notification.from.email and '
  137. 'email.notification.from.default_label.'
  138. )
  139. self.EMAIL_NOTIFICATION_FROM_EMAIL = settings.get(
  140. 'email.notification.from.email',
  141. 'noreply+{user_id}@trac.im'
  142. )
  143. self.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL = settings.get(
  144. 'email.notification.from.default_label',
  145. 'Tracim Notifications'
  146. )
  147. self.EMAIL_NOTIFICATION_REPLY_TO_EMAIL = settings.get(
  148. 'email.notification.reply_to.email',
  149. )
  150. self.EMAIL_NOTIFICATION_REFERENCES_EMAIL = settings.get(
  151. 'email.notification.references.email'
  152. )
  153. self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML = settings.get(
  154. 'email.notification.content_update.template.html',
  155. )
  156. self.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT = settings.get(
  157. 'email.notification.content_update.template.text',
  158. )
  159. self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML = settings.get(
  160. 'email.notification.created_account.template.html',
  161. './tracim_backend/templates/mail/created_account_body_html.mak',
  162. )
  163. self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT = settings.get(
  164. 'email.notification.created_account.template.text',
  165. './tracim_backend/templates/mail/created_account_body_text.mak',
  166. )
  167. self.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT = settings.get(
  168. 'email.notification.content_update.subject',
  169. )
  170. self.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT = settings.get(
  171. 'email.notification.created_account.subject',
  172. '[{website_title}] Created account',
  173. )
  174. self.EMAIL_NOTIFICATION_PROCESSING_MODE = settings.get(
  175. 'email.notification.processing_mode',
  176. )
  177. self.EMAIL_NOTIFICATION_ACTIVATED = asbool(settings.get(
  178. 'email.notification.activated',
  179. ))
  180. self.EMAIL_NOTIFICATION_SMTP_SERVER = settings.get(
  181. 'email.notification.smtp.server',
  182. )
  183. self.EMAIL_NOTIFICATION_SMTP_PORT = settings.get(
  184. 'email.notification.smtp.port',
  185. )
  186. self.EMAIL_NOTIFICATION_SMTP_USER = settings.get(
  187. 'email.notification.smtp.user',
  188. )
  189. self.EMAIL_NOTIFICATION_SMTP_PASSWORD = settings.get(
  190. 'email.notification.smtp.password',
  191. )
  192. self.EMAIL_NOTIFICATION_LOG_FILE_PATH = settings.get(
  193. 'email.notification.log_file_path',
  194. None,
  195. )
  196. # self.EMAIL_REPLY_ACTIVATED = asbool(settings.get(
  197. # 'email.reply.activated',
  198. # False,
  199. # ))
  200. #
  201. # self.EMAIL_REPLY_IMAP_SERVER = settings.get(
  202. # 'email.reply.imap.server',
  203. # )
  204. # self.EMAIL_REPLY_IMAP_PORT = settings.get(
  205. # 'email.reply.imap.port',
  206. # )
  207. # self.EMAIL_REPLY_IMAP_USER = settings.get(
  208. # 'email.reply.imap.user',
  209. # )
  210. # self.EMAIL_REPLY_IMAP_PASSWORD = settings.get(
  211. # 'email.reply.imap.password',
  212. # )
  213. # self.EMAIL_REPLY_IMAP_FOLDER = settings.get(
  214. # 'email.reply.imap.folder',
  215. # )
  216. # self.EMAIL_REPLY_CHECK_HEARTBEAT = int(settings.get(
  217. # 'email.reply.check.heartbeat',
  218. # 60,
  219. # ))
  220. # self.EMAIL_REPLY_TOKEN = settings.get(
  221. # 'email.reply.token',
  222. # )
  223. # self.EMAIL_REPLY_IMAP_USE_SSL = asbool(settings.get(
  224. # 'email.reply.imap.use_ssl',
  225. # ))
  226. # self.EMAIL_REPLY_IMAP_USE_IDLE = asbool(settings.get(
  227. # 'email.reply.imap.use_idle',
  228. # True,
  229. # ))
  230. # self.EMAIL_REPLY_CONNECTION_MAX_LIFETIME = int(settings.get(
  231. # 'email.reply.connection.max_lifetime',
  232. # 600, # 10 minutes
  233. # ))
  234. # self.EMAIL_REPLY_USE_HTML_PARSING = asbool(settings.get(
  235. # 'email.reply.use_html_parsing',
  236. # True,
  237. # ))
  238. # self.EMAIL_REPLY_USE_TXT_PARSING = asbool(settings.get(
  239. # 'email.reply.use_txt_parsing',
  240. # True,
  241. # ))
  242. # self.EMAIL_REPLY_LOCKFILE_PATH = settings.get(
  243. # 'email.reply.lockfile_path',
  244. # ''
  245. # )
  246. # if not self.EMAIL_REPLY_LOCKFILE_PATH and self.EMAIL_REPLY_ACTIVATED:
  247. # raise Exception(
  248. # mandatory_msg.format('email.reply.lockfile_path')
  249. # )
  250. #
  251. self.EMAIL_PROCESSING_MODE = settings.get(
  252. 'email.processing_mode',
  253. 'sync',
  254. ).upper()
  255. if self.EMAIL_PROCESSING_MODE not in (
  256. self.CST.ASYNC,
  257. self.CST.SYNC,
  258. ):
  259. raise Exception(
  260. 'email.processing_mode '
  261. 'can ''be "{}" or "{}", not "{}"'.format(
  262. self.CST.ASYNC,
  263. self.CST.SYNC,
  264. self.EMAIL_PROCESSING_MODE,
  265. )
  266. )
  267. self.EMAIL_SENDER_REDIS_HOST = settings.get(
  268. 'email.async.redis.host',
  269. 'localhost',
  270. )
  271. self.EMAIL_SENDER_REDIS_PORT = int(settings.get(
  272. 'email.async.redis.port',
  273. 6379,
  274. ))
  275. self.EMAIL_SENDER_REDIS_DB = int(settings.get(
  276. 'email.async.redis.db',
  277. 0,
  278. ))
  279. ###
  280. # WSGIDAV (Webdav server)
  281. ###
  282. # TODO - G.M - 27-03-2018 - [WebDav] Restore wsgidav config
  283. #self.WSGIDAV_CONFIG_PATH = settings.get(
  284. # 'wsgidav.config_path',
  285. # 'wsgidav.conf',
  286. #)
  287. # TODO: Convert to importlib
  288. # http://stackoverflow.com/questions/41063938/use-importlib-instead-imp-for-non-py-file
  289. #self.wsgidav_config = imp.load_source(
  290. # 'wsgidav_config',
  291. # self.WSGIDAV_CONFIG_PATH,
  292. #)
  293. # self.WSGIDAV_PORT = self.wsgidav_config.port
  294. # self.WSGIDAV_CLIENT_BASE_URL = settings.get(
  295. # 'wsgidav.client.base_url',
  296. # None,
  297. # )
  298. #
  299. # if not self.WSGIDAV_CLIENT_BASE_URL:
  300. # self.WSGIDAV_CLIENT_BASE_URL = \
  301. # '{0}:{1}'.format(
  302. # self.WEBSITE_SERVER_NAME,
  303. # self.WSGIDAV_PORT,
  304. # )
  305. # logger.warning(self,
  306. # 'NOTE: Generated wsgidav.client.base_url parameter with '
  307. # 'followings parameters: website.server_name and '
  308. # 'wsgidav.conf port'.format(
  309. # self.WSGIDAV_CLIENT_BASE_URL,
  310. # )
  311. # )
  312. #
  313. # if not self.WSGIDAV_CLIENT_BASE_URL.endswith('/'):
  314. # self.WSGIDAV_CLIENT_BASE_URL += '/'
  315. # TODO - G.M - 27-03-2018 - [Caldav] Restore radicale config
  316. ###
  317. # RADICALE (Caldav server)
  318. ###
  319. # self.RADICALE_SERVER_HOST = settings.get(
  320. # 'radicale.server.host',
  321. # '127.0.0.1',
  322. # )
  323. # self.RADICALE_SERVER_PORT = int(settings.get(
  324. # 'radicale.server.port',
  325. # 5232,
  326. # ))
  327. # # Note: Other parameters needed to work in SSL (cert file, etc)
  328. # self.RADICALE_SERVER_SSL = asbool(settings.get(
  329. # 'radicale.server.ssl',
  330. # False,
  331. # ))
  332. # self.RADICALE_SERVER_FILE_SYSTEM_FOLDER = settings.get(
  333. # 'radicale.server.filesystem.folder',
  334. # )
  335. # if not self.RADICALE_SERVER_FILE_SYSTEM_FOLDER:
  336. # raise Exception(
  337. # mandatory_msg.format('radicale.server.filesystem.folder')
  338. # )
  339. # self.RADICALE_SERVER_ALLOW_ORIGIN = settings.get(
  340. # 'radicale.server.allow_origin',
  341. # None,
  342. # )
  343. # if not self.RADICALE_SERVER_ALLOW_ORIGIN:
  344. # self.RADICALE_SERVER_ALLOW_ORIGIN = self.WEBSITE_BASE_URL
  345. # logger.warning(self,
  346. # 'NOTE: Generated radicale.server.allow_origin parameter with '
  347. # 'followings parameters: website.base_url ({0})'
  348. # .format(self.WEBSITE_BASE_URL)
  349. # )
  350. #
  351. # self.RADICALE_SERVER_REALM_MESSAGE = settings.get(
  352. # 'radicale.server.realm_message',
  353. # 'Tracim Calendar - Password Required',
  354. # )
  355. #
  356. # self.RADICALE_CLIENT_BASE_URL_HOST = settings.get(
  357. # 'radicale.client.base_url.host',
  358. # 'http://{}:{}'.format(
  359. # self.RADICALE_SERVER_HOST,
  360. # self.RADICALE_SERVER_PORT,
  361. # ),
  362. # )
  363. #
  364. # self.RADICALE_CLIENT_BASE_URL_PREFIX = settings.get(
  365. # 'radicale.client.base_url.prefix',
  366. # '/',
  367. # )
  368. # # Ensure finished by '/'
  369. # if '/' != self.RADICALE_CLIENT_BASE_URL_PREFIX[-1]:
  370. # self.RADICALE_CLIENT_BASE_URL_PREFIX += '/'
  371. # if '/' != self.RADICALE_CLIENT_BASE_URL_PREFIX[0]:
  372. # self.RADICALE_CLIENT_BASE_URL_PREFIX \
  373. # = '/' + self.RADICALE_CLIENT_BASE_URL_PREFIX
  374. #
  375. # if not self.RADICALE_CLIENT_BASE_URL_HOST:
  376. # logger.warning(self,
  377. # 'Generated radicale.client.base_url.host parameter with '
  378. # 'followings parameters: website.server_name -> {}'
  379. # .format(self.WEBSITE_SERVER_NAME)
  380. # )
  381. # self.RADICALE_CLIENT_BASE_URL_HOST = self.WEBSITE_SERVER_NAME
  382. #
  383. # self.RADICALE_CLIENT_BASE_URL_TEMPLATE = '{}{}'.format(
  384. # self.RADICALE_CLIENT_BASE_URL_HOST,
  385. # self.RADICALE_CLIENT_BASE_URL_PREFIX,
  386. # )
  387. self.PREVIEW_JPG_RESTRICTED_DIMS = asbool(settings.get(
  388. 'preview.jpg.restricted_dims', False
  389. ))
  390. preview_jpg_allowed_dims_str = settings.get('preview.jpg.allowed_dims', '') # nopep8
  391. allowed_dims = []
  392. if preview_jpg_allowed_dims_str:
  393. for sizes in preview_jpg_allowed_dims_str.split(','):
  394. parts = sizes.split('x')
  395. assert len(parts) == 2
  396. width, height = parts
  397. assert width.isdecimal()
  398. assert height.isdecimal()
  399. size = PreviewDim(int(width), int(height))
  400. allowed_dims.append(size)
  401. if not allowed_dims:
  402. size = PreviewDim(256, 256)
  403. allowed_dims.append(size)
  404. self.PREVIEW_JPG_ALLOWED_DIMS = allowed_dims
  405. def configure_filedepot(self):
  406. depot_storage_name = self.DEPOT_STORAGE_NAME
  407. depot_storage_path = self.DEPOT_STORAGE_DIR
  408. depot_storage_settings = {'depot.storage_path': depot_storage_path}
  409. DepotManager.configure(
  410. depot_storage_name,
  411. depot_storage_settings,
  412. )
  413. class CST(object):
  414. ASYNC = 'ASYNC'
  415. SYNC = 'SYNC'
  416. TREEVIEW_FOLDERS = 'folders'
  417. TREEVIEW_ALL = 'all'
  418. class PreviewDim(object):
  419. def __init__(self, width: int, height: int) -> None:
  420. self.width = width
  421. self.height = height
  422. def __repr__(self):
  423. return "<PreviewDim width:{width} height:{height}>".format(
  424. width=self.width,
  425. height=self.height,
  426. )