Browse Source

HUE-688 [beeswax] Beeswax shouldn't show saved queries from other users

Also protect the History of the query.
Romain Rigaux 13 years ago
parent
commit
1aef4c2c8e

+ 15 - 13
apps/beeswax/src/beeswax/templates/list_history.mako

@@ -53,19 +53,21 @@ ${layout.menubar(section='history')}
 			<div class="well sidebar-nav">
 			<div class="well sidebar-nav">
 				<ul class="nav nav-list">
 				<ul class="nav nav-list">
 					<li class="nav-header">Actions</li>
 					<li class="nav-header">Actions</li>
-					% if filter_params.get('user') == '_all':
-				      <%
-				        my_querydict = filter_params.copy()
-				        my_querydict['user'] = request.user.username
-				      %>
-					<li><a href="?${my_querydict.urlencode()}">Show my queries</a></li>
-				 	% else:
-				      <%
-				        my_querydict = filter_params.copy()
-				        my_querydict['user'] = '_all'
-				      %>
-				      <li><a href="?${my_querydict.urlencode()}">Show everyone's queries</a></li>
-				    % endif
+                    % if share_queries:
+                        % if filter_params.get('user') == ':all':
+                          <%
+                            my_querydict = filter_params.copy()
+                            my_querydict['user'] = request.user.username
+                          %>
+                        <li><a href="?${my_querydict.urlencode()}">Show my queries</a></li>
+                        % else:
+                          <%
+                            my_querydict = filter_params.copy()
+                            my_querydict['user'] = ':all'
+                          %>
+                          <li><a href="?${my_querydict.urlencode()}">Show everyone's queries</a></li>
+                        % endif
+                    % endif
 
 
 				 	% if filter_params.get('auto_query', None):
 				 	% if filter_params.get('auto_query', None):
 				      <%
 				      <%

+ 47 - 29
apps/beeswax/src/beeswax/tests.py

@@ -497,41 +497,59 @@ for x in sys.stdin:
     _make_query(self.client, "select one", name='client 1 query', submission_type='Save')
     _make_query(self.client, "select one", name='client 1 query', submission_type='Save')
     _make_query(self.client, "select two", name='client 2 query', submission_type='Save')
     _make_query(self.client, "select two", name='client 2 query', submission_type='Save')
 
 
-    conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(True)
-    resp = self.client.get('/beeswax/list_designs')
-    assert_true('select one' in resp.content)
-    assert_true('select two' in resp.content)
+    finish = conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(True)
+    try:
+      resp = self.client.get('/beeswax/list_designs')
+      assert_true('client 1 query' in resp.content)
+      assert_true('client 2 query' in resp.content)
+    finally:
+      finish()
 
 
-    conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(False)
-    resp = self.client.get('/beeswax/list_designs')
-    assert_true('select one' in resp.content)
-    assert_true('select two' in resp.content)
+    finish = conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(False)
+    try:
+      resp = self.client.get('/beeswax/list_designs')
+      assert_true('client 1 query' in resp.content)
+      assert_true('client 2 query' in resp.content)
+    finally:
+      finish()
 
 
     # Login as someone else
     # Login as someone else
     client2 = make_logged_in_client('not_me')
     client2 = make_logged_in_client('not_me')
-    conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(True)
-    resp = client2.get('/beeswax/list_designs')
-    assert_true('select one' not in resp.content)
-    assert_true('select two' not in resp.content)
-
-    conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(False)
-    resp = client2.get('/beeswax/list_designs')
-    assert_true('select one' in resp.content)
-    assert_true('select two' in resp.content)
-    client2.logout()
+    finish = conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(True)
+    try:
+      resp = client2.get('/beeswax/list_designs')
+      assert_true('client 1 query' not in resp.content)
+      assert_true('client 2 query' not in resp.content)
+    finally:
+      finish()
+
+    finish = conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(False)
+    try:
+      resp = client2.get('/beeswax/list_designs')
+      assert_true('client 1 query' in resp.content)
+      assert_true('client 2 query' in resp.content)
+    finally:
+      finish()
+      client2.logout()
 
 
     # Login as super user
     # Login as super user
     client3 = make_logged_in_client('admin', is_superuser=True)
     client3 = make_logged_in_client('admin', is_superuser=True)
-    conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(True)
-    resp = client3.get('/beeswax/list_designs')
-    assert_true('select one' in resp.content)
-    assert_true('select two' in resp.content)
+    finish = conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(True)
+    try:
+      resp = client3.get('/beeswax/list_designs')
+      assert_true('client 1 query' in resp.content)
+      assert_true('client 2 query' in resp.content)
+    finally:
+      finish()
 
 
-    conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(False)
-    resp = client3.get('/beeswax/list_designs')
-    assert_true('select one' in resp.content)
-    assert_true('select two' in resp.content)
-    client3.logout()
+    finish = conf.SHOW_ONLY_PERSONAL_SAVED_QUERIES.set_for_testing(False)
+    try:
+      resp = client3.get('/beeswax/list_designs')
+      assert_true('client 1 query' in resp.content)
+      assert_true('client 2 query' in resp.content)
+    finally:
+      finish()
+      client3.logout()
 
 
     # Cleaning
     # Cleaning
     beeswax.models.SavedQuery.objects.all()[:2].delete()
     beeswax.models.SavedQuery.objects.all()[:2].delete()
@@ -965,7 +983,7 @@ def test_history_page():
     assert_true(len(resp.context['page'].object_list) >= 0)     # Make the query run
     assert_true(len(resp.context['page'].object_list) >= 0)     # Make the query run
 
 
   do_view('user=test')
   do_view('user=test')
-  do_view('user=_all')
+  do_view('user=:all')
   do_view('design_id=1')
   do_view('design_id=1')
   do_view('auto_query=0')
   do_view('auto_query=0')
   do_view('auto_query=1')
   do_view('auto_query=1')
@@ -980,7 +998,7 @@ def test_history_page():
   do_view('sort=type')
   do_view('sort=type')
   do_view('sort=-type')
   do_view('sort=-type')
   do_view('sort=name&user=bogus&design_id=1&auto_query=1')
   do_view('sort=name&user=bogus&design_id=1&auto_query=1')
-  do_view('sort=-type&user=_all&type=hql&auto_query=0')
+  do_view('sort=-type&user=:all&type=hql&auto_query=0')
 
 
 
 
 def test_strip_trailing_semicolon():
 def test_strip_trailing_semicolon():

+ 82 - 29
apps/beeswax/src/beeswax/views.py

@@ -53,6 +53,42 @@ from filebrowser.views import location_to_url
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
+
+def authorized_get_design(request, design_id, owner_only=False, must_exist=False):
+  if design_id is None and not must_exist:
+    return None
+  try:
+    design = models.SavedQuery.objects.get(id=design_id)
+  except models.SavedQuery.DoesNotExist:
+    if must_exist:
+      raise PopupException('Design %s does not exist.' % (design_id,))
+    else:
+      return None
+
+  if not conf.SHARE_SAVED_QUERIES.get() and (not request.user.is_superuser or owner_only) \
+      and design.owner != request.user:
+    raise PopupException('Cannot access design %s' % (design_id,))
+  else:
+    return design
+
+def authorized_get_history(request, query_history_id, owner_only=False, must_exist=False):
+  if query_history_id is None and not must_exist:
+    return None
+  try:
+    query_history = models.QueryHistory.objects.get(id=query_history_id)
+  except models.QueryHistory.DoesNotExist:
+    if must_exist:
+      raise PopupException('QueryHistory %s does not exist.' % (query_history_id,))
+    else:
+      return None
+
+  if not conf.SHARE_SAVED_QUERIES.get() and (not request.user.is_superuser or owner_only) \
+      and query_history.owner != request.user:
+    raise PopupException('Cannot access QueryHistory %s' % (query_history_id,))
+  else:
+    return query_history
+
+
 def index(request):
 def index(request):
   tables = db_utils.meta_client().get_tables("default", ".*")
   tables = db_utils.meta_client().get_tables("default", ".*")
   if not tables:
   if not tables:
@@ -250,6 +286,8 @@ def execute_directly(request, query_msg, design=None, tablename=None,
 
 
   Note that this may throw a Beeswax exception.
   Note that this may throw a Beeswax exception.
   """
   """
+  if design is not None:
+    authorized_get_design(request, design.id)
   history_obj = db_utils.execute_directly(request.user, query_msg, design, **kwargs)
   history_obj = db_utils.execute_directly(request.user, query_msg, design, **kwargs)
   watch_url = urlresolvers.reverse("beeswax.views.watch_query", kwargs=dict(id=history_obj.id))
   watch_url = urlresolvers.reverse("beeswax.views.watch_query", kwargs=dict(id=history_obj.id))
 
 
@@ -283,6 +321,8 @@ def execute_query(request, design_id=None):
       If given, it will be displayed when the query is successfully finished.
       If given, it will be displayed when the query is successfully finished.
       Otherwise, it will display the view query results page by default.
       Otherwise, it will display the view query results page by default.
   """
   """
+  authorized_get_design(request, design_id)
+
   error_message = None
   error_message = None
   form = beeswax.forms.query_form()
   form = beeswax.forms.query_form()
   action = request.path
   action = request.path
@@ -396,7 +436,8 @@ def _run_parameterized_query(request, design_id, explain):
 
 
   This is an extra "step" in the flow from execute_query.
   This is an extra "step" in the flow from execute_query.
   """
   """
-  design = models.SavedQuery.get(design_id, request.user, models.SavedQuery.HQL)
+  design = authorized_get_design(request, design_id, must_exist=True)
+
   # Reconstitute the form
   # Reconstitute the form
   design_obj = beeswax.design.HQLdesign.loads(design.data)
   design_obj = beeswax.design.HQLdesign.loads(design.data)
   query_form = beeswax.forms.query_form()
   query_form = beeswax.forms.query_form()
@@ -442,6 +483,8 @@ def expand_exception(exc):
 
 
 
 
 def edit_report(request, design_id=None):
 def edit_report(request, design_id=None):
+  authorized_get_design(request, design_id)
+
   return beeswax.report.edit_report(request, design_id)
   return beeswax.report.edit_report(request, design_id)
 
 
 
 
@@ -603,9 +646,9 @@ def _list_designs(querydict, page_size, prefix="", user=None):
 
 
 def delete_design(request, design_id):
 def delete_design(request, design_id):
   """Delete a saved design"""
   """Delete a saved design"""
-  try:
-    design = models.SavedQuery.get(design_id, request.user)
-  except models.SavedQuery.DoesNotExist:
+  design = authorized_get_design(request, design_id)
+
+  if design is None:
     LOG.error('Cannot delete non-existent design %s' % (design_id,))
     LOG.error('Cannot delete non-existent design %s' % (design_id,))
     return list_designs(request)
     return list_designs(request)
 
 
@@ -618,9 +661,9 @@ def delete_design(request, design_id):
 
 
 def clone_design(request, design_id):
 def clone_design(request, design_id):
   """Clone a design belonging to any user"""
   """Clone a design belonging to any user"""
-  try:
-    design = models.SavedQuery.get(design_id)
-  except models.SavedQuery.DoesNotExist:
+  design = authorized_get_design(request, design_id)
+
+  if design is None:
     LOG.error('Cannot clone non-existent design %s' % (design_id,))
     LOG.error('Cannot clone non-existent design %s' % (design_id,))
     return list_designs(request)
     return list_designs(request)
 
 
@@ -648,7 +691,8 @@ def download(request, id, format):
   id = int(id)
   id = int(id)
   assert format in common.DL_FORMATS
   assert format in common.DL_FORMATS
 
 
-  query_history = models.QueryHistory.objects.get(id=id)
+  query_history = authorized_get_history(request, id, must_exist=True)
+
   LOG.debug('Download results for query %s: [ %s ]' %
   LOG.debug('Download results for query %s: [ %s ]' %
       (query_history.server_id, query_history.query))
       (query_history.server_id, query_history.query))
   return data_export.download(query_history, format)
   return data_export.download(query_history, format)
@@ -665,8 +709,9 @@ def query_done_cb(request, server_id):
   """
   """
   res = HttpResponse('<html><head></head><body></body></html>')
   res = HttpResponse('<html><head></head><body></body></html>')
 
 
-  history = models.QueryHistory.objects.get(server_id=server_id)
-  if not history:
+  history = authorized_get_history(request, server_id)
+
+  if history is None:
     LOG.error('Processing query completion email: Cannot find query matching id %s' % (server_id,))
     LOG.error('Processing query completion email: Cannot find query matching id %s' % (server_id,))
     return res
     return res
 
 
@@ -714,6 +759,8 @@ def watch_query(request, id):
   # Coerce types; manage arguments
   # Coerce types; manage arguments
   id = int(id)
   id = int(id)
 
 
+  query_history = authorized_get_history(request, id, must_exist=True)
+
   # GET param: context.
   # GET param: context.
   context_param = request.GET.get('context', '')
   context_param = request.GET.get('context', '')
 
 
@@ -723,8 +770,7 @@ def watch_query(request, id):
   if not on_success_url:
   if not on_success_url:
     on_success_url = results_url
     on_success_url = results_url
 
 
-  # Retrieve models from database to get the server_id
-  query_history = models.QueryHistory.objects.get(id=id)
+  # Get the server_id
   server_id, state = _get_server_id_and_state(query_history)
   server_id, state = _get_server_id_and_state(query_history)
   query_history.save_state(state)
   query_history.save_state(state)
 
 
@@ -843,8 +889,8 @@ def view_results(request, id, first_row=0):
   first_row = long(first_row)
   first_row = long(first_row)
   start_over = (first_row == 0)
   start_over = (first_row == 0)
 
 
-  # Retrieve models from database
-  query_history = models.QueryHistory.objects.get(id=id)
+  query_history = authorized_get_history(request, id, must_exist=True)
+
   handle = QueryHandle(id=query_history.server_id, log_context=query_history.log_context)
   handle = QueryHandle(id=query_history.server_id, log_context=query_history.log_context)
   context = _parse_query_context(request.GET.get('context'))
   context = _parse_query_context(request.GET.get('context'))
 
 
@@ -1118,6 +1164,13 @@ def configuration(request):
                       bool(request.REQUEST.get("include_hadoop", False)))
                       bool(request.REQUEST.get("include_hadoop", False)))
   return render("configuration.mako", request, dict(config_values=config_values))
   return render("configuration.mako", request, dict(config_values=config_values))
 
 
+def _copy_prefix(prefix, base_dict):
+  """Copy keys starting with ``prefix``"""
+  querydict = QueryDict(None, mutable=True)
+  for key, val in base_dict.iteritems():
+    if key.startswith(prefix):
+      querydict[key] = val
+  return querydict
 
 
 def my_queries(request):
 def my_queries(request):
   """
   """
@@ -1128,17 +1181,9 @@ def my_queries(request):
   """
   """
   DEFAULT_PAGE_SIZE = 40
   DEFAULT_PAGE_SIZE = 40
 
 
-  def copy_prefix(prefix):
-    """Copy keys starting with ``prefix``"""
-    querydict = QueryDict(None, mutable=True)
-    for key, val in request.GET.iteritems():
-      if key.startswith(prefix):
-        querydict[key] = val
-    return querydict
-
   # Extract the history list.
   # Extract the history list.
   prefix = 'h-'
   prefix = 'h-'
-  querydict_history = copy_prefix(prefix)
+  querydict_history = _copy_prefix(prefix, request.GET)
   # Manually limit up the user filter.
   # Manually limit up the user filter.
   querydict_history[ prefix + 'user' ] = request.user.username
   querydict_history[ prefix + 'user' ] = request.user.username
   hist_page, hist_filter = _list_query_history(request.user,
   hist_page, hist_filter = _list_query_history(request.user,
@@ -1147,7 +1192,7 @@ def my_queries(request):
                                                prefix)
                                                prefix)
   # Extract the saved query list.
   # Extract the saved query list.
   prefix = 'q-'
   prefix = 'q-'
