Skip to content

Commit

Permalink
Validate user IDs against datasette_acl_actor_ids, if defined
Browse files Browse the repository at this point in the history
Refs #18
  • Loading branch information
simonw committed Sep 1, 2024
1 parent 67e62bb commit b4bbfc3
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 2 deletions.
20 changes: 20 additions & 0 deletions datasette_acl/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from datasette.plugins import pm
from datasette.utils import await_me_maybe


async def can_edit_permissions(datasette, actor):
return await datasette.permission_allowed(actor, "datasette-acl")

Expand All @@ -12,3 +16,19 @@ def generate_changes_message(changes_made, noun):
message = ", ".join(messages)
# Capitalize first letter
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 validate_actor_id(datasette, actor_id):
actor_ids = await get_acl_actor_ids(datasette)
if not actor_ids:
# No validation has been configured
return True
else:
return actor_id in actor_ids
6 changes: 5 additions & 1 deletion datasette_acl/views/groups.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datasette import Response, Forbidden, NotFound
from datasette_acl.utils import can_edit_permissions
from datasette_acl.utils import can_edit_permissions, validate_actor_id
import json
import re

Expand Down Expand Up @@ -201,6 +201,10 @@ async def remove_member(actor_id):
)
else:
# Add user
if not await validate_actor_id(datasette, to_add):
datasette.add_message(
request, "That user ID is not valid", datasette.ERROR
)
await internal_db.execute_write(
"""
insert into acl_actor_groups (actor_id, group_id)
Expand Down
10 changes: 9 additions & 1 deletion datasette_acl/views/table_acls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from datasette import Response, Forbidden
from datasette_acl.utils import can_edit_permissions, generate_changes_message
from datasette_acl.utils import (
can_edit_permissions,
generate_changes_message,
validate_actor_id,
)


async def manage_table_acls(request, datasette):
Expand Down Expand Up @@ -142,6 +146,10 @@ async def manage_table_acls(request, datasette):
actor_id = (post_vars.get("new_actor_id") or "").strip()
if not actor_id:
continue
if not await validate_actor_id(datasette, actor_id):
datasette.add_message(
request, "That user ID is not valid", datasette.ERROR
)
post_key_prefix = "new_user"
else:
post_key_prefix = f"user_permissions_{actor_id}"
Expand Down
93 changes: 93 additions & 0 deletions tests/test_acl_actor_ids.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from datasette import hookimpl
from datasette_acl.utils import get_acl_actor_ids
from datasette.plugins import pm
import pytest


@pytest.fixture
def register_plugin():
class TestActorIdsPlugin:
__name__ = "TestActorIdsPlugin"

@hookimpl
def datasette_acl_actor_ids(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 inner

pm.register(TestActorIdsPlugin(), name="undo")
try:
yield
finally:
pm.unregister(name="undo")


@pytest.mark.asyncio
async def test_datasette_acl_actor_ids_hook(ds, csrftoken, register_plugin):
plugins_response = await ds.client.get("/-/plugins.json")
assert any(
plugin
for plugin in plugins_response.json()
if plugin["name"] == "TestActorIdsPlugin"
)
await ds.get_internal_database().execute_write_script(
"""
create table if not exists users (username text primary key);
insert or ignore into users (username) values ('one');
insert or ignore into users (username) values ('two');
"""
)
actor_ids = await get_acl_actor_ids(ds)
assert actor_ids == ["one", "two"]

to_test = (
("one", True),
("three", False),
)

# Check these are validated when editing permissions
for actor_id, should_work in to_test:
response = await ds.client.post(
"/db/t/-/acl",
data={
"new_actor_id": actor_id,
"new_user_insert-row": "on",
"csrftoken": csrftoken,
},
cookies={
"ds_actor": ds.client.actor_cookie({"id": "root"}),
"ds_csrftoken": csrftoken,
},
)
assert response.status_code == 302
messages = ds.unsign(response.cookies["ds_messages"], "messages")
if should_work:
# Should be a positive message
assert messages[0][1] == ds.INFO
else:
assert messages[0] == ["That user ID is not valid", ds.ERROR]

# And when editing group members
for actor_id, should_work in to_test:
response = await ds.client.post(
"/-/acl/groups/dev",
data={
"add": actor_id,
"csrftoken": csrftoken,
},
cookies={
"ds_actor": ds.client.actor_cookie({"id": "root"}),
"ds_csrftoken": csrftoken,
},
)
assert response.status_code == 302
messages = ds.unsign(response.cookies["ds_messages"], "messages")
if should_work:
# Should be a positive message
assert messages[0][1] == ds.INFO
else:
assert messages[0] == ["That user ID is not valid", ds.ERROR]

0 comments on commit b4bbfc3

Please sign in to comment.