utils.py 6.4KB

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