-  querydict_query = copy_prefix(prefix)
+  querydict_query = _copy_prefix(prefix, request.GET)
   # Manually limit up the user filter.
   # Manually limit up the user filter.
   querydict_query[ prefix + 'user' ] = request.user.username
   querydict_query[ prefix + 'user' ] = request.user.username
   query_page, query_filter = _list_designs(querydict_query, DEFAULT_PAGE_SIZE, prefix)
   query_page, query_filter = _list_designs(querydict_query, DEFAULT_PAGE_SIZE, prefix)
@@ -1168,7 +1213,7 @@ def list_query_history(request):
   We get here from /beeswax/query_history?filterargs, with the options being:
   We get here from /beeswax/query_history?filterargs, with the options being:
     page=<n>            - Controls pagination. Defaults to 1.
     page=<n>            - Controls pagination. Defaults to 1.
     user=<name>         - Show history items from a user. Default to current user only.
     user=<name>         - Show history items from a user. Default to current user only.
-                          Also accepts '_all' to show all history items.
+                          Also accepts ':all' to show all history items.
     type=<type>         - <type> is "report|hql", for design type. Default to show all.
     type=<type>         - <type> is "report|hql", for design type. Default to show all.
     design_id=<id>      - Show history for this particular design id.
     design_id=<id>      - Show history for this particular design id.
     sort=<key>          - Sort by the attribute <key>, which is one of:
     sort=<key>          - Sort by the attribute <key>, which is one of:
@@ -1179,11 +1224,18 @@ def list_query_history(request):
   """
   """
   DEFAULT_PAGE_SIZE = 10
   DEFAULT_PAGE_SIZE = 10
 
 
-  page, filter_params = _list_query_history(request.user, request.GET, DEFAULT_PAGE_SIZE)
+  share_queries = conf.SHARE_SAVED_QUERIES.get() or request.user.is_superuser
+
+  querydict_query = request.GET.copy()
+  if not share_queries:
+    querydict_query['user'] = request.user.username
+
+  page, filter_params = _list_query_history(request.user, querydict_query, DEFAULT_PAGE_SIZE)
   return render('list_history.mako', request, {
   return render('list_history.mako', request, {
     'request': request,
     'request': request,
     'page': page,
     'page': page,
     'filter_params': filter_params,
     'filter_params': filter_params,
+    'share_queries': share_queries,
   })
   })
 
 
 
 
@@ -1214,9 +1266,10 @@ def _list_query_history(user, querydict, page_size, prefix=""):
   if not querydict.get(prefix + 'auto_query', False):
   if not querydict.get(prefix + 'auto_query', False):
     db_queryset = db_queryset.filter(design__isnull=False)
     db_queryset = db_queryset.filter(design__isnull=False)
 
 
-  user = querydict.get(prefix + 'user', user.username)
-  if user != '_all':
-    db_queryset = db_queryset.filter(owner__username=user)
+  username = user.username
+  user_filter = querydict.get(prefix + 'user', user.username)
+  if user_filter != ':all':
+    db_queryset = db_queryset.filter(owner__username=user_filter)
 
 
   # Design id
   # Design id
   design_id = querydict.get(prefix + 'design_id')
   design_id = querydict.get(prefix + 'design_id')