Repository: getredash/redash

Issue:
Share access permissions for add/remove widgets
Hi @arikfr ,

in our PR #1113 we focused primarily on sharing access permissions for queries, and for dashboards we currently only allow to modify basic info of the dashboard. What is still missing is ability to allow other users to add and remove widgets. Is that something you are planning to add, or should we give it a shot?

Another thing that came up - we're currently enforcing `require_admin_or_owner(...)` for managing access permissions (e.g., https://github.com/getredash/redash/blob/master/redash/handlers/permissions.py#L42). This is actually a very restrictive limitation, and we believe that anybody with permissions (owner, admin, other permitted users) should be able to add/remove users. For instance, if you consider person A creating a dashboard, then giving access to persons B and C who are actively maintaining that dashboard. Then, if person A leaves the company, B and C would not be able to make the required changes to add another person D. What do you think?

/cc @rohanpd


Discussion:
This is what the role of an admin is. The ability to administrate an account.
As part of someone leaving a company, they would delegate their admin access to someone else / hand over their account.

I think perhaps what you're asking for instead, is a more 'per-action' style permission system.
Ie, create / add user, create / edit widget, create / edit query, create / edit dashboard, etc.

This would be a better way to do what you want, rather than over-expanding non-admin permissions, which is a security issue.

For the first thing: I think that we should allow managing widgets. At least on my part, it wasn't intentional and just an overlook.

For the second issue: in general I agree with @adamlwgriffiths, that this is what the other admins are for. But I do think we should have the ability to handover ownership of objects to someone else. Opened #1387 to track this.


Resolving patch:
diff --git a/redash/handlers/widgets.py b/redash/handlers/widgets.py
--- a/redash/handlers/widgets.py
+++ b/redash/handlers/widgets.py
@@ -1,10 +1,11 @@
 import json
 
 from flask import request
-
 from redash import models
-from redash.permissions import require_permission, require_admin_or_owner, require_access, view_only
 from redash.handlers.base import BaseResource
+from redash.permissions import (require_access,
+                                require_object_modify_permission,
+                                require_permission, view_only)
 
 
 class WidgetListResource(BaseResource):
@@ -12,7 +13,7 @@ class WidgetListResource(BaseResource):
     def post(self):
         widget_properties = request.get_json(force=True)
         dashboard = models.Dashboard.get_by_id_and_org(widget_properties.pop('dashboard_id'), self.current_org)
-        require_admin_or_owner(dashboard.user_id)
+        require_object_modify_permission(dashboard, self.current_user)
 
         widget_properties['options'] = json.dumps(widget_properties['options'])
         widget_properties.pop('id', None)
@@ -47,7 +48,7 @@ def post(self):
         widget.dashboard.layout = json.dumps(layout)
         widget.dashboard.save()
 
-        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row}
+        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row, 'version': dashboard.version}
 
 
 class WidgetResource(BaseResource):
@@ -55,7 +56,7 @@ class WidgetResource(BaseResource):
     def post(self, widget_id):
         # This method currently handles Text Box widgets only.
         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)
-        require_admin_or_owner(widget.dashboard.user_id)
+        require_object_modify_permission(widget.dashboard, self.current_user)
         widget_properties = request.get_json(force=True)
         widget.text = widget_properties['text']
         widget.save()
@@ -65,7 +66,7 @@ def post(self, widget_id):
     @require_permission('edit_dashboard')
     def delete(self, widget_id):
         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)
-        require_admin_or_owner(widget.dashboard.user_id)
+        require_object_modify_permission(widget.dashboard, self.current_user)
         widget.delete_instance()
 
-        return {'layout': widget.dashboard.layout}
+        return {'layout': widget.dashboard.layout, 'version': widget.dashboard.version}


Test patch: