utils.py 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. # -*- coding: utf-8 -*-
  2. import datetime
  3. import os
  4. import time
  5. import signal
  6. import tg
  7. from tg import config
  8. from tg import require
  9. from tg import response
  10. from tg.controllers.util import abort
  11. from tg.appwrappers.errorpage import ErrorPageApplicationWrapper \
  12. as BaseErrorPageApplicationWrapper
  13. from tg.i18n import ugettext
  14. from tg.support.registry import StackedObjectProxy
  15. from tg.util import LazyString as BaseLazyString
  16. from tg.util import lazify
  17. from redis import Redis
  18. from rq import Queue
  19. from unidecode import unidecode
  20. from wsgidav.middleware import BaseMiddleware
  21. from tracim.lib.base import logger
  22. from webob import Response
  23. from webob.exc import WSGIHTTPException
  24. def exec_time_monitor():
  25. def decorator_func(func):
  26. def wrapper_func(*args, **kwargs):
  27. start = time.time()
  28. retval = func(*args, **kwargs)
  29. end = time.time()
  30. logger.debug(func, 'exec time: {} seconds'.format(end-start))
  31. return retval
  32. return wrapper_func
  33. return decorator_func
  34. class SameValueError(ValueError):
  35. pass
  36. def replace_reset_password_templates(engines):
  37. try:
  38. if engines['text/html'][1] == 'resetpassword.templates.index':
  39. engines['text/html'] = (
  40. 'mako',
  41. 'tracim.templates.reset_password_index',
  42. engines['text/html'][2],
  43. engines['text/html'][3]
  44. )
  45. if engines['text/html'][1] == 'resetpassword.templates.change_password':
  46. engines['text/html'] = (
  47. 'mako',
  48. 'tracim.templates.reset_password_change_password',
  49. engines['text/html'][2],
  50. engines['text/html'][3]
  51. )
  52. except IndexError:
  53. pass
  54. except KeyError:
  55. pass
  56. @property
  57. def NotImplemented():
  58. raise NotImplementedError()
  59. class APIWSGIHTTPException(WSGIHTTPException):
  60. def json_formatter(self, body, status, title, environ):
  61. if self.comment:
  62. msg = '{0}: {1}'.format(title, self.comment)
  63. else:
  64. msg = title
  65. return {
  66. 'code': self.code,
  67. 'msg': msg,
  68. 'detail': self.detail,
  69. }
  70. class api_require(require):
  71. def default_denial_handler(self, reason):
  72. # Add code here if we have to hide 401 errors (security reasons)
  73. abort(response.status_int, reason, passthrough='json')
  74. class ErrorPageApplicationWrapper(BaseErrorPageApplicationWrapper):
  75. # Define here response code to manage in APIWSGIHTTPException
  76. api_managed_error_codes = [
  77. 400, 401, 403, 404,
  78. ]
  79. def __call__(self, controller, environ, context) -> Response:
  80. # We only do ou work when it's /api request
  81. # TODO BS 20161025: Look at PATH_INFO is not smart, find better way
  82. if not environ['PATH_INFO'].startswith('/api'):
  83. return super().__call__(controller, environ, context)
  84. try:
  85. resp = self.next_handler(controller, environ, context)
  86. except: # We catch all exception to display an 500 error json response
  87. if config.get('debug', False): # But in debug, we want to see it
  88. raise
  89. return APIWSGIHTTPException()
  90. # We manage only specified errors codes
  91. if resp.status_int not in self.api_managed_error_codes:
  92. return resp
  93. # Rewrite error in api format
  94. return APIWSGIHTTPException(
  95. code=resp.status_int,
  96. detail=resp.detail,
  97. title=resp.title,
  98. comment=resp.comment,
  99. )
  100. def get_valid_header_file_name(file_name: str) -> str:
  101. """
  102. :param file_name: file name to test
  103. :return: Return given string if compatible to header encoding, or
  104. download.ext if not.
  105. """
  106. try:
  107. file_name.encode('iso-8859-1')
  108. return file_name
  109. except UnicodeEncodeError:
  110. split_file_name = file_name.split('.')
  111. if len(split_file_name) > 1: # If > 1 so file have extension
  112. return 'download.{0}'.format(split_file_name[-1])
  113. return 'download'
  114. def str_as_bool(string: str) -> bool:
  115. if string == '0':
  116. return False
  117. return bool(string)
  118. def str_as_alpha_num_str(unicode_string: str) -> str:
  119. """
  120. convert unicode string to alpha_num-only string.
  121. convert also accented character to ascii equivalent.
  122. :param unicode_string:
  123. :return:
  124. """
  125. ascii_string = unidecode(unicode_string)
  126. alpha_num_string = ''.join(e for e in ascii_string if e.isalnum())
  127. return alpha_num_string
  128. class LazyString(BaseLazyString):
  129. pass
  130. def _lazy_ugettext(text: str):
  131. """
  132. This function test if application context is available
  133. :param text: String to traduce
  134. :return: lazyfied string or string
  135. """
  136. try:
  137. # Test if tg.translator is defined
  138. #
  139. # cf. https://github.com/tracim/tracim/issues/173
  140. #
  141. # HACK - 2017-11-03 - D.A
  142. # Replace context proxyfied by direct access to gettext function
  143. # which is not setup in case the tg2 context is not initialized
  144. tg.translator.gettext # raises a TypeError exception if context not set
  145. return ugettext(text)
  146. except TypeError as e:
  147. logger.debug(_lazy_ugettext, 'TG2 context not available for translation. TypeError: {}'.format(e))
  148. return text
  149. lazy_ugettext = lazify(_lazy_ugettext)
  150. def get_rq_queue(queue_name: str= 'default') -> Queue:
  151. """
  152. :param queue_name: name of queue
  153. :return: wanted queue
  154. """
  155. from tracim.config.app_cfg import CFG
  156. cfg = CFG.get_instance()
  157. return Queue(queue_name, connection=Redis(
  158. host=cfg.EMAIL_SENDER_REDIS_HOST,
  159. port=cfg.EMAIL_SENDER_REDIS_PORT,
  160. db=cfg.EMAIL_SENDER_REDIS_DB,
  161. ))
  162. def current_date_for_filename() -> str:
  163. """
  164. ISO8601 current date, adapted to be used in filename (for
  165. webdav feature for example), with trouble-free characters.
  166. :return: current date as string like "2018-03-19T15.49.27.246592"
  167. """
  168. # INFO - G.M - 19-03-2018 - As ':' is in transform_to_bdd method in
  169. # webdav utils, it may cause trouble. So, it should be replaced to
  170. # a character which will not change in bdd.
  171. return datetime.datetime.now().isoformat().replace(':', '.')
  172. class TracimEnforceHTTPS(BaseMiddleware):
  173. def __init__(self, application, config):
  174. super().__init__(application, config)
  175. self._application = application
  176. self._config = config
  177. def __call__(self, environ, start_response):
  178. # TODO - G.M - 06-03-2018 - Check protocol from http header first
  179. # see http://www.bortzmeyer.org/7239.html
  180. # if this params doesn't exist, rely on tracim config
  181. from tracim.config.app_cfg import CFG
  182. cfg = CFG.get_instance()
  183. if cfg.WEBSITE_BASE_URL.startswith('https'):
  184. environ['wsgi.url_scheme'] = 'https'
  185. return self._application(environ, start_response)