Skip to content

Commit

Permalink
datasette_acl_actor_ids is now datasette_acl_valid_actors
Browse files Browse the repository at this point in the history
Closes #23
  • Loading branch information
simonw committed Sep 10, 2024
1 parent d2e0249 commit 37325c7
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 35 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ plugins:

By default, users of this plugin can assign permissions to any actor ID by entering that ID, whether or not that ID corresponds to a user that exists elsewhere in the current Datasette configuration.

If you are running this plugin in an environment with a fixed, known list of actor IDs you can implement a plugin using the `datasette_acl_actor_ids(datasette)` plugin hook which returns an iterable sequence of string actor IDs.
If you are running this plugin in an environment with a fixed, known list of actor IDs you can implement a plugin using the `datasette_acl_valid_actors(datasette)` plugin hook which returns an iterable sequence of string actor IDs or `{"id": "actor-id", "display": "Actor Name"}` dictionaries

These will then be used for both validation and autocomplete, ensuring users do not attach actor IDs that are not in that list.

Expand All @@ -112,20 +112,18 @@ Example plugin implementation:
from datasette import hookimpl
@hookimpl
def datasette_acl_actor_ids(datasette):
def datasette_acl_valid_actors(datasette):
return ["paulo", "rohan", "simon"]
```
This function can also return an async inner function, for making async calls.
This function can also return an async inner function, for making async calls. This example uses the `[{"id": "actor-id", "display": "Actor Name"}]` format:
```python
from datasette import hookimpl
@hookimpl
def datasette_acl_actor_ids(datasette):
def datasette_acl_valid_actors(datasette):
async def inner():
db = datasette.get_internal_database()
return [r[0] for r in (
await db.execute("select username from users")
).rows]
return (await db.execute("select id, username as display from users")).dicts()
return inner
```

Expand Down
9 changes: 7 additions & 2 deletions datasette_acl/hookspecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@


@hookspec
def datasette_acl_actor_ids(datasette):
def datasette_acl_valid_actors(datasette):
"""
List of actor IDs that can be autocompleted against when editing permissions
List of actors that can be autocompleted against when editing permissions
This hook can return:
- A list of string actor IDs
- A list of dictionaries with "id" and "display" keys
- A function or awaitable function that returns one of the above
"""
6 changes: 3 additions & 3 deletions datasette_acl/templates/manage_acl_group.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ <h2>Add a member</h2>
<form action="{{ request.path }}" method="post" class="core">
<input type="hidden" name="csrftoken" value="{{ csrftoken() }}">
<label for="id_add" style="flex-shrink: 0;">User ID</label>
{% if valid_actor_ids %}
{% if valid_actors %}
<div class="choices" data-type="select-one" tabindex="0" style="flex-grow: 1;">
<select id="id_add" name="add" class="select-choice">
<option></option>
{% for actor_id in valid_actor_ids %}
{% if actor_id not in members %}<option>{{ actor_id }}</option>{% endif %}
{% for actor_id, actor_display in valid_actors %}
{% if actor_id not in members %}<option value="{{ actor_id }}">{{ actor_display }}</option>{% endif %}
{% endfor %}
</select>
</div>
Expand Down
6 changes: 3 additions & 3 deletions datasette_acl/templates/manage_table_acls.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ <h3>Users</h3>

<div style="margin-top: 2em">
<label for="id_new_actor_id" style="display: block; font-size: 0.8em">Other user:</label>
{% if valid_actor_ids %}
{% if valid_actors %}
<select id="id_new_actor_id" name="new_actor_id">
<option></option>
{% for actor_id in valid_actor_ids %}
<option>{{ actor_id }}</option>
{% for actor in valid_actors %}
<option value="{{ actor[0] }}">{{ actor[1] }}</option>
{% endfor %}
</select>
{% else %}
Expand Down
22 changes: 14 additions & 8 deletions datasette_acl/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datasette.plugins import pm
from datasette.utils import await_me_maybe
from typing import List, Tuple


async def can_edit_permissions(datasette, actor):
Expand All @@ -18,17 +19,22 @@ def generate_changes_message(changes_made, noun):
return message[0].upper() + message[1:]


async def get_acl_actor_ids(datasette):
actor_ids = []
for hook in pm.hook.datasette_acl_actor_ids(datasette=datasette):
actor_ids.extend(await await_me_maybe(hook))
return actor_ids
async def get_acl_valid_actors(datasette) -> List[Tuple[str, str]]:
all_actors = []
for hook in pm.hook.datasette_acl_valid_actors(datasette=datasette):
actors = await await_me_maybe(hook)
for actor in actors:
if isinstance(actor, str):
all_actors.append((actor, actor))
else:
all_actors.append((actor["id"], actor["display"]))
return all_actors


async def validate_actor_id(datasette, actor_id):
actor_ids = await get_acl_actor_ids(datasette)
if not actor_ids:
actors = await get_acl_valid_actors(datasette)
if not actors:
# No validation has been configured
return True
else:
return actor_id in actor_ids
return actor_id in dict(actors)
4 changes: 2 additions & 2 deletions datasette_acl/views/groups.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datasette import Response, Forbidden, NotFound
from datasette_acl.utils import (
can_edit_permissions,
get_acl_actor_ids,
get_acl_valid_actors,
validate_actor_id,
)
import json
Expand Down Expand Up @@ -244,7 +244,7 @@ async def remove_member(actor_id):
[group_id],
)
],
"valid_actor_ids": await get_acl_actor_ids(datasette),
"valid_actors": await get_acl_valid_actors(datasette),
},
request=request,
)
Expand Down
4 changes: 2 additions & 2 deletions datasette_acl/views/table_acls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datasette_acl.utils import (
can_edit_permissions,
generate_changes_message,
get_acl_actor_ids,
get_acl_valid_actors,
validate_actor_id,
)
from urllib.parse import parse_qs
Expand Down Expand Up @@ -308,7 +308,7 @@ async def manage_table_acls(request, datasette):
"group_permissions": current_group_permissions,
"user_permissions": current_user_permissions,
"audit_log": audit_log.rows,
"valid_actor_ids": await get_acl_actor_ids(datasette),
"valid_actors": await get_acl_valid_actors(datasette),
},
request=request,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datasette import hookimpl
from datasette_acl.utils import get_acl_actor_ids
from datasette_acl.utils import get_acl_valid_actors
from datasette.plugins import pm
import pytest

Expand All @@ -10,12 +10,14 @@ class TestActorIdsPlugin:
__name__ = "TestActorIdsPlugin"

@hookimpl
def datasette_acl_actor_ids(self, datasette):
def datasette_acl_valid_actors(self, datasette):
async def inner():
db = datasette.get_internal_database()
return [
r[0] for r in (await db.execute("select username from users")).rows
]
return (
await db.execute(
"select username as id, upper(username) as display from users"
)
).dicts()

return inner

Expand All @@ -27,7 +29,7 @@ async def inner():


@pytest.mark.asyncio
async def test_datasette_acl_actor_ids_hook(ds, csrftoken, register_plugin):
async def test_datasette_acl_valid_actors(ds, csrftoken, register_plugin):
plugins_response = await ds.client.get("/-/plugins.json")
assert any(
plugin
Expand All @@ -41,8 +43,8 @@ async def test_datasette_acl_actor_ids_hook(ds, csrftoken, register_plugin):
insert or ignore into users (username) values ('two');
"""
)
actor_ids = await get_acl_actor_ids(ds)
assert actor_ids == ["one", "two"]
actors = await get_acl_valid_actors(ds)
assert actors == [("one", "ONE"), ("two", "TWO")]

to_test = (
("one", True),
Expand Down

0 comments on commit 37325c7

Please sign in to comment.