tests.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Licensed to Cloudera, Inc. under one
  4. # or more contributor license agreements. See the NOTICE file
  5. # distributed with this work for additional information
  6. # regarding copyright ownership. Cloudera, Inc. licenses this file
  7. # to you under the Apache License, Version 2.0 (the
  8. # "License"); you may not use this file except in compliance
  9. # with the License. You may obtain a copy of the License at
  10. #
  11. # http://www.apache.org/licenses/LICENSE-2.0
  12. #
  13. # Unless required by applicable law or agreed to in writing, software
  14. # distributed under the License is distributed on an "AS IS" BASIS,
  15. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. # See the License for the specific language governing permissions and
  17. # limitations under the License.
  18. import json
  19. import logging
  20. import os
  21. import StringIO
  22. import subprocess
  23. import sys
  24. import time
  25. import proxy.conf
  26. import tempfile
  27. from nose.plugins.attrib import attr
  28. from nose.plugins.skip import SkipTest
  29. from nose.tools import assert_true, assert_false, assert_equal, assert_not_equal, assert_raises, nottest
  30. from django.conf.urls import patterns, url
  31. from django.contrib.auth.models import User
  32. from django.core.urlresolvers import reverse
  33. from django.http import HttpResponse
  34. from django.db.models import query, CharField, SmallIntegerField
  35. from settings import HUE_DESKTOP_VERSION
  36. from beeswax.conf import HIVE_SERVER_HOST
  37. from pig.models import PigScript
  38. from useradmin.models import GroupPermission
  39. import desktop
  40. import desktop.conf
  41. import desktop.urls
  42. import desktop.redaction as redaction
  43. import desktop.views as views
  44. from desktop.appmanager import DESKTOP_APPS
  45. from desktop.lib import django_mako
  46. from desktop.lib.django_test_util import make_logged_in_client
  47. from desktop.lib.paginator import Paginator
  48. from desktop.lib.conf import validate_path
  49. from desktop.lib.django_util import TruncatingModel
  50. from desktop.lib.exceptions_renderable import PopupException
  51. from desktop.lib.test_utils import grant_access
  52. from desktop.models import Directory, Document, Document2, get_data_link, _version_from_properties, HUE_VERSION
  53. from desktop.redaction import logfilter
  54. from desktop.redaction.engine import RedactionPolicy, RedactionRule
  55. from desktop.views import check_config, home
  56. def setup_test_environment():
  57. """
  58. Sets up mako to signal template rendering.
  59. """
  60. django_mako.render_to_string = django_mako.render_to_string_test
  61. setup_test_environment.__test__ = False
  62. def teardown_test_environment():
  63. """
  64. This method is called by nose_runner when
  65. the tests all finish. This helps track
  66. down when tests aren't cleaning up after
  67. themselves and leaving threads hanging around.
  68. """
  69. import threading
  70. import desktop.lib.thread_util
  71. # We should shut down all relevant threads by test completion.
  72. threads = list(threading.enumerate())
  73. if len(threads) > 1:
  74. desktop.lib.thread_util.dump_traceback()
  75. assert 1 == len(threads), threads
  76. django_mako.render_to_string = django_mako.render_to_string_normal
  77. teardown_test_environment.__test__ = False
  78. def test_home():
  79. c = make_logged_in_client(username="test_home", groupname="test_home", recreate=True, is_superuser=False)
  80. user = User.objects.get(username="test_home")
  81. response = c.get(reverse(home))
  82. assert_equal(["notmine", "trash", "mine", "history"], json.loads(response.context['json_tags']).keys())
  83. assert_equal(200, response.status_code)
  84. script, created = PigScript.objects.get_or_create(owner=user)
  85. doc = Document.objects.link(script, owner=script.owner, name='test_home')
  86. response = c.get(reverse(home))
  87. assert_true(str(doc.id) in json.loads(response.context['json_documents']))
  88. response = c.get(reverse(home))
  89. tags = json.loads(response.context['json_tags'])
  90. assert_equal([doc.id], tags['mine'][0]['docs'], tags)
  91. assert_equal([], tags['trash']['docs'], tags)
  92. assert_equal([], tags['history']['docs'], tags)
  93. doc.send_to_trash()
  94. response = c.get(reverse(home))
  95. tags = json.loads(response.context['json_tags'])
  96. assert_equal([], tags['mine'][0]['docs'], tags)
  97. assert_equal([doc.id], tags['trash']['docs'], tags)
  98. assert_equal([], tags['history']['docs'], tags)
  99. doc.restore_from_trash()
  100. response = c.get(reverse(home))
  101. tags = json.loads(response.context['json_tags'])
  102. assert_equal([doc.id], tags['mine'][0]['docs'], tags)
  103. assert_equal([], tags['trash']['docs'], tags)
  104. assert_equal([], tags['history']['docs'], tags)
  105. doc.add_to_history()
  106. response = c.get(reverse(home))
  107. tags = json.loads(response.context['json_tags'])
  108. assert_equal([], tags['mine'][0]['docs'], tags)
  109. assert_equal([], tags['trash']['docs'], tags)
  110. assert_equal([], tags['history']['docs'], tags) # We currently don't fetch [doc.id]
  111. def test_skip_wizard():
  112. c = make_logged_in_client() # is_superuser
  113. response = c.get('/', follow=True)
  114. assert_true(['admin_wizard.mako' in _template.filename for _template in response.templates], [_template.filename for _template in response.templates])
  115. c.cookies['hueLandingPage'] = 'home'
  116. response = c.get('/', follow=True)
  117. assert_true(['home.mako' in _template.filename for _template in response.templates], [_template.filename for _template in response.templates])
  118. c.cookies['hueLandingPage'] = ''
  119. response = c.get('/', follow=True)
  120. assert_true(['admin_wizard.mako' in _template.filename for _template in response.templates], [_template.filename for _template in response.templates])
  121. c = make_logged_in_client(username="test_skip_wizard", password="test_skip_wizard", is_superuser=False)
  122. response = c.get('/', follow=True)
  123. assert_true(['home.mako' in _template.filename for _template in response.templates], [_template.filename for _template in response.templates])
  124. c.cookies['hueLandingPage'] = 'home'
  125. response = c.get('/', follow=True)
  126. assert_true(['home.mako' in _template.filename for _template in response.templates], [_template.filename for _template in response.templates])
  127. c.cookies['hueLandingPage'] = ''
  128. response = c.get('/', follow=True)
  129. assert_true(['home.mako' in _template.filename for _template in response.templates], [_template.filename for _template in response.templates])
  130. def test_log_view():
  131. c = make_logged_in_client()
  132. URL = reverse(views.log_view)
  133. LOG = logging.getLogger(__name__)
  134. LOG.warn('une voix m’a réveillé')
  135. # UnicodeDecodeError: 'ascii' codec can't decode byte... should not happen
  136. response = c.get(URL)
  137. assert_equal(200, response.status_code)
  138. c = make_logged_in_client()
  139. URL = reverse(views.log_view)
  140. LOG = logging.getLogger(__name__)
  141. LOG.warn('Got response: PK\x03\x04\n\x00\x00\x08\x00\x00\xad\x0cN?\x00\x00\x00\x00')
  142. # DjangoUnicodeDecodeError: 'utf8' codec can't decode byte 0xad in position 75: invalid start byte... should not happen
  143. response = c.get(URL)
  144. assert_equal(200, response.status_code)
  145. def test_download_log_view():
  146. c = make_logged_in_client()
  147. URL = reverse(views.download_log_view)
  148. LOG = logging.getLogger(__name__)
  149. LOG.warn(u'une voix m’a réveillé')
  150. # UnicodeDecodeError: 'ascii' codec can't decode byte... should not happen
  151. response = c.get(URL)
  152. assert_equal("application/zip", response.get('Content-Type', ''))
  153. def test_dump_config():
  154. c = make_logged_in_client()
  155. CANARY = "abracadabra"
  156. # Depending on the order of the conf.initialize() in settings, the set_for_testing() are not seen in the global settings variable
  157. clear = HIVE_SERVER_HOST.set_for_testing(CANARY)
  158. response1 = c.get(reverse('desktop.views.dump_config'))
  159. assert_true(CANARY in response1.content, response1.content)
  160. response2 = c.get(reverse('desktop.views.dump_config'), dict(private="true"))
  161. assert_true(CANARY in response2.content)
  162. # There are more private variables...
  163. assert_true(len(response1.content) < len(response2.content))
  164. clear()
  165. CANARY = "(localhost|127\.0\.0\.1):(50030|50070|50060|50075)"
  166. clear = proxy.conf.WHITELIST.set_for_testing(CANARY)
  167. response1 = c.get(reverse('desktop.views.dump_config'))
  168. assert_true(CANARY in response1.content)
  169. clear()
  170. # Malformed port per HUE-674
  171. CANARY = "asdfoijaoidfjaosdjffjfjaoojosjfiojdosjoidjfoa"
  172. clear = HIVE_SERVER_HOST.set_for_testing(CANARY)
  173. response1 = c.get(reverse('desktop.views.dump_config'))
  174. assert_true(CANARY in response1.content, response1.content)
  175. clear()
  176. CANARY = '/tmp/spacé.dat'
  177. finish = proxy.conf.WHITELIST.set_for_testing(CANARY)
  178. try:
  179. response = c.get(reverse('desktop.views.dump_config'))
  180. assert_true(CANARY in response.content, response.content)
  181. finally:
  182. finish()
  183. # Not showing some passwords
  184. response = c.get(reverse('desktop.views.dump_config'))
  185. assert_false('bind_password' in response.content)
  186. # Login as someone else
  187. client_not_me = make_logged_in_client(username='not_me', is_superuser=False, groupname='test')
  188. grant_access("not_me", "test", "desktop")
  189. response = client_not_me.get(reverse('desktop.views.dump_config'))
  190. assert_true("You must be a superuser" in response.content, response.content)
  191. os.environ["HUE_CONF_DIR"] = "/tmp/test_hue_conf_dir"
  192. resp = c.get(reverse('desktop.views.dump_config'))
  193. del os.environ["HUE_CONF_DIR"]
  194. assert_true('/tmp/test_hue_conf_dir' in resp.content, resp)
  195. def hue_version():
  196. global HUE_VERSION
  197. HUE_VERSION_BAK = HUE_VERSION
  198. try:
  199. assert_equal('3.9.0-cdh5.9.0-SNAPSHOT', _version_from_properties(StringIO.StringIO("""# Autogenerated build properties
  200. version=3.9.0-cdh5.9.0-SNAPSHOT
  201. git.hash=f5fbe90b6a1d0c186b0ddc6e65ce5fc8d24725c8
  202. cloudera.hash=f5fbe90b6a1d0c186b0ddc6e65ce5fc8d24725c8aaaaa""")))
  203. assert_equal(HUE_DESKTOP_VERSION, _version_from_properties(StringIO.StringIO("""# Autogenerated build properties
  204. version=3.9.0-cdh5.9.0-SNAPSHOT git.hash=f5fbe90b6a1d0c186b0ddc6e65ce5fc8d24725c8 cloudera.hash=f5fbe90b6a1d0c186b0ddc6e65ce5fc8d24725c8aaaaa""")))
  205. assert_equal(HUE_DESKTOP_VERSION, _version_from_properties(StringIO.StringIO('')))
  206. finally:
  207. HUE_VERSION = HUE_VERSION_BAK
  208. def test_prefs():
  209. c = make_logged_in_client()
  210. # Get everything
  211. response = c.get('/desktop/prefs/')
  212. assert_equal('{}', response.content)
  213. # Set and get
  214. response = c.get('/desktop/prefs/foo', dict(set="bar"))
  215. assert_equal('true', response.content)
  216. response = c.get('/desktop/prefs/foo')
  217. assert_equal('"bar"', response.content)
  218. # Reset (use post this time)
  219. c.post('/desktop/prefs/foo', dict(set="baz"))
  220. response = c.get('/desktop/prefs/foo')
  221. assert_equal('"baz"', response.content)
  222. # Check multiple values
  223. c.post('/desktop/prefs/elephant', dict(set="room"))
  224. response = c.get('/desktop/prefs/')
  225. assert_true("baz" in response.content)
  226. assert_true("room" in response.content)
  227. # Delete everything
  228. c.get('/desktop/prefs/elephant', dict(delete=""))
  229. c.get('/desktop/prefs/foo', dict(delete=""))
  230. response = c.get('/desktop/prefs/')
  231. assert_equal('{}', response.content)
  232. # Check non-existent value
  233. response = c.get('/desktop/prefs/doesNotExist')
  234. assert_equal('null', response.content)
  235. def test_status_bar():
  236. """
  237. Subs out the status_bar_views registry with temporary examples.
  238. Tests handling of errors on view functions.
  239. """
  240. backup = views._status_bar_views
  241. views._status_bar_views = []
  242. c = make_logged_in_client()
  243. views.register_status_bar_view(lambda _: HttpResponse("foo", status=200))
  244. views.register_status_bar_view(lambda _: HttpResponse("bar"))
  245. views.register_status_bar_view(lambda _: None)
  246. def f(r):
  247. raise Exception()
  248. views.register_status_bar_view(f)
  249. response = c.get("/desktop/status_bar")
  250. assert_equal("foobar", response.content)
  251. views._status_bar_views = backup
  252. def test_paginator():
  253. """
  254. Test that the paginator works with partial list.
  255. """
  256. def assert_page(page, data, start, end):
  257. assert_equal(page.object_list, data)
  258. assert_equal(page.start_index(), start)
  259. assert_equal(page.end_index(), end)
  260. # First page 1-20
  261. obj = range(20)
  262. pgn = Paginator(obj, per_page=20, total=25)
  263. assert_page(pgn.page(1), obj, 1, 20)
  264. # Second page 21-25
  265. obj = range(5)
  266. pgn = Paginator(obj, per_page=20, total=25)
  267. assert_page(pgn.page(2), obj, 21, 25)
  268. # Handle extra data on first page (22 items on a 20-page)
  269. obj = range(22)
  270. pgn = Paginator(obj, per_page=20, total=25)
  271. assert_page(pgn.page(1), range(20), 1, 20)
  272. # Handle extra data on second page (22 items on a 20-page)
  273. obj = range(22)
  274. pgn = Paginator(obj, per_page=20, total=25)
  275. assert_page(pgn.page(2), range(5), 21, 25)
  276. # Handle total < len(obj). Only works for QuerySet.
  277. obj = query.QuerySet()
  278. obj._result_cache = range(10)
  279. pgn = Paginator(obj, per_page=10, total=9)
  280. assert_page(pgn.page(1), range(10), 1, 10)
  281. # Still works with a normal complete list
  282. obj = range(25)
  283. pgn = Paginator(obj, per_page=20)
  284. assert_page(pgn.page(1), range(20), 1, 20)
  285. assert_page(pgn.page(2), range(20, 25), 21, 25)
  286. def test_thread_dump():
  287. c = make_logged_in_client()
  288. response = c.get("/desktop/debug/threads")
  289. assert_true("test_thread_dump" in response.content)
  290. def test_truncating_model():
  291. class TinyModel(TruncatingModel):
  292. short_field = CharField(max_length=10)
  293. non_string_field = SmallIntegerField()
  294. a = TinyModel()
  295. a.short_field = 'a' * 9 # One less than it's max length
  296. assert_true(a.short_field == 'a' * 9, 'Short-enough field does not get truncated')
  297. a.short_field = 'a' * 11 # One more than it's max_length
  298. assert_true(a.short_field == 'a' * 10, 'Too-long field gets truncated')
  299. a.non_string_field = 10**10
  300. assert_true(a.non_string_field == 10**10, 'non-string fields are not truncated')
  301. def test_error_handling():
  302. raise SkipTest
  303. restore_django_debug = desktop.conf.DJANGO_DEBUG_MODE.set_for_testing(False)
  304. restore_500_debug = desktop.conf.HTTP_500_DEBUG_MODE.set_for_testing(False)
  305. exc_msg = "error_raising_view: Test earráid handling"
  306. def error_raising_view(request, *args, **kwargs):
  307. raise Exception(exc_msg)
  308. def popup_exception_view(request, *args, **kwargs):
  309. raise PopupException(exc_msg, title="earráid", detail=exc_msg)
  310. # Add an error view
  311. error_url_pat = patterns('',
  312. url('^500_internal_error$', error_raising_view),
  313. url('^popup_exception$', popup_exception_view))
  314. desktop.urls.urlpatterns.extend(error_url_pat)
  315. try:
  316. def store_exc_info(*args, **kwargs):
  317. pass
  318. # Disable the test client's exception forwarding
  319. c = make_logged_in_client()
  320. c.store_exc_info = store_exc_info
  321. response = c.get('/500_internal_error')
  322. assert_true(any(["500.mako" in _template.filename for _template in response.templates]))
  323. assert_true('Thank you for your patience' in response.content)
  324. assert_true(exc_msg not in response.content)
  325. # Now test the 500 handler with backtrace
  326. desktop.conf.HTTP_500_DEBUG_MODE.set_for_testing(True)
  327. response = c.get('/500_internal_error')
  328. assert_equal(response.template.name, 'Technical 500 template')
  329. assert_true(exc_msg in response.content)
  330. # PopupException
  331. response = c.get('/popup_exception')
  332. assert_true(any(["popup_error.mako" in _template.filename for _template in response.templates]))
  333. assert_true(exc_msg in response.content)
  334. finally:
  335. # Restore the world
  336. for i in error_url_pat:
  337. desktop.urls.urlpatterns.remove(i)
  338. restore_django_debug()
  339. restore_500_debug()
  340. def test_desktop_permissions():
  341. USERNAME = 'test_core_permissions'
  342. GROUPNAME = 'default'
  343. desktop.conf.REDIRECT_WHITELIST.set_for_testing('^\/.*$,^http:\/\/testserver\/.*$')
  344. c = make_logged_in_client(USERNAME, groupname=GROUPNAME, recreate=True, is_superuser=False)
  345. # Access to the basic works
  346. assert_equal(200, c.get('/accounts/login/', follow=True).status_code)
  347. assert_equal(200, c.get('/accounts/logout', follow=True).status_code)
  348. assert_equal(200, c.get('/home', follow=True).status_code)
  349. def test_app_permissions():
  350. USERNAME = 'test_app_permissions'
  351. GROUPNAME = 'impala_only'
  352. desktop.conf.REDIRECT_WHITELIST.set_for_testing('^\/.*$,^http:\/\/testserver\/.*$')
  353. c = make_logged_in_client(USERNAME, groupname=GROUPNAME, recreate=True, is_superuser=False)
  354. # Reset all perms
  355. GroupPermission.objects.filter(group__name=GROUPNAME).delete()
  356. def check_app(status_code, app_name):
  357. if app_name in DESKTOP_APPS:
  358. assert_equal(
  359. status_code,
  360. c.get('/' + app_name, follow=True).status_code,
  361. 'status_code=%s app_name=%s' % (status_code, app_name))
  362. # Access to nothing
  363. check_app(401, 'beeswax')
  364. check_app(401, 'impala')
  365. check_app(401, 'hbase')
  366. # Add access to beeswax
  367. grant_access(USERNAME, GROUPNAME, "beeswax")
  368. check_app(200, 'beeswax')
  369. check_app(401, 'impala')
  370. check_app(401, 'hbase')
  371. # Add access to hbase
  372. grant_access(USERNAME, GROUPNAME, "hbase")
  373. check_app(200, 'beeswax')
  374. check_app(401, 'impala')
  375. check_app(200, 'hbase')
  376. # Reset all perms
  377. GroupPermission.objects.filter(group__name=GROUPNAME).delete()
  378. check_app(401, 'beeswax')
  379. check_app(401, 'impala')
  380. check_app(401, 'hbase')
  381. # Test only impala perm
  382. grant_access(USERNAME, GROUPNAME, "impala")
  383. check_app(401, 'beeswax')
  384. check_app(200, 'impala')
  385. check_app(401, 'hbase')
  386. def test_error_handling_failure():
  387. # Change rewrite_user to call has_hue_permission
  388. # Try to get filebrowser page
  389. # test for default 500 page
  390. # Restore rewrite_user
  391. import desktop.auth.backend
  392. c = make_logged_in_client()
  393. restore_django_debug = desktop.conf.DJANGO_DEBUG_MODE.set_for_testing(False)
  394. restore_500_debug = desktop.conf.HTTP_500_DEBUG_MODE.set_for_testing(False)
  395. original_rewrite_user = desktop.auth.backend.rewrite_user
  396. def rewrite_user(user):
  397. user = original_rewrite_user(user)
  398. delattr(user, 'has_hue_permission')
  399. return user
  400. original_rewrite_user = desktop.auth.backend.rewrite_user
  401. desktop.auth.backend.rewrite_user = rewrite_user
  402. try:
  403. # Make sure we are showing default 500.html page.
  404. # See django.test.client#L246
  405. assert_raises(AttributeError, c.get, reverse('desktop.views.dump_config'))
  406. finally:
  407. # Restore the world
  408. restore_django_debug()
  409. restore_500_debug()
  410. desktop.auth.backend.rewrite_user = original_rewrite_user
  411. def test_404_handling():
  412. view_name = '/the-view-that-is-not-there'
  413. c = make_logged_in_client()
  414. response = c.get(view_name)
  415. assert_true(any(['404.mako' in _template.filename for _template in response.templates]), response.templates)
  416. assert_true('not found' in response.content)
  417. assert_true(view_name in response.content)
  418. class RecordingHandler(logging.Handler):
  419. def __init__(self, *args, **kwargs):
  420. logging.Handler.__init__(self, *args, **kwargs)
  421. self.records = []
  422. def emit(self, r):
  423. self.records.append(r)
  424. def test_log_event():
  425. c = make_logged_in_client()
  426. root = logging.getLogger("desktop.views.log_frontend_event")
  427. handler = RecordingHandler()
  428. root.addHandler(handler)
  429. c.get("/desktop/log_frontend_event?level=info&message=foo")
  430. assert_equal("INFO", handler.records[-1].levelname)
  431. assert_equal("Untrusted log event from user test: foo", handler.records[-1].message)
  432. assert_equal("desktop.views.log_frontend_event", handler.records[-1].name)
  433. c.get("/desktop/log_frontend_event?level=error&message=foo2")
  434. assert_equal("ERROR", handler.records[-1].levelname)
  435. assert_equal("Untrusted log event from user test: foo2", handler.records[-1].message)
  436. c.get("/desktop/log_frontend_event?message=foo3")
  437. assert_equal("INFO", handler.records[-1].levelname)
  438. assert_equal("Untrusted log event from user test: foo3", handler.records[-1].message)
  439. c.post("/desktop/log_frontend_event", {
  440. "message": "01234567" * 1024})
  441. assert_equal("INFO", handler.records[-1].levelname)
  442. assert_equal("Untrusted log event from user test: " + "01234567"*(1024/8),
  443. handler.records[-1].message)
  444. root.removeHandler(handler)
  445. def test_validate_path():
  446. with tempfile.NamedTemporaryFile() as local_file:
  447. reset = desktop.conf.SSL_PRIVATE_KEY.set_for_testing(local_file.name)
  448. assert_equal([], validate_path(desktop.conf.SSL_PRIVATE_KEY, is_dir=False))
  449. reset()
  450. try:
  451. reset = desktop.conf.SSL_PRIVATE_KEY.set_for_testing('/tmm/does_not_exist')
  452. assert_not_equal([], validate_path(desktop.conf.SSL_PRIVATE_KEY, is_dir=True))
  453. assert_true(False)
  454. except Exception, ex:
  455. assert_true('does not exist' in str(ex), ex)
  456. finally:
  457. reset()
  458. @attr('requires_hadoop')
  459. def test_config_check():
  460. with tempfile.NamedTemporaryFile() as cert_file:
  461. with tempfile.NamedTemporaryFile() as key_file:
  462. reset = (
  463. desktop.conf.SECRET_KEY.set_for_testing(''),
  464. desktop.conf.SECRET_KEY_SCRIPT.set_for_testing(present=False),
  465. desktop.conf.SSL_CERTIFICATE.set_for_testing(cert_file.name),
  466. desktop.conf.SSL_PRIVATE_KEY.set_for_testing(key_file.name),
  467. desktop.conf.DEFAULT_SITE_ENCODING.set_for_testing('klingon')
  468. )
  469. try:
  470. cli = make_logged_in_client()
  471. resp = cli.get('/desktop/debug/check_config')
  472. assert_true('Secret key should be configured' in resp.content, resp)
  473. assert_true('klingon' in resp.content, resp)
  474. assert_true('Encoding not supported' in resp.content, resp)
  475. # Set HUE_CONF_DIR and make sure check_config returns appropriate conf
  476. os.environ["HUE_CONF_DIR"] = "/tmp/test_hue_conf_dir"
  477. resp = cli.get('/desktop/debug/check_config')
  478. del os.environ["HUE_CONF_DIR"]
  479. assert_true('/tmp/test_hue_conf_dir' in resp.content, resp)
  480. finally:
  481. for old_conf in reset:
  482. old_conf()
  483. def test_last_access_time():
  484. c = make_logged_in_client(username="access_test")
  485. c.post('/accounts/login/')
  486. login = desktop.auth.views.get_current_users()
  487. before_access_time = time.time()
  488. response = c.get('/home')
  489. after_access_time = time.time()
  490. access = desktop.auth.views.get_current_users()
  491. user = response.context['user']
  492. login_time = login[user]['time']
  493. access_time = access[user]['time']
  494. # Check that 'last_access_time' is later than login time
  495. assert_true(login_time < access_time)
  496. # Check that 'last_access_time' is in between the timestamps before and after the last access path
  497. assert_true(before_access_time < access_time)
  498. assert_true(access_time < after_access_time)
  499. def test_ui_customizations():
  500. custom_message = 'test ui customization'
  501. reset = (
  502. desktop.conf.CUSTOM.BANNER_TOP_HTML.set_for_testing(custom_message),
  503. desktop.conf.CUSTOM.LOGIN_SPLASH_HTML.set_for_testing(custom_message),
  504. )
  505. try:
  506. c = make_logged_in_client()
  507. resp = c.get('/accounts/login/', follow=False)
  508. assert_true(custom_message in resp.content, resp)
  509. resp = c.get('/about', follow=True)
  510. assert_true(custom_message in resp.content, resp)
  511. finally:
  512. for old_conf in reset:
  513. old_conf()
  514. @attr('requires_hadoop')
  515. def test_check_config_ajax():
  516. c = make_logged_in_client()
  517. response = c.get(reverse(check_config))
  518. assert_true("misconfiguration" in response.content, response.content)
  519. def test_cx_Oracle():
  520. """
  521. Tests that cx_Oracle (external dependency) is built correctly.
  522. """
  523. if 'ORACLE_HOME' not in os.environ and 'ORACLE_INSTANTCLIENT_HOME' not in os.environ:
  524. raise SkipTest
  525. try:
  526. import cx_Oracle
  527. return
  528. except ImportError, ex:
  529. if "No module named" in ex.message:
  530. assert_true(False, "cx_Oracle skipped its build. This happens if "
  531. "env var ORACLE_HOME or ORACLE_INSTANTCLIENT_HOME is not defined. "
  532. "So ignore this test failure if your build does not need to work "
  533. "with an oracle backend.")
  534. class TestStrictRedirection():
  535. def setUp(self):
  536. self.client = make_logged_in_client()
  537. self.user = dict(username="test", password="test")
  538. desktop.conf.REDIRECT_WHITELIST.set_for_testing('^\/.*$,^http:\/\/example.com\/.*$')
  539. def test_redirection_blocked(self):
  540. # Redirection with code 301 should be handled properly
  541. # Redirection with Status code 301 example reference: http://www.somacon.com/p145.php
  542. self._test_redirection(redirection_url='http://www.somacon.com/color/html_css_table_border_styles.php',
  543. expected_status_code=403)
  544. # Redirection with code 302 should be handled properly
  545. self._test_redirection(redirection_url='http://www.google.com',
  546. expected_status_code=403)
  547. def test_redirection_allowed(self):
  548. # Redirection to the host where Hue is running should be OK.
  549. self._test_redirection(redirection_url='/', expected_status_code=302)
  550. self._test_redirection(redirection_url='/pig', expected_status_code=302)
  551. self._test_redirection(redirection_url='http://testserver/', expected_status_code=302)
  552. self._test_redirection(redirection_url='https://testserver/', expected_status_code=302, **{
  553. 'SERVER_PORT': '443',
  554. 'wsgi.url_scheme': 'https',
  555. })
  556. self._test_redirection(redirection_url='http://example.com/', expected_status_code=302)
  557. def _test_redirection(self, redirection_url, expected_status_code, **kwargs):
  558. self.client.get('/accounts/logout', **kwargs)
  559. response = self.client.post('/accounts/login/?next=' + redirection_url, self.user, **kwargs)
  560. assert_equal(expected_status_code, response.status_code)
  561. if expected_status_code == 403:
  562. error_msg = 'Redirect to ' + redirection_url + ' is not allowed.'
  563. assert_true(error_msg in response.content, response.content)
  564. class BaseTestPasswordConfig(object):
  565. SCRIPT = '%s -c "print \'\\n password from script \\n\'"' % sys.executable
  566. def get_config_password(self):
  567. raise NotImplementedError
  568. def get_config_password_script(self):
  569. raise NotImplementedError
  570. def get_password(self):
  571. raise NotImplementedError
  572. def test_read_password_from_script(self):
  573. self._run_test_read_password_from_script_with(present=False)
  574. self._run_test_read_password_from_script_with(data=None)
  575. self._run_test_read_password_from_script_with(data='')
  576. def _run_test_read_password_from_script_with(self, **kwargs):
  577. resets = [
  578. self.get_config_password().set_for_testing(**kwargs),
  579. self.get_config_password_script().set_for_testing(self.SCRIPT),
  580. ]
  581. try:
  582. assert_equal(self.get_password(), ' password from script ', 'pwd: %s, kwargs: %s' % (self.get_password(), kwargs))
  583. finally:
  584. for reset in resets:
  585. reset()
  586. def test_config_password_overrides_script_password(self):
  587. resets = [
  588. self.get_config_password().set_for_testing(' password from config '),
  589. self.get_config_password_script().set_for_testing(self.SCRIPT),
  590. ]
  591. try:
  592. assert_equal(self.get_password(), ' password from config ')
  593. finally:
  594. for reset in resets:
  595. reset()
  596. def test_password_script_raises_exception(self):
  597. resets = [
  598. self.get_config_password().set_for_testing(present=False),
  599. self.get_config_password_script().set_for_testing(
  600. '%s -c "import sys; sys.exit(1)"' % sys.executable
  601. ),
  602. ]
  603. try:
  604. assert_raises(subprocess.CalledProcessError, self.get_password)
  605. finally:
  606. for reset in resets:
  607. reset()
  608. resets = [
  609. self.get_config_password().set_for_testing(present=False),
  610. self.get_config_password_script().set_for_testing('/does-not-exist'),
  611. ]
  612. try:
  613. assert_raises(subprocess.CalledProcessError, self.get_password)
  614. finally:
  615. for reset in resets:
  616. reset()
  617. class TestSecretKeyConfig(BaseTestPasswordConfig):
  618. def get_config_password(self):
  619. return desktop.conf.SECRET_KEY
  620. def get_config_password_script(self):
  621. return desktop.conf.SECRET_KEY_SCRIPT
  622. def get_password(self):
  623. return desktop.conf.get_secret_key()
  624. class TestDatabasePasswordConfig(BaseTestPasswordConfig):
  625. def get_config_password(self):
  626. return desktop.conf.DATABASE.PASSWORD
  627. def get_config_password_script(self):
  628. return desktop.conf.DATABASE.PASSWORD_SCRIPT
  629. def get_password(self):
  630. return desktop.conf.get_database_password()
  631. class TestLDAPPasswordConfig(BaseTestPasswordConfig):
  632. def get_config_password(self):
  633. return desktop.conf.AUTH_PASSWORD
  634. def get_config_password_script(self):
  635. return desktop.conf.AUTH_PASSWORD_SCRIPT
  636. def get_password(self):
  637. # We are using dynamic_default now, so we need to cheat for the tests as only using set_for_testing(present=False) will trigger it.
  638. if desktop.conf.AUTH_PASSWORD.get():
  639. return desktop.conf.AUTH_PASSWORD.get()
  640. else:
  641. return self.get_config_password_script().get()
  642. class TestLDAPBindPasswordConfig(BaseTestPasswordConfig):
  643. def setup(self):
  644. self.finish = desktop.conf.LDAP.LDAP_SERVERS.set_for_testing({'test': {}})
  645. def teardown(self):
  646. self.finish()
  647. def get_config_password(self):
  648. return desktop.conf.LDAP.LDAP_SERVERS['test'].BIND_PASSWORD
  649. def get_config_password_script(self):
  650. return desktop.conf.LDAP.LDAP_SERVERS['test'].BIND_PASSWORD_SCRIPT
  651. def get_password(self):
  652. return desktop.conf.get_ldap_bind_password(desktop.conf.LDAP.LDAP_SERVERS['test'])
  653. class TestSMTPPasswordConfig(BaseTestPasswordConfig):
  654. def get_config_password(self):
  655. return desktop.conf.SMTP.PASSWORD
  656. def get_config_password_script(self):
  657. return desktop.conf.SMTP.PASSWORD_SCRIPT
  658. def get_password(self):
  659. return desktop.conf.get_smtp_password()
  660. class TestDocument(object):
  661. def setUp(self):
  662. make_logged_in_client(username="original_owner", groupname="test_doc", recreate=True, is_superuser=False)
  663. self.user = User.objects.get(username="original_owner")
  664. make_logged_in_client(username="copy_owner", groupname="test_doc", recreate=True, is_superuser=False)
  665. self.copy_user = User.objects.get(username="copy_owner")
  666. self.document2 = Document2.objects.create(name='Test Document2',
  667. type='search-dashboard',
  668. owner=self.user,
  669. description='Test Document2')
  670. self.document = Document.objects.link(content_object=self.document2,
  671. owner=self.user,
  672. name='Test Document',
  673. description='Test Document',
  674. extra='test')
  675. self.document.save()
  676. self.document2.doc.add(self.document)
  677. def tearDown(self):
  678. # Get any Doc2 objects that were created and delete them, Doc1 child objects will be deleted in turn
  679. test_docs = Document2.objects.filter(name__contains='Test Document2')
  680. test_docs.delete()
  681. def test_document_create(self):
  682. assert_true(Document2.objects.filter(name='Test Document2').exists())
  683. assert_true(Document.objects.filter(name='Test Document').exists())
  684. assert_equal(Document2.objects.get(name='Test Document2').id, self.document2.id)
  685. assert_equal(Document.objects.get(name='Test Document').id, self.document.id)
  686. def test_document_trashed(self):
  687. home_dir = Directory.objects.get_home_directory(self.user)
  688. test_dir, created = Directory.objects.get_or_create(
  689. parent_directory=home_dir,
  690. owner=self.user,
  691. name='test_dir'
  692. )
  693. test_doc = Document2.objects.create(name='Test Document2',
  694. type='search-dashboard',
  695. owner=self.user,
  696. description='Test Document2',
  697. parent_directory=test_dir)
  698. assert_false(test_dir.is_trashed)
  699. assert_false(test_doc.is_trashed)
  700. test_dir.trash()
  701. assert_true(test_doc.is_trashed)
  702. assert_true(test_dir.is_trashed)
  703. test_doc.delete()
  704. test_dir.delete()
  705. def test_document_copy(self):
  706. name = 'Test Document2 Copy'
  707. self.doc2_count = Document2.objects.count()
  708. self.doc1_count = Document.objects.count()
  709. doc2 = self.document2.copy(name=name, owner=self.copy_user, description=self.document2.description)
  710. doc = self.document.copy(doc2, name=name, owner=self.copy_user, description=self.document2.description)
  711. # Test that copying creates another object
  712. assert_equal(Document2.objects.count(), self.doc2_count + 1)
  713. assert_equal(Document.objects.count(), self.doc1_count + 1)
  714. # Test that the content object is not pointing to the same object
  715. assert_not_equal(self.document2.doc, doc2.doc)
  716. # Test that the owner is attributed to the new user
  717. assert_equal(doc2.owner, self.copy_user)
  718. # Test that copying enables attribute overrides
  719. assert_equal(Document2.objects.filter(name=name).count(), 1)
  720. assert_equal(doc2.description, self.document2.description)
  721. # Test that the content object is not pointing to the same object
  722. assert_not_equal(self.document.content_object, doc.content_object)
  723. # Test that the owner is attributed to the new user
  724. assert_equal(doc.owner, self.copy_user)
  725. # Test that copying enables attribute overrides
  726. assert_equal(Document.objects.filter(name=name).count(), 1)
  727. assert_equal(doc.description, self.document.description)
  728. def test_redact_statements(self):
  729. old_policies = redaction.global_redaction_engine.policies
  730. redaction.global_redaction_engine.policies = [
  731. RedactionPolicy([
  732. RedactionRule('', 'ssn=\d{3}-\d{2}-\d{4}', 'ssn=XXX-XX-XXXX'),
  733. ])
  734. ]
  735. logfilter.add_log_redaction_filter_to_logger(redaction.global_redaction_engine, logging.root)
  736. sensitive_query = 'SELECT "ssn=123-45-6789"'
  737. redacted_query = 'SELECT "ssn=XXX-XX-XXXX"'
  738. nonsensitive_query = 'SELECT "hello"'
  739. snippets = [
  740. {
  741. 'status': 'ready',
  742. 'viewSettings': {
  743. 'sqlDialect': True,
  744. 'snippetImage': '/static/beeswax/art/icon_beeswax_48.png',
  745. 'placeHolder': 'Example: SELECT * FROM tablename, or press CTRL + space',
  746. 'aceMode': 'ace/mode/hive'
  747. },
  748. 'id': '10a29cda-063f-1439-4836-d0c460154075',
  749. 'statement_raw': sensitive_query,
  750. 'statement': sensitive_query,
  751. 'type': 'hive'
  752. },
  753. {
  754. 'status': 'ready',
  755. 'viewSettings': {
  756. 'sqlDialect': True,
  757. 'snippetImage': '/static/impala/art/icon_impala_48.png',
  758. 'placeHolder': 'Example: SELECT * FROM tablename, or press CTRL + space',
  759. 'aceMode': 'ace/mode/impala'
  760. },
  761. 'id': 'e17d195a-beb5-76bf-7489-a9896eeda67a',
  762. 'statement_raw': sensitive_query,
  763. 'statement': sensitive_query,
  764. 'type': 'impala'
  765. },
  766. {
  767. 'status': 'ready',
  768. 'viewSettings': {
  769. 'sqlDialect': True,
  770. 'snippetImage': '/static/beeswax/art/icon_beeswax_48.png',
  771. 'placeHolder': 'Example: SELECT * FROM tablename, or press CTRL + space',
  772. 'aceMode': 'ace/mode/hive'
  773. },
  774. 'id': '10a29cda-063f-1439-4836-d0c460154075',
  775. 'statement_raw': nonsensitive_query,
  776. 'statement': nonsensitive_query,
  777. 'type': 'hive'
  778. },
  779. ]
  780. try:
  781. self.document2.type = 'notebook'
  782. self.document2.update_data({'snippets': snippets})
  783. self.document2.search = sensitive_query
  784. self.document2.save()
  785. saved_snippets = self.document2.data_dict['snippets']
  786. # Make sure redacted queries are redacted.
  787. assert_equal(redacted_query, saved_snippets[0]['statement'])
  788. assert_equal(redacted_query, saved_snippets[0]['statement_raw'])
  789. assert_equal(True, saved_snippets[0]['is_redacted'])
  790. assert_equal(redacted_query, saved_snippets[1]['statement'])
  791. assert_equal(redacted_query, saved_snippets[1]['statement_raw'])
  792. assert_equal(True, saved_snippets[1]['is_redacted'])
  793. document = Document2.objects.get(pk=self.document2.pk)
  794. assert_equal(redacted_query, document.search)
  795. # Make sure unredacted queries are not redacted.
  796. assert_equal(nonsensitive_query, saved_snippets[2]['statement'])
  797. assert_equal(nonsensitive_query, saved_snippets[2]['statement_raw'])
  798. assert_false('is_redacted' in saved_snippets[2])
  799. finally:
  800. redaction.global_redaction_engine.policies = old_policies
  801. def test_session_secure_cookie():
  802. with tempfile.NamedTemporaryFile() as cert_file:
  803. with tempfile.NamedTemporaryFile() as key_file:
  804. resets = [
  805. desktop.conf.SSL_CERTIFICATE.set_for_testing(cert_file.name),
  806. desktop.conf.SSL_PRIVATE_KEY.set_for_testing(key_file.name),
  807. desktop.conf.SESSION.SECURE.set_for_testing(False),
  808. ]
  809. try:
  810. assert_true(desktop.conf.is_https_enabled())
  811. assert_false(desktop.conf.SESSION.SECURE.get())
  812. finally:
  813. for reset in resets:
  814. reset()
  815. resets = [
  816. desktop.conf.SSL_CERTIFICATE.set_for_testing(cert_file.name),
  817. desktop.conf.SSL_PRIVATE_KEY.set_for_testing(key_file.name),
  818. desktop.conf.SESSION.SECURE.set_for_testing(True),
  819. ]
  820. try:
  821. assert_true(desktop.conf.is_https_enabled())
  822. assert_true(desktop.conf.SESSION.SECURE.get())
  823. finally:
  824. for reset in resets:
  825. reset()
  826. resets = [
  827. desktop.conf.SSL_CERTIFICATE.set_for_testing(cert_file.name),
  828. desktop.conf.SSL_PRIVATE_KEY.set_for_testing(key_file.name),
  829. desktop.conf.SESSION.SECURE.set_for_testing(present=False),
  830. ]
  831. try:
  832. assert_true(desktop.conf.is_https_enabled())
  833. assert_true(desktop.conf.SESSION.SECURE.get())
  834. finally:
  835. for reset in resets:
  836. reset()
  837. resets = [
  838. desktop.conf.SSL_CERTIFICATE.set_for_testing(present=None),
  839. desktop.conf.SSL_PRIVATE_KEY.set_for_testing(present=None),
  840. desktop.conf.SESSION.SECURE.set_for_testing(present=False),
  841. ]
  842. try:
  843. assert_false(desktop.conf.is_https_enabled())
  844. assert_false(desktop.conf.SESSION.SECURE.get())
  845. finally:
  846. for reset in resets:
  847. reset()
  848. def test_get_data_link():
  849. assert_equal(None, get_data_link({}))
  850. assert_equal('gethue.com', get_data_link({'type': 'link', 'link': 'gethue.com'}))
  851. assert_equal('/hbase/#Cluster/document_demo/query/20150527', get_data_link({'type': 'hbase', 'table': 'document_demo', 'row_key': '20150527'}))
  852. assert_equal('/hbase/#Cluster/document_demo/query/20150527[f1]', get_data_link({'type': 'hbase', 'table': 'document_demo', 'row_key': '20150527', 'fam': 'f1'}))
  853. assert_equal('/hbase/#Cluster/document_demo/query/20150527[f1:c1]', get_data_link({'type': 'hbase', 'table': 'document_demo', 'row_key': '20150527', 'fam': 'f1', 'col': 'c1'}))
  854. assert_equal('/filebrowser/view=/data/hue/1', get_data_link({'type': 'hdfs', 'path': '/data/hue/1'}))
  855. assert_equal('/metastore/table/default/sample_07', get_data_link({'type': 'hive', 'database': 'default', 'table': 'sample_07'}))