home / github

Menu
  • Search all tables
  • GraphQL API

issue_comments

Table actions
  • GraphQL API for issue_comments

9 rows where issue = 1434094365 sorted by updated_at descending

✖
✖

✎ View and edit SQL

This data as json, CSV (advanced)

Suggested facets: created_at (date), updated_at (date)

user 1

  • simonw 9

issue 1

  • Tool for simulating permission checks against actors · 9 ✖

author_association 1

  • OWNER 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 database_name or (database_name, table_name) tuple for resource is a bit of a code smell. Maybe this is a chance to tidy that up too?

{
    "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 register_permissions(), for consistency with register_routes() and register_commands().

{
    "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: python PERMISSIONS = ( Permission("view-instance", "vi", False, False, True), Permission("view-database", "vd", True, False, True), Permission("view-database-download", "vdd", True, False, True), Permission("view-table", "vt", True, True, True), Permission("view-query", "vq", True, True, True), Permission("insert-row", "ir", True, True, False), Permission("delete-row", "dr", True, True, False), Permission("drop-table", "dt", True, True, False), Permission("execute-sql", "es", True, False, True), Permission("permissions-debug", "pd", False, False, False), Permission("debug-menu", "dm", False, False, False), ) Instead of just calling them by their undeclared names in places like this: python await self.ds.permission_allowed( request.actor, "execute-sql", database, default=True ) On the one hand I can ditch that confusing default=True option - whether a permission is on by default becomes a characteristic of that Permission() itself, which feels much neater.

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 /-/permissions page.

So.... I'm going to restrict the usage of this tool to users with access to /-/permissions and put it on that page instead.

{
    "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 _r field on actor, see #1855):

```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, }, )

  • async def post(self, request):
  • vars = await request.post_vars()
  • actor = json.loads(vars["actor"])
  • permission = vars["permission"]
  • resource_1 = vars["resource_1"]
  • resource_2 = vars["resource_2"]
  • resource = []
  • if resource_1:
  • resource.append(resource_1)
  • if resource_2:
  • resource.append(resource_2)
  • resource = tuple(resource)
  • result = await self.ds.permission_allowed(
  • actor, permission, resource, default="USE_DEFAULT"
  • )
  • return Response.json(
  • {
  • "actor": actor,
  • "permission": permission,
  • "resource": resource,
  • "result": result,
  • }
  • ) +

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

CSV options:

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]);
Powered by Datasette · Queries took 20.502ms · About: github-to-sqlite
  • Sort ascending
  • Sort descending
  • Facet by this
  • Hide this column
  • Show all columns
  • Show not-blank rows