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">
 				<ul class="nav nav-list">
 					<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):
 				      <%

+ 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 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
     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
     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
     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
 
   do_view('user=test')
-  do_view('user=_all')
+  do_view('user=:all')
   do_view('design_id=1')
   do_view('auto_query=0')
   do_view('auto_query=1')
@@ -980,7 +998,7 @@ def test_history_page():
   do_view('sort=type')
   do_view('sort=-type')
   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():

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

@@ -53,6 +53,42 @@ from filebrowser.views import location_to_url
 
 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):
   tables = db_utils.meta_client().get_tables("default", ".*")
   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.
   """
+  if design is not None:
+    authorized_get_design(request, design.id)
   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))
 
@@ -283,6 +321,8 @@ def execute_query(request, design_id=None):
       If given, it will be displayed when the query is successfully finished.
       Otherwise, it will display the view query results page by default.
   """
+  authorized_get_design(request, design_id)
+
   error_message = None
   form = beeswax.forms.query_form()
   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.
   """
-  design = models.SavedQuery.get(design_id, request.user, models.SavedQuery.HQL)
+  design = authorized_get_design(request, design_id, must_exist=True)
+
   # Reconstitute the form
   design_obj = beeswax.design.HQLdesign.loads(design.data)
   query_form = beeswax.forms.query_form()
@@ -442,6 +483,8 @@ def expand_exception(exc):
 
 
 def edit_report(request, design_id=None):
+  authorized_get_design(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):
   """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,))
     return list_designs(request)
 
@@ -618,9 +661,9 @@ def delete_design(request, design_id):
 
 def clone_design(request, design_id):
   """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,))
     return list_designs(request)
 
@@ -648,7 +691,8 @@ def download(request, id, format):
   id = int(id)
   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 ]' %
       (query_history.server_id, query_history.query))
   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>')
 
-  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,))
     return res
 
@@ -714,6 +759,8 @@ def watch_query(request, id):
   # Coerce types; manage arguments
   id = int(id)
 
+  query_history = authorized_get_history(request, id, must_exist=True)
+
   # GET param: context.
   context_param = request.GET.get('context', '')
 
@@ -723,8 +770,7 @@ def watch_query(request, id):
   if not on_success_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)
   query_history.save_state(state)
 
@@ -843,8 +889,8 @@ def view_results(request, id, first_row=0):
   first_row = long(first_row)
   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)
   context = _parse_query_context(request.GET.get('context'))
 
@@ -1118,6 +1164,13 @@ def configuration(request):
                       bool(request.REQUEST.get("include_hadoop", False)))
   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):
   """
@@ -1128,17 +1181,9 @@ def my_queries(request):
   """
   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.
   prefix = 'h-'
-  querydict_history = copy_prefix(prefix)
+  querydict_history = _copy_prefix(prefix, request.GET)
   # Manually limit up the user filter.
   querydict_history[ prefix + 'user' ] = request.user.username
   hist_page, hist_filter = _list_query_history(request.user,
@@ -1147,7 +1192,7 @@ def my_queries(request):
                                                prefix)
   # Extract the saved query list.
   prefix = 'q-'
-  querydict_query = copy_prefix(prefix)
+  querydict_query = _copy_prefix(prefix, request.GET)
   # Manually limit up the user filter.
   querydict_query[ prefix + 'user' ] = request.user.username
   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:
     page=<n>            - Controls pagination. Defaults to 1.
     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.
     design_id=<id>      - Show history for this particular design id.
     sort=<key>          - Sort by the attribute <key>, which is one of:
@@ -1179,11 +1224,18 @@ def list_query_history(request):
   """
   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, {
     'request': request,
     'page': page,
     '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):
     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 = querydict.get(prefix + 'design_id')