mirror of
https://github.com/simonw/datasette.git
synced 2026-06-17 22:37:48 +02:00
If a user does not own a private query they cannot update or delete it either, even if they have global update-query. https://github.com/simonw/datasette/pull/2741/changes#r3306417463
114 lines
3.3 KiB
Python
114 lines
3.3 KiB
Python
"""
|
|
Default permission settings for Datasette.
|
|
|
|
Provides default allow rules for standard view/execute actions.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Optional
|
|
|
|
if TYPE_CHECKING:
|
|
from datasette.app import Datasette
|
|
|
|
from datasette import hookimpl
|
|
from datasette.permissions import PermissionSQL
|
|
|
|
# Actions that are allowed by default (unless --default-deny is used)
|
|
DEFAULT_ALLOW_ACTIONS = frozenset(
|
|
{
|
|
"view-instance",
|
|
"view-database",
|
|
"view-database-download",
|
|
"view-table",
|
|
"view-query",
|
|
"execute-sql",
|
|
}
|
|
)
|
|
|
|
|
|
@hookimpl(specname="permission_resources_sql")
|
|
async def default_allow_sql_check(
|
|
datasette: "Datasette",
|
|
actor: Optional[dict],
|
|
action: str,
|
|
) -> Optional[PermissionSQL]:
|
|
"""
|
|
Enforce the default_allow_sql setting.
|
|
|
|
When default_allow_sql is false (the default), execute-sql is denied
|
|
unless explicitly allowed by config or other rules.
|
|
"""
|
|
if action == "execute-sql":
|
|
if not datasette.setting("default_allow_sql"):
|
|
return PermissionSQL.deny(reason="default_allow_sql is false")
|
|
|
|
return None
|
|
|
|
|
|
@hookimpl(specname="permission_resources_sql")
|
|
async def default_action_permissions_sql(
|
|
datasette: "Datasette",
|
|
actor: Optional[dict],
|
|
action: str,
|
|
) -> Optional[PermissionSQL]:
|
|
"""
|
|
Provide default allow rules for standard view/execute actions.
|
|
|
|
These defaults are skipped when datasette is started with --default-deny.
|
|
The restriction_sql mechanism (from actor_restrictions_sql) will still
|
|
filter these results if the actor has restrictions.
|
|
"""
|
|
if datasette.default_deny:
|
|
return None
|
|
|
|
if action in DEFAULT_ALLOW_ACTIONS:
|
|
reason = f"default allow for {action}".replace("'", "''")
|
|
return PermissionSQL.allow(reason=reason)
|
|
|
|
return None
|
|
|
|
|
|
@hookimpl(specname="permission_resources_sql")
|
|
async def default_query_permissions_sql(
|
|
datasette: "Datasette",
|
|
actor: Optional[dict],
|
|
action: str,
|
|
) -> Optional[PermissionSQL]:
|
|
actor_id = actor.get("id") if isinstance(actor, dict) else None
|
|
|
|
if action not in {"view-query", "update-query", "delete-query"}:
|
|
return None
|
|
|
|
params = {"query_owner_id": actor_id}
|
|
rule_sqls = []
|
|
if actor_id is not None:
|
|
if action in {"update-query", "delete-query"}:
|
|
# Query owner can update/delete query
|
|
rule_sqls.append("""
|
|
SELECT database_name AS parent, name AS child, 1 AS allow,
|
|
'query owner' AS reason
|
|
FROM queries
|
|
WHERE source = 'user'
|
|
AND owner_id = :query_owner_id
|
|
""")
|
|
else:
|
|
# Query owner can view-query
|
|
rule_sqls.append("""
|
|
SELECT database_name AS parent, name AS child, 1 AS allow,
|
|
'query owner' AS reason
|
|
FROM queries
|
|
WHERE owner_id = :query_owner_id
|
|
""")
|
|
|
|
# restriction_sql enforces private queries ONLY visible/mutable by owner
|
|
return PermissionSQL(
|
|
sql="\nUNION ALL\n".join(rule_sqls) if rule_sqls else None,
|
|
restriction_sql="""
|
|
SELECT database_name AS parent, name AS child
|
|
FROM queries
|
|
WHERE is_private = 0
|
|
OR owner_id = :query_owner_id
|
|
""",
|
|
params=params,
|
|
)
|