We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `redash/handlers/widgets.py`
Content:
```
1 import json
2 
3 from flask import request
4 
5 from redash import models
6 from redash.permissions import require_permission, require_admin_or_owner, require_access, view_only
7 from redash.handlers.base import BaseResource
8 
9 
10 class WidgetListResource(BaseResource):
11     @require_permission('edit_dashboard')
12     def post(self):
13         widget_properties = request.get_json(force=True)
14         dashboard = models.Dashboard.get_by_id_and_org(widget_properties.pop('dashboard_id'), self.current_org)
15         require_admin_or_owner(dashboard.user_id)
16 
17         widget_properties['options'] = json.dumps(widget_properties['options'])
18         widget_properties.pop('id', None)
19         widget_properties['dashboard'] = dashboard
20 
21         visualization_id = widget_properties.pop('visualization_id')
22         if visualization_id:
23             visualization = models.Visualization.get_by_id_and_org(visualization_id, self.current_org)
24             require_access(visualization.query.groups, self.current_user, view_only)
25         else:
26             visualization = None
27 
28         widget_properties['visualization'] = visualization
29 
30         widget = models.Widget.create(**widget_properties)
31 
32         layout = json.loads(widget.dashboard.layout)
33         new_row = True
34 
35         if len(layout) == 0 or widget.width == 2:
36             layout.append([widget.id])
37         elif len(layout[-1]) == 1:
38             neighbour_widget = models.Widget.get(models.Widget.id == layout[-1][0])
39             if neighbour_widget.width == 1:
40                 layout[-1].append(widget.id)
41                 new_row = False
42             else:
43                 layout.append([widget.id])
44         else:
45             layout.append([widget.id])
46 
47         widget.dashboard.layout = json.dumps(layout)
48         widget.dashboard.save()
49 
50         return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row}
51 
52 
53 class WidgetResource(BaseResource):
54     @require_permission('edit_dashboard')
55     def post(self, widget_id):
56         # This method currently handles Text Box widgets only.
57         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)
58         require_admin_or_owner(widget.dashboard.user_id)
59         widget_properties = request.get_json(force=True)
60         widget.text = widget_properties['text']
61         widget.save()
62 
63         return widget.to_dict()
64 
65     @require_permission('edit_dashboard')
66     def delete(self, widget_id):
67         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)
68         require_admin_or_owner(widget.dashboard.user_id)
69         widget.delete_instance()
70 
71         return {'layout': widget.dashboard.layout}
72
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
     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 @@
         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 @@
     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 @@
     @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}

{"golden_diff": "diff --git a/redash/handlers/widgets.py b/redash/handlers/widgets.py\n--- a/redash/handlers/widgets.py\n+++ b/redash/handlers/widgets.py\n@@ -1,10 +1,11 @@\n import json\n \n from flask import request\n-\n from redash import models\n-from redash.permissions import require_permission, require_admin_or_owner, require_access, view_only\n from redash.handlers.base import BaseResource\n+from redash.permissions import (require_access,\n+                                require_object_modify_permission,\n+                                require_permission, view_only)\n \n \n class WidgetListResource(BaseResource):\n@@ -12,7 +13,7 @@\n     def post(self):\n         widget_properties = request.get_json(force=True)\n         dashboard = models.Dashboard.get_by_id_and_org(widget_properties.pop('dashboard_id'), self.current_org)\n-        require_admin_or_owner(dashboard.user_id)\n+        require_object_modify_permission(dashboard, self.current_user)\n \n         widget_properties['options'] = json.dumps(widget_properties['options'])\n         widget_properties.pop('id', None)\n@@ -47,7 +48,7 @@\n         widget.dashboard.layout = json.dumps(layout)\n         widget.dashboard.save()\n \n-        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row}\n+        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row, 'version': dashboard.version}\n \n \n class WidgetResource(BaseResource):\n@@ -55,7 +56,7 @@\n     def post(self, widget_id):\n         # This method currently handles Text Box widgets only.\n         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n-        require_admin_or_owner(widget.dashboard.user_id)\n+        require_object_modify_permission(widget.dashboard, self.current_user)\n         widget_properties = request.get_json(force=True)\n         widget.text = widget_properties['text']\n         widget.save()\n@@ -65,7 +66,7 @@\n     @require_permission('edit_dashboard')\n     def delete(self, widget_id):\n         widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n-        require_admin_or_owner(widget.dashboard.user_id)\n+        require_object_modify_permission(widget.dashboard, self.current_user)\n         widget.delete_instance()\n \n-        return {'layout': widget.dashboard.layout}\n+        return {'layout': widget.dashboard.layout, 'version': widget.dashboard.version}\n", "issue": "Share access permissions for add/remove widgets\nHi @arikfr ,\r\n\r\nin 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?\r\n\r\nAnother 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?\r\n\r\n/cc @rohanpd\n", "before_files": [{"content": "import json\n\nfrom flask import request\n\nfrom redash import models\nfrom redash.permissions import require_permission, require_admin_or_owner, require_access, view_only\nfrom redash.handlers.base import BaseResource\n\n\nclass WidgetListResource(BaseResource):\n    @require_permission('edit_dashboard')\n    def post(self):\n        widget_properties = request.get_json(force=True)\n        dashboard = models.Dashboard.get_by_id_and_org(widget_properties.pop('dashboard_id'), self.current_org)\n        require_admin_or_owner(dashboard.user_id)\n\n        widget_properties['options'] = json.dumps(widget_properties['options'])\n        widget_properties.pop('id', None)\n        widget_properties['dashboard'] = dashboard\n\n        visualization_id = widget_properties.pop('visualization_id')\n        if visualization_id:\n            visualization = models.Visualization.get_by_id_and_org(visualization_id, self.current_org)\n            require_access(visualization.query.groups, self.current_user, view_only)\n        else:\n            visualization = None\n\n        widget_properties['visualization'] = visualization\n\n        widget = models.Widget.create(**widget_properties)\n\n        layout = json.loads(widget.dashboard.layout)\n        new_row = True\n\n        if len(layout) == 0 or widget.width == 2:\n            layout.append([widget.id])\n        elif len(layout[-1]) == 1:\n            neighbour_widget = models.Widget.get(models.Widget.id == layout[-1][0])\n            if neighbour_widget.width == 1:\n                layout[-1].append(widget.id)\n                new_row = False\n            else:\n                layout.append([widget.id])\n        else:\n            layout.append([widget.id])\n\n        widget.dashboard.layout = json.dumps(layout)\n        widget.dashboard.save()\n\n        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row}\n\n\nclass WidgetResource(BaseResource):\n    @require_permission('edit_dashboard')\n    def post(self, widget_id):\n        # This method currently handles Text Box widgets only.\n        widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n        require_admin_or_owner(widget.dashboard.user_id)\n        widget_properties = request.get_json(force=True)\n        widget.text = widget_properties['text']\n        widget.save()\n\n        return widget.to_dict()\n\n    @require_permission('edit_dashboard')\n    def delete(self, widget_id):\n        widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n        require_admin_or_owner(widget.dashboard.user_id)\n        widget.delete_instance()\n\n        return {'layout': widget.dashboard.layout}\n", "path": "redash/handlers/widgets.py"}], "after_files": [{"content": "import json\n\nfrom flask import request\nfrom redash import models\nfrom redash.handlers.base import BaseResource\nfrom redash.permissions import (require_access,\n                                require_object_modify_permission,\n                                require_permission, view_only)\n\n\nclass WidgetListResource(BaseResource):\n    @require_permission('edit_dashboard')\n    def post(self):\n        widget_properties = request.get_json(force=True)\n        dashboard = models.Dashboard.get_by_id_and_org(widget_properties.pop('dashboard_id'), self.current_org)\n        require_object_modify_permission(dashboard, self.current_user)\n\n        widget_properties['options'] = json.dumps(widget_properties['options'])\n        widget_properties.pop('id', None)\n        widget_properties['dashboard'] = dashboard\n\n        visualization_id = widget_properties.pop('visualization_id')\n        if visualization_id:\n            visualization = models.Visualization.get_by_id_and_org(visualization_id, self.current_org)\n            require_access(visualization.query.groups, self.current_user, view_only)\n        else:\n            visualization = None\n\n        widget_properties['visualization'] = visualization\n\n        widget = models.Widget.create(**widget_properties)\n\n        layout = json.loads(widget.dashboard.layout)\n        new_row = True\n\n        if len(layout) == 0 or widget.width == 2:\n            layout.append([widget.id])\n        elif len(layout[-1]) == 1:\n            neighbour_widget = models.Widget.get(models.Widget.id == layout[-1][0])\n            if neighbour_widget.width == 1:\n                layout[-1].append(widget.id)\n                new_row = False\n            else:\n                layout.append([widget.id])\n        else:\n            layout.append([widget.id])\n\n        widget.dashboard.layout = json.dumps(layout)\n        widget.dashboard.save()\n\n        return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row, 'version': dashboard.version}\n\n\nclass WidgetResource(BaseResource):\n    @require_permission('edit_dashboard')\n    def post(self, widget_id):\n        # This method currently handles Text Box widgets only.\n        widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n        require_object_modify_permission(widget.dashboard, self.current_user)\n        widget_properties = request.get_json(force=True)\n        widget.text = widget_properties['text']\n        widget.save()\n\n        return widget.to_dict()\n\n    @require_permission('edit_dashboard')\n    def delete(self, widget_id):\n        widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n        require_object_modify_permission(widget.dashboard, self.current_user)\n        widget.delete_instance()\n\n        return {'layout': widget.dashboard.layout, 'version': widget.dashboard.version}\n", "path": "redash/handlers/widgets.py"}]}