We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Redash Permissions not working for some use cases
### Issue Summary

Currently, when query owner grants permission to another user for a query, the user is still unable to perform the following tasks:

* change data source
* schedule the query
* add and save new visualisation

I believe the user should have the ability to do all the things that the owner could do once permission has been granted.

### Technical details:

* Redash Version: 1.0.3
* Browser/OS: Chrome
* How did you install Redash: AWS using the AMI


--- 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/visualizations.py`
Content:
```
1 import json
2 from flask import request
3 
4 from redash import models
5 from redash.permissions import require_permission, require_admin_or_owner
6 from redash.handlers.base import BaseResource, get_object_or_404
7 
8 
9 class VisualizationListResource(BaseResource):
10     @require_permission('edit_query')
11     def post(self):
12         kwargs = request.get_json(force=True)
13 
14         query = get_object_or_404(models.Query.get_by_id_and_org, kwargs.pop('query_id'), self.current_org)
15         require_admin_or_owner(query.user_id)
16 
17         kwargs['options'] = json.dumps(kwargs['options'])
18         kwargs['query_rel'] = query
19 
20         vis = models.Visualization(**kwargs)
21         models.db.session.add(vis)
22         models.db.session.commit()
23         d = vis.to_dict(with_query=False)
24         return d
25 
26 
27 class VisualizationResource(BaseResource):
28     @require_permission('edit_query')
29     def post(self, visualization_id):
30         vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)
31         require_admin_or_owner(vis.query_rel.user_id)
32 
33         kwargs = request.get_json(force=True)
34         if 'options' in kwargs:
35             kwargs['options'] = json.dumps(kwargs['options'])
36 
37         kwargs.pop('id', None)
38         kwargs.pop('query_id', None)
39 
40         self.update_model(vis, kwargs)
41         d = vis.to_dict(with_query=False)
42         models.db.session.commit()
43         return d
44 
45     @require_permission('edit_query')
46     def delete(self, visualization_id):
47         vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)
48         require_admin_or_owner(vis.query_rel.user_id)
49         models.db.session.delete(vis)
50         models.db.session.commit()
51
```

Path: `redash/permissions.py`
Content:
```
1 from flask_login import current_user
2 from flask_restful import abort
3 import functools
4 from funcy import flatten
5 
6 view_only = True
7 not_view_only = False
8 
9 ACCESS_TYPE_VIEW = 'view'
10 ACCESS_TYPE_MODIFY = 'modify'
11 ACCESS_TYPE_DELETE = 'delete'
12 
13 ACCESS_TYPES = (ACCESS_TYPE_VIEW, ACCESS_TYPE_MODIFY, ACCESS_TYPE_DELETE)
14 
15 
16 def has_access(object_groups, user, need_view_only):
17     if 'admin' in user.permissions:
18         return True
19 
20     matching_groups = set(object_groups.keys()).intersection(user.group_ids)
21 
22     if not matching_groups:
23         return False
24 
25     required_level = 1 if need_view_only else 2
26 
27     group_level = 1 if all(flatten([object_groups[group] for group in matching_groups])) else 2
28 
29     return required_level <= group_level
30 
31 
32 def require_access(object_groups, user, need_view_only):
33     if not has_access(object_groups, user, need_view_only):
34         abort(403)
35 
36 
37 class require_permissions(object):
38     def __init__(self, permissions):
39         self.permissions = permissions
40 
41     def __call__(self, fn):
42         @functools.wraps(fn)
43         def decorated(*args, **kwargs):
44             has_permissions = current_user.has_permissions(self.permissions)
45 
46             if has_permissions:
47                 return fn(*args, **kwargs)
48             else:
49                 abort(403)
50 
51         return decorated
52 
53 
54 def require_permission(permission):
55     return require_permissions((permission,))
56 
57 
58 def require_admin(fn):
59     return require_permission('admin')(fn)
60 
61 
62 def require_super_admin(fn):
63     return require_permission('super_admin')(fn)
64 
65 
66 def has_permission_or_owner(permission, object_owner_id):
67     return int(object_owner_id) == current_user.id or current_user.has_permission(permission)
68 
69 
70 def is_admin_or_owner(object_owner_id):
71     return has_permission_or_owner('admin', object_owner_id)
72 
73 
74 def require_permission_or_owner(permission, object_owner_id):
75     if not has_permission_or_owner(permission, object_owner_id):
76         abort(403)
77 
78 
79 def require_admin_or_owner(object_owner_id):
80     if not is_admin_or_owner(object_owner_id):
81         abort(403, message="You don't have permission to edit this resource.")
82 
83 
84 def can_modify(obj, user):
85     return is_admin_or_owner(obj.user_id) or user.has_access(obj, ACCESS_TYPE_MODIFY)
86 
87 
88 def require_object_modify_permission(obj, user):
89     if not can_modify(obj, user):
90         abort(403)
91
```
--- 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/visualizations.py b/redash/handlers/visualizations.py
--- a/redash/handlers/visualizations.py
+++ b/redash/handlers/visualizations.py
@@ -1,9 +1,12 @@
 import json
+
 from flask import request
 
 from redash import models
-from redash.permissions import require_permission, require_admin_or_owner
 from redash.handlers.base import BaseResource, get_object_or_404
+from redash.permissions import (require_admin_or_owner,
+                                require_object_modify_permission,
+                                require_permission)
 
 
 class VisualizationListResource(BaseResource):
@@ -12,7 +15,7 @@
         kwargs = request.get_json(force=True)
 
         query = get_object_or_404(models.Query.get_by_id_and_org, kwargs.pop('query_id'), self.current_org)
-        require_admin_or_owner(query.user_id)
+        require_object_modify_permission(query, self.current_user)
 
         kwargs['options'] = json.dumps(kwargs['options'])
         kwargs['query_rel'] = query
@@ -28,7 +31,7 @@
     @require_permission('edit_query')
     def post(self, visualization_id):
         vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)
-        require_admin_or_owner(vis.query_rel.user_id)
+        require_object_modify_permission(vis.query_rel, self.current_user)
 
         kwargs = request.get_json(force=True)
         if 'options' in kwargs:
@@ -45,6 +48,6 @@
     @require_permission('edit_query')
     def delete(self, visualization_id):
         vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)
-        require_admin_or_owner(vis.query_rel.user_id)
+        require_object_modify_permission(vis.query_rel, self.current_user)
         models.db.session.delete(vis)
         models.db.session.commit()
diff --git a/redash/permissions.py b/redash/permissions.py
--- a/redash/permissions.py
+++ b/redash/permissions.py
@@ -1,6 +1,7 @@
+import functools
+
 from flask_login import current_user
 from flask_restful import abort
-import functools
 from funcy import flatten
 
 view_only = True

{"golden_diff": "diff --git a/redash/handlers/visualizations.py b/redash/handlers/visualizations.py\n--- a/redash/handlers/visualizations.py\n+++ b/redash/handlers/visualizations.py\n@@ -1,9 +1,12 @@\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\n from redash.handlers.base import BaseResource, get_object_or_404\n+from redash.permissions import (require_admin_or_owner,\n+                                require_object_modify_permission,\n+                                require_permission)\n \n \n class VisualizationListResource(BaseResource):\n@@ -12,7 +15,7 @@\n         kwargs = request.get_json(force=True)\n \n         query = get_object_or_404(models.Query.get_by_id_and_org, kwargs.pop('query_id'), self.current_org)\n-        require_admin_or_owner(query.user_id)\n+        require_object_modify_permission(query, self.current_user)\n \n         kwargs['options'] = json.dumps(kwargs['options'])\n         kwargs['query_rel'] = query\n@@ -28,7 +31,7 @@\n     @require_permission('edit_query')\n     def post(self, visualization_id):\n         vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)\n-        require_admin_or_owner(vis.query_rel.user_id)\n+        require_object_modify_permission(vis.query_rel, self.current_user)\n \n         kwargs = request.get_json(force=True)\n         if 'options' in kwargs:\n@@ -45,6 +48,6 @@\n     @require_permission('edit_query')\n     def delete(self, visualization_id):\n         vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)\n-        require_admin_or_owner(vis.query_rel.user_id)\n+        require_object_modify_permission(vis.query_rel, self.current_user)\n         models.db.session.delete(vis)\n         models.db.session.commit()\ndiff --git a/redash/permissions.py b/redash/permissions.py\n--- a/redash/permissions.py\n+++ b/redash/permissions.py\n@@ -1,6 +1,7 @@\n+import functools\n+\n from flask_login import current_user\n from flask_restful import abort\n-import functools\n from funcy import flatten\n \n view_only = True\n", "issue": "Redash Permissions not working for some use cases\n### Issue Summary\r\n\r\nCurrently, when query owner grants permission to another user for a query, the user is still unable to perform the following tasks:\r\n\r\n* change data source\r\n* schedule the query\r\n* add and save new visualisation\r\n\r\nI believe the user should have the ability to do all the things that the owner could do once permission has been granted.\r\n\r\n### Technical details:\r\n\r\n* Redash Version: 1.0.3\r\n* Browser/OS: Chrome\r\n* How did you install Redash: AWS using the AMI\r\n\n", "before_files": [{"content": "import json\nfrom flask import request\n\nfrom redash import models\nfrom redash.permissions import require_permission, require_admin_or_owner\nfrom redash.handlers.base import BaseResource, get_object_or_404\n\n\nclass VisualizationListResource(BaseResource):\n    @require_permission('edit_query')\n    def post(self):\n        kwargs = request.get_json(force=True)\n\n        query = get_object_or_404(models.Query.get_by_id_and_org, kwargs.pop('query_id'), self.current_org)\n        require_admin_or_owner(query.user_id)\n\n        kwargs['options'] = json.dumps(kwargs['options'])\n        kwargs['query_rel'] = query\n\n        vis = models.Visualization(**kwargs)\n        models.db.session.add(vis)\n        models.db.session.commit()\n        d = vis.to_dict(with_query=False)\n        return d\n\n\nclass VisualizationResource(BaseResource):\n    @require_permission('edit_query')\n    def post(self, visualization_id):\n        vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)\n        require_admin_or_owner(vis.query_rel.user_id)\n\n        kwargs = request.get_json(force=True)\n        if 'options' in kwargs:\n            kwargs['options'] = json.dumps(kwargs['options'])\n\n        kwargs.pop('id', None)\n        kwargs.pop('query_id', None)\n\n        self.update_model(vis, kwargs)\n        d = vis.to_dict(with_query=False)\n        models.db.session.commit()\n        return d\n\n    @require_permission('edit_query')\n    def delete(self, visualization_id):\n        vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)\n        require_admin_or_owner(vis.query_rel.user_id)\n        models.db.session.delete(vis)\n        models.db.session.commit()\n", "path": "redash/handlers/visualizations.py"}, {"content": "from flask_login import current_user\nfrom flask_restful import abort\nimport functools\nfrom funcy import flatten\n\nview_only = True\nnot_view_only = False\n\nACCESS_TYPE_VIEW = 'view'\nACCESS_TYPE_MODIFY = 'modify'\nACCESS_TYPE_DELETE = 'delete'\n\nACCESS_TYPES = (ACCESS_TYPE_VIEW, ACCESS_TYPE_MODIFY, ACCESS_TYPE_DELETE)\n\n\ndef has_access(object_groups, user, need_view_only):\n    if 'admin' in user.permissions:\n        return True\n\n    matching_groups = set(object_groups.keys()).intersection(user.group_ids)\n\n    if not matching_groups:\n        return False\n\n    required_level = 1 if need_view_only else 2\n\n    group_level = 1 if all(flatten([object_groups[group] for group in matching_groups])) else 2\n\n    return required_level <= group_level\n\n\ndef require_access(object_groups, user, need_view_only):\n    if not has_access(object_groups, user, need_view_only):\n        abort(403)\n\n\nclass require_permissions(object):\n    def __init__(self, permissions):\n        self.permissions = permissions\n\n    def __call__(self, fn):\n        @functools.wraps(fn)\n        def decorated(*args, **kwargs):\n            has_permissions = current_user.has_permissions(self.permissions)\n\n            if has_permissions:\n                return fn(*args, **kwargs)\n            else:\n                abort(403)\n\n        return decorated\n\n\ndef require_permission(permission):\n    return require_permissions((permission,))\n\n\ndef require_admin(fn):\n    return require_permission('admin')(fn)\n\n\ndef require_super_admin(fn):\n    return require_permission('super_admin')(fn)\n\n\ndef has_permission_or_owner(permission, object_owner_id):\n    return int(object_owner_id) == current_user.id or current_user.has_permission(permission)\n\n\ndef is_admin_or_owner(object_owner_id):\n    return has_permission_or_owner('admin', object_owner_id)\n\n\ndef require_permission_or_owner(permission, object_owner_id):\n    if not has_permission_or_owner(permission, object_owner_id):\n        abort(403)\n\n\ndef require_admin_or_owner(object_owner_id):\n    if not is_admin_or_owner(object_owner_id):\n        abort(403, message=\"You don't have permission to edit this resource.\")\n\n\ndef can_modify(obj, user):\n    return is_admin_or_owner(obj.user_id) or user.has_access(obj, ACCESS_TYPE_MODIFY)\n\n\ndef require_object_modify_permission(obj, user):\n    if not can_modify(obj, user):\n        abort(403)\n", "path": "redash/permissions.py"}], "after_files": [{"content": "import json\n\nfrom flask import request\n\nfrom redash import models\nfrom redash.handlers.base import BaseResource, get_object_or_404\nfrom redash.permissions import (require_admin_or_owner,\n                                require_object_modify_permission,\n                                require_permission)\n\n\nclass VisualizationListResource(BaseResource):\n    @require_permission('edit_query')\n    def post(self):\n        kwargs = request.get_json(force=True)\n\n        query = get_object_or_404(models.Query.get_by_id_and_org, kwargs.pop('query_id'), self.current_org)\n        require_object_modify_permission(query, self.current_user)\n\n        kwargs['options'] = json.dumps(kwargs['options'])\n        kwargs['query_rel'] = query\n\n        vis = models.Visualization(**kwargs)\n        models.db.session.add(vis)\n        models.db.session.commit()\n        d = vis.to_dict(with_query=False)\n        return d\n\n\nclass VisualizationResource(BaseResource):\n    @require_permission('edit_query')\n    def post(self, visualization_id):\n        vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)\n        require_object_modify_permission(vis.query_rel, self.current_user)\n\n        kwargs = request.get_json(force=True)\n        if 'options' in kwargs:\n            kwargs['options'] = json.dumps(kwargs['options'])\n\n        kwargs.pop('id', None)\n        kwargs.pop('query_id', None)\n\n        self.update_model(vis, kwargs)\n        d = vis.to_dict(with_query=False)\n        models.db.session.commit()\n        return d\n\n    @require_permission('edit_query')\n    def delete(self, visualization_id):\n        vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)\n        require_object_modify_permission(vis.query_rel, self.current_user)\n        models.db.session.delete(vis)\n        models.db.session.commit()\n", "path": "redash/handlers/visualizations.py"}, {"content": "import functools\n\nfrom flask_login import current_user\nfrom flask_restful import abort\nfrom funcy import flatten\n\nview_only = True\nnot_view_only = False\n\nACCESS_TYPE_VIEW = 'view'\nACCESS_TYPE_MODIFY = 'modify'\nACCESS_TYPE_DELETE = 'delete'\n\nACCESS_TYPES = (ACCESS_TYPE_VIEW, ACCESS_TYPE_MODIFY, ACCESS_TYPE_DELETE)\n\n\ndef has_access(object_groups, user, need_view_only):\n    if 'admin' in user.permissions:\n        return True\n\n    matching_groups = set(object_groups.keys()).intersection(user.group_ids)\n\n    if not matching_groups:\n        return False\n\n    required_level = 1 if need_view_only else 2\n\n    group_level = 1 if all(flatten([object_groups[group] for group in matching_groups])) else 2\n\n    return required_level <= group_level\n\n\ndef require_access(object_groups, user, need_view_only):\n    if not has_access(object_groups, user, need_view_only):\n        abort(403)\n\n\nclass require_permissions(object):\n    def __init__(self, permissions):\n        self.permissions = permissions\n\n    def __call__(self, fn):\n        @functools.wraps(fn)\n        def decorated(*args, **kwargs):\n            has_permissions = current_user.has_permissions(self.permissions)\n\n            if has_permissions:\n                return fn(*args, **kwargs)\n            else:\n                abort(403)\n\n        return decorated\n\n\ndef require_permission(permission):\n    return require_permissions((permission,))\n\n\ndef require_admin(fn):\n    return require_permission('admin')(fn)\n\n\ndef require_super_admin(fn):\n    return require_permission('super_admin')(fn)\n\n\ndef has_permission_or_owner(permission, object_owner_id):\n    return int(object_owner_id) == current_user.id or current_user.has_permission(permission)\n\n\ndef is_admin_or_owner(object_owner_id):\n    return has_permission_or_owner('admin', object_owner_id)\n\n\ndef require_permission_or_owner(permission, object_owner_id):\n    if not has_permission_or_owner(permission, object_owner_id):\n        abort(403)\n\n\ndef require_admin_or_owner(object_owner_id):\n    if not is_admin_or_owner(object_owner_id):\n        abort(403, message=\"You don't have permission to edit this resource.\")\n\n\ndef can_modify(obj, user):\n    return is_admin_or_owner(obj.user_id) or user.has_access(obj, ACCESS_TYPE_MODIFY)\n\n\ndef require_object_modify_permission(obj, user):\n    if not can_modify(obj, user):\n        abort(403)\n", "path": "redash/permissions.py"}]}