middlewares.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import os
  2. import sys
  3. import threading
  4. import time
  5. from datetime import datetime
  6. from xml.etree import ElementTree
  7. import transaction
  8. import yaml
  9. from pyramid.paster import get_appsettings
  10. from wsgidav import util, compat
  11. from wsgidav.middleware import BaseMiddleware
  12. from tracim_backend import CFG
  13. from tracim_backend.lib.core.user import UserApi
  14. from tracim_backend.models import get_engine, get_session_factory, get_tm_session
  15. class TracimWsgiDavDebugFilter(BaseMiddleware):
  16. """
  17. COPY PASTE OF wsgidav.debug_filter.WsgiDavDebugFilter
  18. WITH ADD OF DUMP RESPONSE & REQUEST
  19. """
  20. def __init__(self, application, config):
  21. self._application = application
  22. self._config = config
  23. # self.out = sys.stderr
  24. self.out = sys.stdout
  25. self.passedLitmus = {}
  26. # These methods boost verbose=2 to verbose=3
  27. self.debug_methods = config.get("debug_methods", [])
  28. # Litmus tests containing these string boost verbose=2 to verbose=3
  29. self.debug_litmus = config.get("debug_litmus", [])
  30. # Exit server, as soon as this litmus test has finished
  31. self.break_after_litmus = [
  32. # "locks: 15",
  33. ]
  34. self.last_request_time = '__NOT_SET__'
  35. # We disable request content dump for moment
  36. # if self._config.get('dump_requests'):
  37. # # Monkey patching
  38. # old_parseXmlBody = util.parseXmlBody
  39. # def new_parseXmlBody(environ, allowEmpty=False):
  40. # xml = old_parseXmlBody(environ, allowEmpty)
  41. # self._dump_request(environ, xml)
  42. # return xml
  43. # util.parseXmlBody = new_parseXmlBody
  44. def __call__(self, environ, start_response):
  45. """"""
  46. # srvcfg = environ["wsgidav.config"]
  47. verbose = self._config.get("verbose", 2)
  48. self.last_request_time = '{0}_{1}'.format(
  49. datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S'),
  50. int(round(time.time() * 1000)),
  51. )
  52. method = environ["REQUEST_METHOD"]
  53. debugBreak = False
  54. dumpRequest = False
  55. dumpResponse = False
  56. if verbose >= 3 or self._config.get("dump_requests"):
  57. dumpRequest = dumpResponse = True
  58. # Process URL commands
  59. if "dump_storage" in environ.get("QUERY_STRING"):
  60. dav = environ.get("wsgidav.provider")
  61. if dav.lockManager:
  62. dav.lockManager._dump()
  63. if dav.propManager:
  64. dav.propManager._dump()
  65. # Turn on max. debugging for selected litmus tests
  66. litmusTag = environ.get("HTTP_X_LITMUS",
  67. environ.get("HTTP_X_LITMUS_SECOND"))
  68. if litmusTag and verbose >= 2:
  69. print("----\nRunning litmus test '%s'..." % litmusTag,
  70. file=self.out)
  71. for litmusSubstring in self.debug_litmus:
  72. if litmusSubstring in litmusTag:
  73. verbose = 3
  74. debugBreak = True
  75. dumpRequest = True
  76. dumpResponse = True
  77. break
  78. for litmusSubstring in self.break_after_litmus:
  79. if litmusSubstring in self.passedLitmus and litmusSubstring not in litmusTag:
  80. print(" *** break after litmus %s" % litmusTag,
  81. file=self.out)
  82. sys.exit(-1)
  83. if litmusSubstring in litmusTag:
  84. self.passedLitmus[litmusSubstring] = True
  85. # Turn on max. debugging for selected request methods
  86. if verbose >= 2 and method in self.debug_methods:
  87. verbose = 3
  88. debugBreak = True
  89. dumpRequest = True
  90. dumpResponse = True
  91. # Set debug options to environment
  92. environ["wsgidav.verbose"] = verbose
  93. # environ["wsgidav.debug_methods"] = self.debug_methods
  94. environ["wsgidav.debug_break"] = debugBreak
  95. environ["wsgidav.dump_request_body"] = dumpRequest
  96. environ["wsgidav.dump_response_body"] = dumpResponse
  97. # Dump request headers
  98. if dumpRequest:
  99. print("<%s> --- %s Request ---" % (
  100. threading.currentThread().ident, method), file=self.out)
  101. for k, v in environ.items():
  102. if k == k.upper():
  103. print("%20s: '%s'" % (k, v), file=self.out)
  104. print("\n", file=self.out)
  105. self._dump_request(environ, xml=None)
  106. # Intercept start_response
  107. #
  108. sub_app_start_response = util.SubAppStartResponse()
  109. nbytes = 0
  110. first_yield = True
  111. app_iter = self._application(environ, sub_app_start_response)
  112. for v in app_iter:
  113. # Start response (the first time)
  114. if first_yield:
  115. # Success!
  116. start_response(sub_app_start_response.status,
  117. sub_app_start_response.response_headers,
  118. sub_app_start_response.exc_info)
  119. # Dump response headers
  120. if first_yield and dumpResponse:
  121. print("<%s> --- %s Response(%s): ---" % (
  122. threading.currentThread().ident,
  123. method,
  124. sub_app_start_response.status),
  125. file=self.out)
  126. headersdict = dict(sub_app_start_response.response_headers)
  127. for envitem in headersdict.keys():
  128. print("%s: %s" % (envitem, repr(headersdict[envitem])),
  129. file=self.out)
  130. print("", file=self.out)
  131. # Check, if response is a binary string, otherwise we probably have
  132. # calculated a wrong content-length
  133. assert compat.is_bytes(v), v
  134. # Dump response body
  135. drb = environ.get("wsgidav.dump_response_body")
  136. if compat.is_basestring(drb):
  137. # Middleware provided a formatted body representation
  138. print(drb, file=self.out)
  139. elif drb is True:
  140. # Else dump what we get, (except for long GET responses)
  141. if method == "GET":
  142. if first_yield:
  143. print(v[:50], "...", file=self.out)
  144. elif len(v) > 0:
  145. print(v, file=self.out)
  146. if dumpResponse:
  147. self._dump_response(sub_app_start_response, drb)
  148. drb = environ["wsgidav.dump_response_body"] = None
  149. nbytes += len(v)
  150. first_yield = False
  151. yield v
  152. if hasattr(app_iter, "close"):
  153. app_iter.close()
  154. # Start response (if it hasn't been done yet)
  155. if first_yield:
  156. # Success!
  157. start_response(sub_app_start_response.status,
  158. sub_app_start_response.response_headers,
  159. sub_app_start_response.exc_info)
  160. if dumpResponse:
  161. print("\n<%s> --- End of %s Response (%i bytes) ---" % (
  162. threading.currentThread().ident, method, nbytes), file=self.out)
  163. return
  164. def _dump_response(self, sub_app_start_response, drb):
  165. dump_to_path = self._config.get(
  166. 'dump_requests_path',
  167. '/tmp/wsgidav_dumps',
  168. )
  169. os.makedirs(dump_to_path, exist_ok=True)
  170. dump_file = '{0}/{1}_RESPONSE_{2}.yml'.format(
  171. dump_to_path,
  172. self.last_request_time,
  173. sub_app_start_response.status[0:3],
  174. )
  175. with open(dump_file, 'w+') as f:
  176. dump_content = dict()
  177. headers = {}
  178. for header_tuple in sub_app_start_response.response_headers:
  179. headers[header_tuple[0]] = header_tuple[1]
  180. dump_content['headers'] = headers
  181. if isinstance(drb, str):
  182. dump_content['content'] = drb.replace('PROPFIND XML response body:\n', '')
  183. f.write(yaml.dump(dump_content, default_flow_style=False))
  184. def _dump_request(self, environ, xml):
  185. dump_to_path = self._config.get(
  186. 'dump_requests_path',
  187. '/tmp/wsgidav_dumps',
  188. )
  189. os.makedirs(dump_to_path, exist_ok=True)
  190. dump_file = '{0}/{1}_REQUEST_{2}.yml'.format(
  191. dump_to_path,
  192. self.last_request_time,
  193. environ['REQUEST_METHOD'],
  194. )
  195. with open(dump_file, 'w+') as f:
  196. dump_content = dict()
  197. dump_content['path'] = environ.get('PATH_INFO', '')
  198. dump_content['Authorization'] = environ.get('HTTP_AUTHORIZATION', '')
  199. if xml:
  200. dump_content['content'] = ElementTree.tostring(xml, 'utf-8')
  201. f.write(yaml.dump(dump_content, default_flow_style=False))
  202. class TracimEnforceHTTPS(BaseMiddleware):
  203. def __init__(self, application, config):
  204. super().__init__(application, config)
  205. self._application = application
  206. self._config = config
  207. def __call__(self, environ, start_response):
  208. # TODO - G.M - 06-03-2018 - Check protocol from http header first
  209. # see http://www.bortzmeyer.org/7239.html
  210. # if this params doesn't exist, rely on tracim config
  211. # from tracim.config.app_cfg import CFG
  212. # cfg = CFG.get_instance()
  213. #
  214. # if cfg.WEBSITE_BASE_URL.startswith('https'):
  215. # environ['wsgi.url_scheme'] = 'https'
  216. return self._application(environ, start_response)
  217. class TracimEnv(BaseMiddleware):
  218. def __init__(self, application, config):
  219. super().__init__(application, config)
  220. self._application = application
  221. self._config = config
  222. global_conf = get_appsettings(config['tracim_config']).global_conf
  223. local_conf = get_appsettings(config['tracim_config'], 'tracim_web')
  224. self.settings = global_conf
  225. self.settings.update(local_conf)
  226. self.engine = get_engine(self.settings)
  227. self.session_factory = get_session_factory(self.engine)
  228. self.app_config = CFG(self.settings)
  229. self.app_config.configure_filedepot()
  230. def __call__(self, environ, start_response):
  231. # TODO - G.M - 18-05-2018 - This code should not create trouble
  232. # with thread and database, this should be verify.
  233. # see https://github.com/tracim/tracim_backend/issues/62
  234. tm = transaction.manager
  235. dbsession = get_tm_session(self.session_factory, tm)
  236. environ['tracim_tm'] = tm
  237. environ['tracim_dbsession'] = dbsession
  238. environ['tracim_cfg'] = self.app_config
  239. app = self._application(environ, start_response)
  240. dbsession.close()
  241. return app
  242. class TracimUserSession(BaseMiddleware):
  243. def __init__(self, application, config):
  244. super().__init__(application, config)
  245. self._application = application
  246. self._config = config
  247. def __call__(self, environ, start_response):
  248. environ['tracim_user'] = UserApi(
  249. None,
  250. session=environ['tracim_dbsession'],
  251. config=environ['tracim_cfg'],
  252. ).get_one_by_email(environ['http_authenticator.username'])
  253. return self._application(environ, start_response)