test_misc.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  2. import json
  3. from io import StringIO
  4. from socket import gethostbyname
  5. from unittest.mock import patch
  6. from urllib.parse import urlparse
  7. import odoo
  8. from odoo.http import root
  9. from odoo.tests import tagged
  10. from odoo.tests.common import HOST, new_test_user, get_db_name
  11. from odoo.tools import config, file_path
  12. from odoo.addons.test_http.controllers import CT_JSON
  13. from .test_common import TestHttpBase
  14. @tagged('post_install', '-at_install')
  15. class TestHttpMisc(TestHttpBase):
  16. def test_misc0_redirect(self):
  17. res = self.nodb_url_open('/test_http//greeting')
  18. self.assertEqual(res.status_code, 404)
  19. def test_misc1_reverse_proxy(self):
  20. # client <-> reverse-proxy <-> odoo
  21. client_ip = '127.0.0.16'
  22. reverseproxy_ip = gethostbyname(HOST)
  23. host = 'mycompany.odoo.com'
  24. headers = {
  25. 'Host': '',
  26. 'X-Forwarded-For': client_ip,
  27. 'X-Forwarded-Host': host,
  28. 'X-Forwarded-Proto': 'https'
  29. }
  30. # Don't trust client-sent forwarded headers
  31. with patch.object(config, 'options', {**config.options, 'proxy_mode': False}):
  32. res = self.nodb_url_open('/test_http/wsgi_environ', headers=headers)
  33. self.assertEqual(res.status_code, 200)
  34. self.assertEqual(res.json()['REMOTE_ADDR'], reverseproxy_ip)
  35. self.assertEqual(res.json()['HTTP_HOST'], '')
  36. # Trust proxy-sent forwarded headers
  37. with patch.object(config, 'options', {**config.options, 'proxy_mode': True}):
  38. res = self.nodb_url_open('/test_http/wsgi_environ', headers=headers)
  39. self.assertEqual(res.status_code, 200)
  40. self.assertEqual(res.json()['REMOTE_ADDR'], client_ip)
  41. self.assertEqual(res.json()['HTTP_HOST'], host)
  42. def test_misc2_local_redirect(self):
  43. def local_redirect(path):
  44. fake_req = odoo.tools.misc.DotDict(db=False)
  45. return odoo.http.Request.redirect(fake_req, path, local=True).headers['Location']
  46. self.assertEqual(local_redirect('https://www.example.com/hello?a=b'), '/hello?a=b')
  47. self.assertEqual(local_redirect('/hello?a=b'), '/hello?a=b')
  48. self.assertEqual(local_redirect('hello?a=b'), '/hello?a=b')
  49. self.assertEqual(local_redirect('www.example.com/hello?a=b'), '/www.example.com/hello?a=b')
  50. self.assertEqual(local_redirect('https://www.example.comhttps://www.example2.com/hello?a=b'), '/www.example2.com/hello?a=b')
  51. self.assertEqual(local_redirect('https://https://www.example.com/hello?a=b'), '/www.example.com/hello?a=b')
  52. def test_misc3_is_static_file(self):
  53. uri = 'test_http/static/src/img/gizeh.png'
  54. path = file_path(uri)
  55. # Valid URLs
  56. self.assertEqual(root.get_static_file(f'/{uri}'), path, "Valid file")
  57. self.assertEqual(root.get_static_file(f'odoo.com/{uri}', host='odoo.com'), path, "Valid file with valid host")
  58. self.assertEqual(root.get_static_file(f'http://odoo.com/{uri}', host='odoo.com'), path, "Valid file with valid host")
  59. # Invalid URLs
  60. self.assertIsNone(root.get_static_file('/test_http/i-dont-exist'), "File doesn't exist")
  61. self.assertIsNone(root.get_static_file('/test_http/__manifest__.py'), "File is not static")
  62. self.assertIsNone(root.get_static_file(f'odoo.com/{uri}'), "No host allowed")
  63. self.assertIsNone(root.get_static_file(f'http://odoo.com/{uri}'), "No host allowed")
  64. def test_misc4_rpc_qweb(self):
  65. jack = new_test_user(self.env, 'jackoneill', context={'lang': 'en_US'})
  66. milky_way = self.env.ref('test_http.milky_way')
  67. payload = json.dumps({'jsonrpc': '2.0', 'method': 'call', 'id': None, 'params': {
  68. 'service': 'object', 'method': 'execute', 'args': [
  69. get_db_name(), jack.id, 'jackoneill', 'test_http.galaxy', 'render', milky_way.id
  70. ]
  71. }})
  72. for method in (self.db_url_open, self.nodb_url_open):
  73. with self.subTest(method=method.__name__):
  74. res = method('/jsonrpc', data=payload, headers=CT_JSON)
  75. res.raise_for_status()
  76. res_rpc = res.json()
  77. self.assertNotIn('error', res_rpc.keys(), res_rpc.get('error', {}).get('data', {}).get('message'))
  78. self.assertIn(milky_way.name, res_rpc['result'], "QWeb template was correctly rendered")
  79. def test_misc5_upload_file_retry(self):
  80. from odoo.addons.test_http import controllers # pylint: disable=C0415
  81. with patch.object(controllers, "should_fail", True), StringIO("Hello world!") as file:
  82. res = self.url_open("/test_http/upload_file", files={"ufile": file}, timeout=None)
  83. self.assertEqual(res.status_code, 200)
  84. self.assertEqual(res.text, file.getvalue())
  85. @tagged('post_install', '-at_install')
  86. class TestHttpCors(TestHttpBase):
  87. def test_cors0_http_default(self):
  88. res_opt = self.opener.options(f'{self.base_url()}/test_http/cors_http_default', timeout=10, allow_redirects=False)
  89. self.assertIn(res_opt.status_code, (200, 204))
  90. self.assertEqual(res_opt.headers.get('Access-Control-Allow-Origin'), '*')
  91. self.assertEqual(res_opt.headers.get('Access-Control-Allow-Methods'), 'GET, POST')
  92. self.assertEqual(res_opt.headers.get('Access-Control-Max-Age'), '86400') # one day
  93. self.assertEqual(res_opt.headers.get('Access-Control-Allow-Headers'), 'Origin, X-Requested-With, Content-Type, Accept, Authorization')
  94. res_get = self.url_open('/test_http/cors_http_default')
  95. self.assertEqual(res_get.status_code, 200)
  96. self.assertEqual(res_get.headers.get('Access-Control-Allow-Origin'), '*')
  97. self.assertEqual(res_get.headers.get('Access-Control-Allow-Methods'), 'GET, POST')
  98. def test_cors1_http_methods(self):
  99. res_opt = self.opener.options(f'{self.base_url()}/test_http/cors_http_methods', timeout=10, allow_redirects=False)
  100. self.assertIn(res_opt.status_code, (200, 204))
  101. self.assertEqual(res_opt.headers.get('Access-Control-Allow-Origin'), '*')
  102. self.assertEqual(res_opt.headers.get('Access-Control-Allow-Methods'), 'GET, PUT')
  103. self.assertEqual(res_opt.headers.get('Access-Control-Max-Age'), '86400') # one day
  104. self.assertEqual(res_opt.headers.get('Access-Control-Allow-Headers'), 'Origin, X-Requested-With, Content-Type, Accept, Authorization')
  105. res_post = self.url_open('/test_http/cors_http_methods')
  106. self.assertEqual(res_post.status_code, 200)
  107. self.assertEqual(res_post.headers.get('Access-Control-Allow-Origin'), '*')
  108. self.assertEqual(res_post.headers.get('Access-Control-Allow-Methods'), 'GET, PUT')
  109. def test_cors2_json(self):
  110. res_opt = self.opener.options(f'{self.base_url()}/test_http/cors_json', timeout=10, allow_redirects=False)
  111. self.assertIn(res_opt.status_code, (200, 204), res_opt.text)
  112. self.assertEqual(res_opt.headers.get('Access-Control-Allow-Origin'), '*')
  113. self.assertEqual(res_opt.headers.get('Access-Control-Allow-Methods'), 'POST')
  114. self.assertEqual(res_opt.headers.get('Access-Control-Max-Age'), '86400') # one day
  115. self.assertEqual(res_opt.headers.get('Access-Control-Allow-Headers'), 'Origin, X-Requested-With, Content-Type, Accept, Authorization')
  116. res_post = self.url_open('/test_http/cors_json', data=json.dumps({'params': {}}), headers=CT_JSON)
  117. self.assertEqual(res_post.status_code, 200)
  118. self.assertEqual(res_post.headers.get('Access-Control-Allow-Origin'), '*')
  119. self.assertEqual(res_post.headers.get('Access-Control-Allow-Methods'), 'POST')
  120. @tagged('post_install', '-at_install')
  121. class TestHttpEnsureDb(TestHttpBase):
  122. def setUp(self):
  123. super().setUp()
  124. self.db_list = ['db0', 'db1']
  125. def test_ensure_db0_db_selector(self):
  126. res = self.multidb_url_open('/test_http/ensure_db')
  127. res.raise_for_status()
  128. self.assertEqual(res.status_code, 303)
  129. self.assertEqual(urlparse(res.headers.get('Location', '')).path, '/web/database/selector')
  130. def test_ensure_db1_grant_db(self):
  131. res = self.multidb_url_open('/test_http/ensure_db?db=db0', timeout=10000)
  132. res.raise_for_status()
  133. self.assertEqual(res.status_code, 302)
  134. self.assertEqual(urlparse(res.headers.get('Location', '')).path, '/test_http/ensure_db')
  135. self.assertEqual(odoo.http.root.session_store.get(res.cookies['session_id']).db, 'db0')
  136. # follow the redirection
  137. res = self.multidb_url_open('/test_http/ensure_db')
  138. res.raise_for_status()
  139. self.assertEqual(res.status_code, 200)
  140. self.assertEqual(res.text, 'db0')
  141. def test_ensure_db2_use_session_db(self):
  142. session = self.authenticate(None, None)
  143. session.db = 'db0'
  144. odoo.http.root.session_store.save(session)
  145. res = self.multidb_url_open('/test_http/ensure_db')
  146. res.raise_for_status()
  147. self.assertEqual(res.status_code, 200)
  148. self.assertEqual(res.text, 'db0')
  149. def test_ensure_db3_change_db(self):
  150. session = self.authenticate(None, None)
  151. session.db = 'db0'
  152. odoo.http.root.session_store.save(session)
  153. res = self.multidb_url_open('/test_http/ensure_db?db=db1')
  154. res.raise_for_status()
  155. self.assertEqual(res.status_code, 302)
  156. self.assertEqual(urlparse(res.headers.get('Location', '')).path, '/test_http/ensure_db')
  157. new_session = odoo.http.root.session_store.get(res.cookies['session_id'])
  158. self.assertNotEqual(session.sid, new_session.sid)
  159. self.assertEqual(new_session.db, 'db1')
  160. self.assertEqual(new_session.uid, None)
  161. # follow redirection
  162. self.opener.cookies['session_id'] = new_session.sid
  163. res = self.multidb_url_open('/test_http/ensure_db')
  164. res.raise_for_status()
  165. self.assertEqual(res.status_code, 200)
  166. self.assertEqual(res.text, 'db1')