issue_comments
9 rows where author_association = "OWNER", issue = 1434094365 and user = 9599 sorted by updated_at descending
This data as json, CSV (advanced)
Suggested facets: created_at (date), updated_at (date)
issue 1
- Tool for simulating permission checks against actors · 9 ✖
id | html_url | issue_url | node_id | user | created_at | updated_at ▲ | author_association | body | reactions | issue | performed_via_github_app |
---|---|---|---|---|---|---|---|---|---|---|---|
1301645921 | https://github.com/simonw/datasette/issues/1881#issuecomment-1301645921 | https://api.github.com/repos/simonw/datasette/issues/1881 | IC_kwDOBm6k_c5NlYph | simonw 9599 | 2022-11-03T05:10:05Z | 2022-12-09T01:38:21Z | OWNER | I'd love to come up with a good short name for the second part of the resource two-tuple, the thing which is usually the name of a table but could also be the name of a SQL view or the name of a canned query. Idea 8th December: why not call it resource? A resource could be a thing that lives inside a database. |
{ "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
Tool for simulating permission checks against actors 1434094365 | |
1302813449 | https://github.com/simonw/datasette/issues/1881#issuecomment-1302813449 | https://api.github.com/repos/simonw/datasette/issues/1881 | IC_kwDOBm6k_c5Np1sJ | simonw 9599 | 2022-11-04T00:14:07Z | 2022-11-04T00:14:07Z | OWNER | Tool is now live here: https://latest-1-0-dev.datasette.io/-/permissions Needs root perms, so access this first: https://latest-1-0-dev.datasette.io/login-as-root |
{ "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
Tool for simulating permission checks against actors 1434094365 | |
1302812918 | https://github.com/simonw/datasette/issues/1881#issuecomment-1302812918 | https://api.github.com/repos/simonw/datasette/issues/1881 | IC_kwDOBm6k_c5Np1j2 | simonw 9599 | 2022-11-04T00:13:05Z | 2022-11-04T00:13:05Z | OWNER | Has tests now. |
{ "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
Tool for simulating permission checks against actors 1434094365 | |
1301639741 | https://github.com/simonw/datasette/issues/1881#issuecomment-1301639741 | https://api.github.com/repos/simonw/datasette/issues/1881 | IC_kwDOBm6k_c5NlXI9 | simonw 9599 | 2022-11-03T04:58:21Z | 2022-11-03T04:58:21Z | OWNER | The whole |
{ "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
Tool for simulating permission checks against actors 1434094365 | |
1301639370 | https://github.com/simonw/datasette/issues/1881#issuecomment-1301639370 | https://api.github.com/repos/simonw/datasette/issues/1881 | IC_kwDOBm6k_c5NlXDK | simonw 9599 | 2022-11-03T04:57:21Z | 2022-11-03T04:57:21Z | OWNER | The plugin hook would be called |
{ "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
Tool for simulating permission checks against actors 1434094365 | |
1301638918 | https://github.com/simonw/datasette/issues/1881#issuecomment-1301638918 | https://api.github.com/repos/simonw/datasette/issues/1881 | IC_kwDOBm6k_c5NlW8G | simonw 9599 | 2022-11-03T04:56:06Z | 2022-11-03T04:56:06Z | OWNER | I've also introduced a new concept of a permission abbreviation, which like the permission name needs to be globally unique. That's a problem for plugins - they might just be able to guarantee that their permission long-form name is unique among other plugins (through sensible naming conventions) but the thing where they declare a initial-letters-only abbreviation is far more risky. I think abbreviations are optional - they are provided for core permissions but plugins are advised not to use them. Also Datasette could check that the installed plugins do not provide conflicting permissions on startup and refuse to start if they do. |
{ "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
Tool for simulating permission checks against actors 1434094365 | |
1301638156 | https://github.com/simonw/datasette/issues/1881#issuecomment-1301638156 | https://api.github.com/repos/simonw/datasette/issues/1881 | IC_kwDOBm6k_c5NlWwM | simonw 9599 | 2022-11-03T04:54:00Z | 2022-11-03T04:54:00Z | OWNER | If I have the permissions defined like this:
On the other hand though, plugins that introduce their own permissions - like https://datasette.io/plugins/datasette-edit-schema - will need a way to register those permissions with Datasette core. Probably another plugin hook. |
{ "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
Tool for simulating permission checks against actors 1434094365 | |
1301635906 | https://github.com/simonw/datasette/issues/1881#issuecomment-1301635906 | https://api.github.com/repos/simonw/datasette/issues/1881 | IC_kwDOBm6k_c5NlWNC | simonw 9599 | 2022-11-03T04:48:09Z | 2022-11-03T04:48:09Z | OWNER | I built this prototype on the http://127.0.0.1:8001/-/allow-debug page, which is open to anyone to visit. But... I just realized that using this tool can leak information - you can use it to guess the names of invisible databases and tables and run theoretical permission checks against them. Using the tool also pollutes the list of permission checks that show up on the root-anlo So.... I'm going to restrict the usage of this tool to users with access to |
{ "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
Tool for simulating permission checks against actors 1434094365 | |
1301635340 | https://github.com/simonw/datasette/issues/1881#issuecomment-1301635340 | https://api.github.com/repos/simonw/datasette/issues/1881 | IC_kwDOBm6k_c5NlWEM | simonw 9599 | 2022-11-03T04:46:41Z | 2022-11-03T04:46:41Z | OWNER | Built this prototype: In building it I realized I needed to know which permissions took a table, a database, both or neither. So I had to bake that into the code. Here's the prototype so far (which includes a prototype of the logic for the ```diff diff --git a/datasette/default_permissions.py b/datasette/default_permissions.py index 32b0c758..f68aa38f 100644 --- a/datasette/default_permissions.py +++ b/datasette/default_permissions.py @@ -6,8 +6,8 @@ import json import time -@hookimpl(tryfirst=True) -def permission_allowed(datasette, actor, action, resource): +@hookimpl(tryfirst=True, specname="permission_allowed") +def permission_allowed_default(datasette, actor, action, resource): async def inner(): if action in ( "permissions-debug", @@ -57,6 +57,44 @@ def permission_allowed(datasette, actor, action, resource): return inner +@hookimpl(specname="permission_allowed") +def permission_allowed_actor_restrictions(actor, action, resource): + if actor is None: + return None + r = actor.get("_r") + if not _r: + # No restrictions, so we have no opinion + return None + action_initials = "".join([word[0] for word in action.split("-")]) + # If _r is defined then we use those to further restrict the actor + # Crucially, we only use this to say NO (return False) - we never + # use it to return YES (True) because that might over-ride other + # restrictions placed on this actor + all_allowed = _r.get("a") + if all_allowed is not None: + assert isinstance(all_allowed, list) + if action_initials in all_allowed: + return None + # How about for the current database? + if action in ("view-database", "view-database-download", "execute-sql"): + database_allowed = _r.get("d", {}).get(resource) + if database_allowed is not None: + assert isinstance(database_allowed, list) + if action_initials in database_allowed: + return None + # Or the current table? That's any time the resource is (database, table) + if not isinstance(resource, str) and len(resource) == 2: + database, table = resource + table_allowed = _r.get("t", {}).get(database, {}).get(table) + # TODO: What should this do for canned queries? + if table_allowed is not None: + assert isinstance(table_allowed, list) + if action_initials in table_allowed: + return None + # This action is not specifically allowed, so reject it + return False + + @hookimpl def actor_from_request(datasette, request): prefix = "dstok" diff --git a/datasette/templates/allow_debug.html b/datasette/templates/allow_debug.html index 0f1b30f0..ae43f0f5 100644 --- a/datasette/templates/allow_debug.html +++ b/datasette/templates/allow_debug.html @@ -35,7 +35,7 @@ p.message-warning { Use this tool to try out different actor and allow combinations. See Defining permissions with "allow" blocks for documentation. -<form action="{{ urls.path('-/allow-debug') }}" method="get"> +<form action="{{ urls.path('-/allow-debug') }}" method="get" style="margin-bottom: 1em"> <label>Allow block</label> <textarea name="allow">{{ allow_input }}</textarea> @@ -55,4 +55,82 @@ p.message-warning {{% if result == "False" %} Result: deny {% endif %}+ Test permission check+ +This tool lets you simulate an actor and a permission check for that actor. + +<form action="{{ urls.path('-/allow-debug') }}" id="debug-post" method="post" style="margin-bottom: 1em"> + +
+
+ <label>Actor</label> + <textarea name="actor">{% if actor_input %}{{ actor_input }}{% else %}{"id": "root"}{% endif %}</textarea> +
+
+ <label>Permission check</label> +<label for="permission" style="display:block">Permission</label> + <select name="permission" id="permission"> + {% for permission in [ + "view-instance", + "view-database", + "view-database-download", + "view-table", + "view-query", + "insert-row", + "delete-row", + "drop-table", + "execute-sql", + "permissions-debug", + "debug-menu"] %} + <option value="{{ permission }}">{{ permission }}</option> + {% endfor %} + </select> + <label for="resource_1">Database name</label> +<label for="resource_2">Table or query name</label> +
+
+
+</form>
+
+<script>
+var rawPerms = {{ permissions|tojson }};
+var permissions = Object.fromEntries(rawPerms.map(([label, abbr, needs_resource_1, needs_resource_2, def]) => [label, {needs_resource_1, needs_resource_2, def}]))
+var permissionSelect = document.getElementById('permission');
+var resource1 = document.getElementById('resource_1');
+var resource2 = document.getElementById('resource_2');
+function updateResourceVisibility() {
+ var permission = permissionSelect.value;
+ var {needs_resource_1, needs_resource_2} = permissions[permission];
+ if (needs_resource_1) {
+ resource1.closest('p').style.display = 'block';
+ } else {
+ resource1.closest('p').style.display = 'none';
+ }
+ if (needs_resource_2) {
+ resource2.closest('p').style.display = 'block';
+ } else {
+ resource2.closest('p').style.display = 'none';
+ }
+}
+permissionSelect.addEventListener('change', updateResourceVisibility);
+updateResourceVisibility();
+
+// When #debug-post form is submitted, use fetch() to POST data
+var debugPost = document.getElementById('debug-post');
+debugPost.addEventListener('submit', function(ev) {
+ ev.preventDefault();
+ var formData = new FormData(debugPost);
+ console.log(formData);
+ fetch(debugPost.action, {
+ method: 'POST',
+ body: new URLSearchParams(formData),
+ }).then(function(response) {
+ return response.json();
+ }).then(function(data) {
+ alert(JSON.stringify(data, null, 4));
+ });
+});
+</script>
+ + {% endblock %} diff --git a/datasette/views/special.py b/datasette/views/special.py index 9922a621..d46fc280 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -1,6 +1,8 @@ import json +from datasette.permissions import PERMISSIONS from datasette.utils.asgi import Response, Forbidden from datasette.utils import actor_matches_allow, add_cors_headers +from datasette.permissions import PERMISSIONS from .base import BaseView import secrets import time @@ -138,9 +140,34 @@ class AllowDebugView(BaseView): "error": "\n\n".join(errors) if errors else "", "actor_input": actor_input, "allow_input": allow_input, + "permissions": PERMISSIONS, }, )
class MessagesDebugView(BaseView): name = "messages_debug" ``` |
{ "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
Tool for simulating permission checks against actors 1434094365 |
Advanced export
JSON shape: default, array, newline-delimited, object
CREATE TABLE [issue_comments] ( [html_url] TEXT, [issue_url] TEXT, [id] INTEGER PRIMARY KEY, [node_id] TEXT, [user] INTEGER REFERENCES [users]([id]), [created_at] TEXT, [updated_at] TEXT, [author_association] TEXT, [body] TEXT, [reactions] TEXT, [issue] INTEGER REFERENCES [issues]([id]) , [performed_via_github_app] TEXT); CREATE INDEX [idx_issue_comments_issue] ON [issue_comments] ([issue]); CREATE INDEX [idx_issue_comments_user] ON [issue_comments] ([user]);
user 1