actor_matches_allow utility function, refs #800

This commit is contained in:
Simon Willison 2020-06-06 11:39:11 -07:00
commit 14f6b4d200
3 changed files with 62 additions and 2 deletions

View file

@ -854,3 +854,22 @@ def call_with_supported_arguments(fn, **kwargs):
)
call_with.append(kwargs[parameter])
return fn(*call_with)
def actor_matches_allow(actor, allow):
if allow is None:
return True
for key, values in allow.items():
if values == "*" and key in actor:
return True
if isinstance(values, str):
values = [values]
actor_values = actor.get(key)
if actor_values is None:
return False
if isinstance(actor_values, str):
actor_values = [actor_values]
actor_values = set(actor_values)
if actor_values.intersection(values):
return True
return False

View file

@ -50,8 +50,8 @@ The URL on the first line includes a one-use token which can be used to sign in
.. _authentication_permissions_canned_queries:
Setting permissions for canned queries
======================================
Permissions for canned queries
==============================
Datasette's :ref:`canned_queries` default to allowing any user to execute them.
@ -120,6 +120,20 @@ If you want to provide access to any actor with a value for a specific key, use
These keys act as an "or" mechanism. A actor will be able to execute the query if any of their JSON properties match any of the values in the corresponding lists in the ``allow`` block.
.. _authentication_actor_matches_allow:
actor_matches_allow()
=====================
Plugins that wish to implement the same permissions scheme as canned queries can take advantage of the ``datasette.utils.actor_matches_allow(actor, allow)`` function:
.. code-block:: python
from datasette.utils import actor_matches_allow
actor_matches_allow({"id": "root"}, {"id": "*"})
# returns True
.. _PermissionsDebugView:
Permissions Debug

View file

@ -459,3 +459,30 @@ def test_multi_params(data, should_raise):
p1 = utils.MultiParams(data)
assert "bar" == p1["foo"]
assert ["bar", "baz"] == list(p1.getlist("foo"))
@pytest.mark.parametrize(
"actor,allow,expected",
[
({"id": "root"}, None, True),
({"id": "root"}, {}, False),
# Special "*" value for any key:
({"id": "root"}, {"id": "*"}, True),
({}, {"id": "*"}, False),
({"name": "root"}, {"id": "*"}, False),
# Supports single strings or list of values:
({"id": "root"}, {"id": "bob"}, False),
({"id": "root"}, {"id": ["bob"]}, False),
({"id": "root"}, {"id": "root"}, True),
({"id": "root"}, {"id": ["root"]}, True),
# Any matching role will work:
({"id": "garry", "roles": ["staff", "dev"]}, {"roles": ["staff"]}, True),
({"id": "garry", "roles": ["staff", "dev"]}, {"roles": ["dev"]}, True),
({"id": "garry", "roles": ["staff", "dev"]}, {"roles": ["otter"]}, False),
({"id": "garry", "roles": ["staff", "dev"]}, {"roles": ["dev", "otter"]}, True),
({"id": "garry", "roles": []}, {"roles": ["staff"]}, False),
({"id": "garry"}, {"roles": ["staff"]}, False),
],
)
def test_actor_matches_allow(actor, allow, expected):
assert expected == utils.actor_matches_allow(actor, allow)