mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Compare commits
3 commits
main
...
permission
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e35a6b4f7 | ||
|
|
94be9953c5 | ||
|
|
5140f4e815 |
3 changed files with 186 additions and 46 deletions
|
|
@ -9,6 +9,7 @@ import time
|
|||
@hookimpl(tryfirst=True, specname="permission_allowed")
|
||||
def permission_allowed_default(datasette, actor, action, resource):
|
||||
async def inner():
|
||||
# id=root gets some special permissions:
|
||||
if action in (
|
||||
"permissions-debug",
|
||||
"debug-menu",
|
||||
|
|
@ -20,45 +21,72 @@ def permission_allowed_default(datasette, actor, action, resource):
|
|||
):
|
||||
if actor and actor.get("id") == "root":
|
||||
return True
|
||||
elif action == "view-instance":
|
||||
allow = datasette.metadata("allow")
|
||||
if allow is not None:
|
||||
return actor_matches_allow(actor, allow)
|
||||
elif action == "view-database":
|
||||
if resource == "_internal" and (actor is None or actor.get("id") != "root"):
|
||||
return False
|
||||
database_allow = datasette.metadata("allow", database=resource)
|
||||
if database_allow is None:
|
||||
return None
|
||||
return actor_matches_allow(actor, database_allow)
|
||||
elif action == "view-table":
|
||||
database, table = resource
|
||||
tables = datasette.metadata("tables", database=database) or {}
|
||||
table_allow = (tables.get(table) or {}).get("allow")
|
||||
if table_allow is None:
|
||||
return None
|
||||
return actor_matches_allow(actor, table_allow)
|
||||
elif action == "view-query":
|
||||
# Check if this query has a "allow" block in metadata
|
||||
database, query_name = resource
|
||||
query = await datasette.get_canned_query(database, query_name, actor)
|
||||
assert query is not None
|
||||
allow = query.get("allow")
|
||||
if allow is None:
|
||||
return None
|
||||
return actor_matches_allow(actor, allow)
|
||||
elif action == "execute-sql":
|
||||
# Use allow_sql block from database block, or from top-level
|
||||
database_allow_sql = datasette.metadata("allow_sql", database=resource)
|
||||
if database_allow_sql is None:
|
||||
database_allow_sql = datasette.metadata("allow_sql")
|
||||
if database_allow_sql is None:
|
||||
return None
|
||||
return actor_matches_allow(actor, database_allow_sql)
|
||||
|
||||
# Resolve metadata view permissions
|
||||
if action in (
|
||||
"view-instance",
|
||||
"view-database",
|
||||
"view-table",
|
||||
"view-query",
|
||||
"execute-sql",
|
||||
):
|
||||
result = await _resolve_metadata_view_permissions(
|
||||
datasette, actor, action, resource
|
||||
)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
# Check custom permissions: blocks
|
||||
return await _resolve_metadata_permissions_blocks(
|
||||
datasette, actor, action, resource
|
||||
)
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
async def _resolve_metadata_permissions_blocks(datasette, actor, action, resource):
|
||||
# Check custom permissions: blocks - not yet implemented
|
||||
return None
|
||||
|
||||
|
||||
async def _resolve_metadata_view_permissions(datasette, actor, action, resource):
|
||||
if action == "view-instance":
|
||||
allow = datasette.metadata("allow")
|
||||
if allow is not None:
|
||||
return actor_matches_allow(actor, allow)
|
||||
elif action == "view-database":
|
||||
if resource == "_internal" and (actor is None or actor.get("id") != "root"):
|
||||
return False
|
||||
database_allow = datasette.metadata("allow", database=resource)
|
||||
if database_allow is None:
|
||||
return None
|
||||
return actor_matches_allow(actor, database_allow)
|
||||
elif action == "view-table":
|
||||
database, table = resource
|
||||
tables = datasette.metadata("tables", database=database) or {}
|
||||
table_allow = (tables.get(table) or {}).get("allow")
|
||||
if table_allow is None:
|
||||
return None
|
||||
return actor_matches_allow(actor, table_allow)
|
||||
elif action == "view-query":
|
||||
# Check if this query has a "allow" block in metadata
|
||||
database, query_name = resource
|
||||
query = await datasette.get_canned_query(database, query_name, actor)
|
||||
assert query is not None
|
||||
allow = query.get("allow")
|
||||
if allow is None:
|
||||
return None
|
||||
return actor_matches_allow(actor, allow)
|
||||
elif action == "execute-sql":
|
||||
# Use allow_sql block from database block, or from top-level
|
||||
database_allow_sql = datasette.metadata("allow_sql", database=resource)
|
||||
if database_allow_sql is None:
|
||||
database_allow_sql = datasette.metadata("allow_sql")
|
||||
if database_allow_sql is None:
|
||||
return None
|
||||
return actor_matches_allow(actor, database_allow_sql)
|
||||
|
||||
|
||||
@hookimpl(specname="permission_allowed")
|
||||
def permission_allowed_actor_restrictions(actor, action, resource):
|
||||
if actor is None:
|
||||
|
|
|
|||
|
|
@ -185,8 +185,14 @@ The ``/-/allow-debug`` tool lets you try out different ``"action"`` blocks agai
|
|||
|
||||
.. _authentication_permissions_metadata:
|
||||
|
||||
Configuring permissions in metadata.json
|
||||
========================================
|
||||
Access permissions in metadata
|
||||
==============================
|
||||
|
||||
There are two ways to configure permissions using ``metadata.json`` (or ``metadata.yaml``).
|
||||
|
||||
For simple visibility permissions you can use ``"allow"`` blocks in the root, database, table and query sections.
|
||||
|
||||
For other permissions you can use a ``"permissions"`` block, described :ref:`in the next section <blah>`.
|
||||
|
||||
You can limit who is allowed to view different parts of your Datasette instance using ``"allow"`` keys in your :ref:`metadata` configuration.
|
||||
|
||||
|
|
@ -201,8 +207,8 @@ If a user cannot access a specific database, they will not be able to access tab
|
|||
|
||||
.. _authentication_permissions_instance:
|
||||
|
||||
Controlling access to an instance
|
||||
---------------------------------
|
||||
Access to an instance
|
||||
---------------------
|
||||
|
||||
Here's how to restrict access to your entire Datasette instance to just the ``"id": "root"`` user:
|
||||
|
||||
|
|
@ -228,8 +234,8 @@ One reason to do this is if you are using a Datasette plugin - such as `datasett
|
|||
|
||||
.. _authentication_permissions_database:
|
||||
|
||||
Controlling access to specific databases
|
||||
----------------------------------------
|
||||
Access to specific databases
|
||||
----------------------------
|
||||
|
||||
To limit access to a specific ``private.db`` database to just authenticated users, use the ``"allow"`` block like this:
|
||||
|
||||
|
|
@ -247,8 +253,8 @@ To limit access to a specific ``private.db`` database to just authenticated user
|
|||
|
||||
.. _authentication_permissions_table:
|
||||
|
||||
Controlling access to specific tables and views
|
||||
-----------------------------------------------
|
||||
Access to specific tables and views
|
||||
-----------------------------------
|
||||
|
||||
To limit access to the ``users`` table in your ``bakery.db`` database:
|
||||
|
||||
|
|
@ -277,8 +283,8 @@ This works for SQL views as well - you can list their names in the ``"tables"``
|
|||
|
||||
.. _authentication_permissions_query:
|
||||
|
||||
Controlling access to specific canned queries
|
||||
---------------------------------------------
|
||||
Access to specific canned queries
|
||||
---------------------------------
|
||||
|
||||
:ref:`canned_queries` allow you to configure named SQL queries in your ``metadata.json`` that can be executed by users. These queries can be set up to both read and write to the database, so controlling who can execute them can be important.
|
||||
|
||||
|
|
@ -333,6 +339,63 @@ To limit this ability for just one specific database, use this:
|
|||
}
|
||||
}
|
||||
|
||||
.. _authentication_permissions_other:
|
||||
|
||||
Other permissions in metadata
|
||||
=============================
|
||||
|
||||
For all other permissions, you can use one or more ``"permissions"`` blocks in your metadata.
|
||||
|
||||
To grant access to the :ref:`permissions debug tool <PermissionsDebugView>` to all signed in users you can grant ``permissions-debug`` to any actor with an ``id`` matching the wildcard ``*`` by adding this a the root of your metadata:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"permissions": {
|
||||
"debug-menu": {
|
||||
"id": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
To grant ``create-table`` to the user with ``id`` of ``editor`` for the ``docs`` database:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"databases": {
|
||||
"docs": {
|
||||
"permissions": {
|
||||
"create-table": {
|
||||
"id": "editor"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
And for ``insert-row`` against the ``reports`` table in that ``docs`` database:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"databases": {
|
||||
"docs": {
|
||||
"tables": {
|
||||
"reports": {
|
||||
"permissions": {
|
||||
"insert-row": {
|
||||
"id": "editor"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
The :ref:`PermissionsDebugView` can be useful for helping test permissions that you have configured in this way.
|
||||
|
||||
.. _CreateTokenView:
|
||||
|
||||
API Tokens
|
||||
|
|
@ -423,10 +486,12 @@ The currently authenticated actor is made available to plugins as ``request.acto
|
|||
The permissions debug tool
|
||||
==========================
|
||||
|
||||
The debug tool at ``/-/permissions`` is only available to the :ref:`authenticated root user <authentication_root>` (or any actor granted the ``permissions-debug`` action according to a plugin).
|
||||
The debug tool at ``/-/permissions`` is only available to the :ref:`authenticated root user <authentication_root>` (or any actor granted the ``permissions-debug`` action).
|
||||
|
||||
It shows the thirty most recent permission checks that have been carried out by the Datasette instance.
|
||||
|
||||
It also provides an interface for running hypothetical permission checks against a hypothetical actor. This is a useful way of confirming that your configured permissions work in the way you expect.
|
||||
|
||||
This is designed to help administrators and plugin authors understand exactly how permission checks are being carried out, in order to effectively configure Datasette's permission system.
|
||||
|
||||
.. _authentication_ds_actor:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import collections
|
||||
from datasette.app import Datasette
|
||||
from .fixtures import app_client, assert_permissions_checked, make_app_client
|
||||
from bs4 import BeautifulSoup as Soup
|
||||
|
|
@ -640,3 +641,49 @@ async def test_actor_restricted_permissions(
|
|||
"result": expected_result,
|
||||
}
|
||||
assert response.json() == expected
|
||||
|
||||
|
||||
PermMetadataTestCase = collections.namedtuple(
|
||||
"PermMetadataTestCase",
|
||||
"metadata,actor,action,resource,default,expected_result",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.xfail(reason="Not implemented yet")
|
||||
@pytest.mark.parametrize(
|
||||
"metadata,actor,action,resource,default,expected_result",
|
||||
(
|
||||
# Simple view-instance default=True example
|
||||
PermMetadataTestCase(
|
||||
metadata={},
|
||||
actor=None,
|
||||
action="view-instance",
|
||||
resource=None,
|
||||
default=True,
|
||||
expected_result=True,
|
||||
),
|
||||
# debug-menu on root
|
||||
PermMetadataTestCase(
|
||||
metadata={"permissions": {"debug-menu": {"id": "user"}}},
|
||||
actor={"id": "user"},
|
||||
action="debug-menu",
|
||||
resource=None,
|
||||
default=False,
|
||||
expected_result=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_permissions_in_metadata(
|
||||
perms_ds, metadata, actor, action, resource, default, expected_result
|
||||
):
|
||||
previous_metadata = perms_ds.metadata()
|
||||
updated_metadata = copy.deepcopy(previous_metadata)
|
||||
updated_metadata.update(metadata)
|
||||
try:
|
||||
result = await perms_ds.permission_allowed(
|
||||
actor, action, resource, default=default
|
||||
)
|
||||
assert result == expected_result
|
||||
finally:
|
||||
perms_ds._metadata_local = previous_metadata
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue