PluginSQL renamed to PermissionSQL, closes #2524

This commit is contained in:
Simon Willison 2025-10-23 09:34:19 -07:00
commit b9c6e7a0f6
11 changed files with 84 additions and 81 deletions

View file

@ -115,7 +115,8 @@ from .tracer import AsgiTracer
from .plugins import pm, DEFAULT_PLUGINS, get_plugins from .plugins import pm, DEFAULT_PLUGINS, get_plugins
from .version import __version__ from .version import __version__
from .utils.permissions import build_rules_union, PluginSQL from .permissions import PermissionSQL
from .utils.permissions import build_rules_union
app_root = Path(__file__).parent.parent app_root = Path(__file__).parent.parent
@ -1067,11 +1068,11 @@ class Datasette:
async def allowed_resources_sql( async def allowed_resources_sql(
self, actor: dict | None, action: str self, actor: dict | None, action: str
) -> tuple[str, dict]: ) -> tuple[str, dict]:
"""Combine permission_resources_sql PluginSQL blocks into a UNION query. """Combine permission_resources_sql PermissionSQL blocks into a UNION query.
Returns a (sql, params) tuple suitable for execution against SQLite. Returns a (sql, params) tuple suitable for execution against SQLite.
""" """
plugin_blocks: List[PluginSQL] = [] plugin_blocks: List[PermissionSQL] = []
for block in pm.hook.permission_resources_sql( for block in pm.hook.permission_resources_sql(
datasette=self, datasette=self,
actor=actor, actor=actor,
@ -1087,7 +1088,7 @@ class Datasette:
for candidate in candidates: for candidate in candidates:
if candidate is None: if candidate is None:
continue continue
if not isinstance(candidate, PluginSQL): if not isinstance(candidate, PermissionSQL):
continue continue
plugin_blocks.append(candidate) plugin_blocks.append(candidate)

View file

@ -1,5 +1,5 @@
from datasette import hookimpl, Permission from datasette import hookimpl, Permission
from datasette.utils.permissions import PluginSQL from datasette.permissions import PermissionSQL
from datasette.utils import actor_matches_allow from datasette.utils import actor_matches_allow
import itsdangerous import itsdangerous
import time import time
@ -174,7 +174,7 @@ def permission_allowed_default(datasette, actor, action, resource):
@hookimpl @hookimpl
async def permission_resources_sql(datasette, actor, action): async def permission_resources_sql(datasette, actor, action):
rules: list[PluginSQL] = [] rules: list[PermissionSQL] = []
config_rules = await _config_permission_rules(datasette, actor, action) config_rules = await _config_permission_rules(datasette, actor, action)
rules.extend(config_rules) rules.extend(config_rules)
@ -191,7 +191,7 @@ async def permission_resources_sql(datasette, actor, action):
"SELECT NULL AS parent, NULL AS child, 1 AS allow, " f"'{reason}' AS reason" "SELECT NULL AS parent, NULL AS child, 1 AS allow, " f"'{reason}' AS reason"
) )
rules.append( rules.append(
PluginSQL( PermissionSQL(
source="default_permissions", source="default_permissions",
sql=sql, sql=sql,
params={}, params={},
@ -205,7 +205,7 @@ async def permission_resources_sql(datasette, actor, action):
return rules return rules
async def _config_permission_rules(datasette, actor, action) -> list[PluginSQL]: async def _config_permission_rules(datasette, actor, action) -> list[PermissionSQL]:
config = datasette.config or {} config = datasette.config or {}
if actor is None: if actor is None:
@ -332,7 +332,7 @@ async def _config_permission_rules(datasette, actor, action) -> list[PluginSQL]:
params[f"{key}_reason"] = reason params[f"{key}_reason"] = reason
sql = "\nUNION ALL\n".join(parts) sql = "\nUNION ALL\n".join(parts)
return [PluginSQL(source="config_permissions", sql=sql, params=params)] return [PermissionSQL(source="config_permissions", sql=sql, params=params)]
async def _resolve_config_permissions_blocks(datasette, actor, action, resource): async def _resolve_config_permissions_blocks(datasette, actor, action, resource):

View file

@ -124,8 +124,8 @@ def permission_allowed(datasette, actor, action, resource):
def permission_resources_sql(datasette, actor, action): def permission_resources_sql(datasette, actor, action):
"""Return SQL query fragments for permission checks on resources. """Return SQL query fragments for permission checks on resources.
Returns None, a PluginSQL object, or a list of PluginSQL objects. Returns None, a PermissionSQL object, or a list of PermissionSQL objects.
Each PluginSQL contains SQL that should return rows with columns: Each PermissionSQL contains SQL that should return rows with columns:
parent (str|None), child (str|None), allow (int), reason (str). parent (str|None), child (str|None), allow (int), reason (str).
Used to efficiently check permissions across multiple resources at once. Used to efficiently check permissions across multiple resources at once.

View file

@ -1,6 +1,6 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, NamedTuple from typing import Any, Dict, Optional, NamedTuple
class Resource(ABC): class Resource(ABC):
@ -86,6 +86,21 @@ class Action:
resource_class: type[Resource] resource_class: type[Resource]
@dataclass
class PermissionSQL:
"""
A plugin contributes SQL that yields:
parent TEXT NULL,
child TEXT NULL,
allow INTEGER, -- 1 allow, 0 deny
reason TEXT
"""
source: str # identifier used for auditing (e.g., plugin name)
sql: str # SQL that SELECTs the 4 columns above
params: Dict[str, Any] # bound params for the SQL (values only; no ':' prefix)
# This is obsolete, replaced by Action and ResourceType # This is obsolete, replaced by Action and ResourceType
@dataclass @dataclass
class Permission: class Permission:

View file

@ -22,7 +22,7 @@ The core pattern is:
from typing import Optional from typing import Optional
from datasette.plugins import pm from datasette.plugins import pm
from datasette.utils import await_me_maybe from datasette.utils import await_me_maybe
from datasette.utils.permissions import PluginSQL from datasette.permissions import PermissionSQL
async def build_allowed_resources_sql( async def build_allowed_resources_sql(
@ -80,10 +80,10 @@ async def build_allowed_resources_sql(
continue continue
if isinstance(result, list): if isinstance(result, list):
for plugin_sql in result: for plugin_sql in result:
if isinstance(plugin_sql, PluginSQL): if isinstance(plugin_sql, PermissionSQL):
rule_sqls.append(plugin_sql.sql) rule_sqls.append(plugin_sql.sql)
all_params.update(plugin_sql.params) all_params.update(plugin_sql.params)
elif isinstance(result, PluginSQL): elif isinstance(result, PermissionSQL):
rule_sqls.append(result.sql) rule_sqls.append(result.sql)
all_params.update(result.params) all_params.update(result.params)
@ -211,10 +211,10 @@ async def check_permission_for_resource(
continue continue
if isinstance(result, list): if isinstance(result, list):
for plugin_sql in result: for plugin_sql in result:
if isinstance(plugin_sql, PluginSQL): if isinstance(plugin_sql, PermissionSQL):
rule_sqls.append(plugin_sql.sql) rule_sqls.append(plugin_sql.sql)
all_params.update(plugin_sql.params) all_params.update(plugin_sql.params)
elif isinstance(result, PluginSQL): elif isinstance(result, PermissionSQL):
rule_sqls.append(result.sql) rule_sqls.append(result.sql)
all_params.update(result.params) all_params.update(result.params)

View file

@ -1,31 +1,17 @@
# perm_utils.py # perm_utils.py
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union
import sqlite3 import sqlite3
from datasette.permissions import PermissionSQL
# ----------------------------- # -----------------------------
# Plugin interface & utilities # Plugin interface & utilities
# ----------------------------- # -----------------------------
@dataclass
class PluginSQL:
"""
A plugin contributes SQL that yields:
parent TEXT NULL,
child TEXT NULL,
allow INTEGER, -- 1 allow, 0 deny
reason TEXT
"""
source: str # identifier used for auditing (e.g., plugin name)
sql: str # SQL that SELECTs the 4 columns above
params: Dict[str, Any] # bound params for the SQL (values only; no ':' prefix)
def _namespace_params(i: int, params: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: def _namespace_params(i: int, params: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
""" """
Rewrite parameter placeholders to distinct names per plugin block. Rewrite parameter placeholders to distinct names per plugin block.
@ -45,12 +31,12 @@ def _namespace_params(i: int, params: Dict[str, Any]) -> Tuple[str, Dict[str, An
return rewrite, namespaced return rewrite, namespaced
PluginProvider = Callable[[str], PluginSQL] PluginProvider = Callable[[str], PermissionSQL]
PluginOrFactory = Union[PluginSQL, PluginProvider] PluginOrFactory = Union[PermissionSQL, PluginProvider]
def build_rules_union( def build_rules_union(
actor: str, plugins: Sequence[PluginSQL] actor: str, plugins: Sequence[PermissionSQL]
) -> Tuple[str, Dict[str, Any]]: ) -> Tuple[str, Dict[str, Any]]:
""" """
Compose plugin SQL into a UNION ALL with namespaced parameters. Compose plugin SQL into a UNION ALL with namespaced parameters.
@ -107,8 +93,8 @@ async def resolve_permissions_from_catalog(
(Use child=NULL for parent-scoped actions like "execute-sql".) (Use child=NULL for parent-scoped actions like "execute-sql".)
- *db* exposes: rows = await db.execute(sql, params) - *db* exposes: rows = await db.execute(sql, params)
where rows is an iterable of sqlite3.Row where rows is an iterable of sqlite3.Row
- plugins are either PluginSQL objects or callables accepting (action: str) - plugins are either PermissionSQL objects or callables accepting (action: str)
and returning PluginSQL instances selecting (parent, child, allow, reason) and returning PermissionSQL instances selecting (parent, child, allow, reason)
Decision policy: Decision policy:
1) Specificity first: child (depth=2) > parent (depth=1) > root (depth=0) 1) Specificity first: child (depth=2) > parent (depth=1) > root (depth=0)
@ -121,14 +107,14 @@ async def resolve_permissions_from_catalog(
- parent, child, allow, reason, source_plugin, depth - parent, child, allow, reason, source_plugin, depth
- resource (rendered "/parent/child" or "/parent" or "/") - resource (rendered "/parent/child" or "/parent" or "/")
""" """
resolved_plugins: List[PluginSQL] = [] resolved_plugins: List[PermissionSQL] = []
for plugin in plugins: for plugin in plugins:
if callable(plugin) and not isinstance(plugin, PluginSQL): if callable(plugin) and not isinstance(plugin, PermissionSQL):
resolved = plugin(action) # type: ignore[arg-type] resolved = plugin(action) # type: ignore[arg-type]
else: else:
resolved = plugin # type: ignore[assignment] resolved = plugin # type: ignore[assignment]
if not isinstance(resolved, PluginSQL): if not isinstance(resolved, PermissionSQL):
raise TypeError("Plugin providers must return PluginSQL instances") raise TypeError("Plugin providers must return PermissionSQL instances")
resolved_plugins.append(resolved) resolved_plugins.append(resolved)
union_sql, rule_params = build_rules_union(actor, resolved_plugins) union_sql, rule_params = build_rules_union(actor, resolved_plugins)

View file

@ -9,7 +9,8 @@ from datasette.utils import (
tilde_encode, tilde_encode,
tilde_decode, tilde_decode,
) )
from datasette.utils.permissions import PluginSQL, resolve_permissions_from_catalog from datasette.permissions import PermissionSQL
from datasette.utils.permissions import resolve_permissions_from_catalog
from datasette.plugins import pm from datasette.plugins import pm
from .base import BaseView, View from .base import BaseView, View
import secrets import secrets
@ -303,9 +304,9 @@ class AllowedResourcesView(BaseView):
for candidate in candidates: for candidate in candidates:
if candidate is None: if candidate is None:
continue continue
if not isinstance(candidate, PluginSQL): if not isinstance(candidate, PermissionSQL):
logger.warning( logger.warning(
"Skipping permission_resources_sql result %r from plugin; expected PluginSQL", "Skipping permission_resources_sql result %r from plugin; expected PermissionSQL",
candidate, candidate,
) )
continue continue

View file

@ -12,7 +12,7 @@ import pytest
import pytest_asyncio import pytest_asyncio
from datasette.app import Datasette from datasette.app import Datasette
from datasette.plugins import pm from datasette.plugins import pm
from datasette.utils.permissions import PluginSQL from datasette.permissions import PermissionSQL
from datasette.resources import TableResource from datasette.resources import TableResource
from datasette import hookimpl from datasette import hookimpl
@ -63,7 +63,7 @@ async def test_allowed_resources_global_allow(test_ds):
def rules_callback(datasette, actor, action): def rules_callback(datasette, actor, action):
if actor and actor.get("id") == "alice": if actor and actor.get("id") == "alice":
sql = "SELECT NULL AS parent, NULL AS child, 1 AS allow, 'global: alice has access' AS reason" sql = "SELECT NULL AS parent, NULL AS child, 1 AS allow, 'global: alice has access' AS reason"
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -101,7 +101,7 @@ async def test_allowed_specific_resource(test_ds):
UNION ALL UNION ALL
SELECT 'analytics' AS parent, NULL AS child, 1 AS allow, 'analyst access' AS reason SELECT 'analytics' AS parent, NULL AS child, 1 AS allow, 'analyst access' AS reason
""" """
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -145,7 +145,7 @@ async def test_allowed_resources_with_reasons(test_ds):
SELECT 'analytics' AS parent, 'sensitive' AS child, 0 AS allow, SELECT 'analytics' AS parent, 'sensitive' AS child, 0 AS allow,
'child: sensitive data denied' AS reason 'child: sensitive data denied' AS reason
""" """
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -185,7 +185,7 @@ async def test_child_deny_overrides_parent_allow(test_ds):
SELECT 'analytics' AS parent, 'sensitive' AS child, 0 AS allow, SELECT 'analytics' AS parent, 'sensitive' AS child, 0 AS allow,
'child: deny sensitive' AS reason 'child: deny sensitive' AS reason
""" """
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -233,7 +233,7 @@ async def test_child_allow_overrides_parent_deny(test_ds):
SELECT 'production' AS parent, 'orders' AS child, 1 AS allow, SELECT 'production' AS parent, 'orders' AS child, 1 AS allow,
'child: carol can see orders' AS reason 'child: carol can see orders' AS reason
""" """
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -304,7 +304,7 @@ async def test_sql_does_filtering_not_python(test_ds):
SELECT 'analytics' AS parent, 'users' AS child, 1 AS allow, SELECT 'analytics' AS parent, 'users' AS child, 1 AS allow,
'specific allow' AS reason 'specific allow' AS reason
""" """
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
pm.register(plugin, name="test_plugin") pm.register(plugin, name="test_plugin")

View file

@ -12,7 +12,7 @@ from datasette.app import Datasette
from datasette import cli, hookimpl, Permission from datasette import cli, hookimpl, Permission
from datasette.filters import FilterArguments from datasette.filters import FilterArguments
from datasette.plugins import get_plugins, DEFAULT_PLUGINS, pm from datasette.plugins import get_plugins, DEFAULT_PLUGINS, pm
from datasette.utils.permissions import PluginSQL from datasette.permissions import PermissionSQL
from datasette.utils.sqlite import sqlite3 from datasette.utils.sqlite import sqlite3
from datasette.utils import StartupError, await_me_maybe from datasette.utils import StartupError, await_me_maybe
from jinja2 import ChoiceLoader, FileSystemLoader from jinja2 import ChoiceLoader, FileSystemLoader
@ -722,7 +722,7 @@ async def test_hook_permission_resources_sql():
collected.append(block) collected.append(block)
assert collected assert collected
assert all(isinstance(item, PluginSQL) for item in collected) assert all(isinstance(item, PermissionSQL) for item in collected)
@pytest.mark.asyncio @pytest.mark.asyncio

View file

@ -8,7 +8,7 @@ import pytest
import pytest_asyncio import pytest_asyncio
from datasette.app import Datasette from datasette.app import Datasette
from datasette.plugins import pm from datasette.plugins import pm
from datasette.utils.permissions import PluginSQL from datasette.permissions import PermissionSQL
from datasette import hookimpl from datasette import hookimpl
@ -57,7 +57,7 @@ async def test_tables_endpoint_global_access(test_ds):
def rules_callback(datasette, actor, action): def rules_callback(datasette, actor, action):
if actor and actor.get("id") == "alice": if actor and actor.get("id") == "alice":
sql = "SELECT NULL AS parent, NULL AS child, 1 AS allow, 'global: alice has access' AS reason" sql = "SELECT NULL AS parent, NULL AS child, 1 AS allow, 'global: alice has access' AS reason"
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -97,7 +97,7 @@ async def test_tables_endpoint_database_restriction(test_ds):
if actor and actor.get("role") == "analyst": if actor and actor.get("role") == "analyst":
# Allow only analytics database # Allow only analytics database
sql = "SELECT 'analytics' AS parent, NULL AS child, 1 AS allow, 'analyst access' AS reason" sql = "SELECT 'analytics' AS parent, NULL AS child, 1 AS allow, 'analyst access' AS reason"
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -144,7 +144,7 @@ async def test_tables_endpoint_table_exception(test_ds):
UNION ALL UNION ALL
SELECT 'analytics' AS parent, 'users' AS child, 1 AS allow, 'carol exception' AS reason SELECT 'analytics' AS parent, 'users' AS child, 1 AS allow, 'carol exception' AS reason
""" """
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -186,7 +186,7 @@ async def test_tables_endpoint_deny_overrides_allow(test_ds):
UNION ALL UNION ALL
SELECT 'analytics' AS parent, 'sensitive' AS child, 0 AS allow, 'deny sensitive' AS reason SELECT 'analytics' AS parent, 'sensitive' AS child, 0 AS allow, 'deny sensitive' AS reason
""" """
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -252,7 +252,7 @@ async def test_tables_endpoint_specific_table_only(test_ds):
UNION ALL UNION ALL
SELECT 'production' AS parent, 'orders' AS child, 1 AS allow, 'specific table 2' AS reason SELECT 'production' AS parent, 'orders' AS child, 1 AS allow, 'specific table 2' AS reason
""" """
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)
@ -290,7 +290,7 @@ async def test_tables_endpoint_empty_result(test_ds):
if actor and actor.get("id") == "blocked": if actor and actor.get("id") == "blocked":
# Global deny # Global deny
sql = "SELECT NULL AS parent, NULL AS child, 0 AS allow, 'global deny' AS reason" sql = "SELECT NULL AS parent, NULL AS child, 0 AS allow, 'global deny' AS reason"
return PluginSQL(source="test", sql=sql, params={}) return PermissionSQL(source="test", sql=sql, params={})
return None return None
plugin = PermissionRulesPlugin(rules_callback) plugin = PermissionRulesPlugin(rules_callback)

View file

@ -1,7 +1,7 @@
import pytest import pytest
from datasette.app import Datasette from datasette.app import Datasette
from datasette.permissions import PermissionSQL
from datasette.utils.permissions import ( from datasette.utils.permissions import (
PluginSQL,
PluginProvider, PluginProvider,
resolve_permissions_from_catalog, resolve_permissions_from_catalog,
) )
@ -26,8 +26,8 @@ NO_RULES_SQL = (
def plugin_allow_all_for_user(user: str) -> PluginProvider: def plugin_allow_all_for_user(user: str) -> PluginProvider:
def provider(action: str) -> PluginSQL: def provider(action: str) -> PermissionSQL:
return PluginSQL( return PermissionSQL(
"allow_all", "allow_all",
""" """
SELECT NULL AS parent, NULL AS child, 1 AS allow, SELECT NULL AS parent, NULL AS child, 1 AS allow,
@ -41,8 +41,8 @@ def plugin_allow_all_for_user(user: str) -> PluginProvider:
def plugin_deny_specific_table(user: str, parent: str, child: str) -> PluginProvider: def plugin_deny_specific_table(user: str, parent: str, child: str) -> PluginProvider:
def provider(action: str) -> PluginSQL: def provider(action: str) -> PermissionSQL:
return PluginSQL( return PermissionSQL(
"deny_specific_table", "deny_specific_table",
""" """
SELECT :parent AS parent, :child AS child, 0 AS allow, SELECT :parent AS parent, :child AS child, 0 AS allow,
@ -56,8 +56,8 @@ def plugin_deny_specific_table(user: str, parent: str, child: str) -> PluginProv
def plugin_org_policy_deny_parent(parent: str) -> PluginProvider: def plugin_org_policy_deny_parent(parent: str) -> PluginProvider:
def provider(action: str) -> PluginSQL: def provider(action: str) -> PermissionSQL:
return PluginSQL( return PermissionSQL(
"org_policy_parent_deny", "org_policy_parent_deny",
""" """
SELECT :parent AS parent, NULL AS child, 0 AS allow, SELECT :parent AS parent, NULL AS child, 0 AS allow,
@ -70,8 +70,8 @@ def plugin_org_policy_deny_parent(parent: str) -> PluginProvider:
def plugin_allow_parent_for_user(user: str, parent: str) -> PluginProvider: def plugin_allow_parent_for_user(user: str, parent: str) -> PluginProvider:
def provider(action: str) -> PluginSQL: def provider(action: str) -> PermissionSQL:
return PluginSQL( return PermissionSQL(
"allow_parent", "allow_parent",
""" """
SELECT :parent AS parent, NULL AS child, 1 AS allow, SELECT :parent AS parent, NULL AS child, 1 AS allow,
@ -85,8 +85,8 @@ def plugin_allow_parent_for_user(user: str, parent: str) -> PluginProvider:
def plugin_child_allow_for_user(user: str, parent: str, child: str) -> PluginProvider: def plugin_child_allow_for_user(user: str, parent: str, child: str) -> PluginProvider:
def provider(action: str) -> PluginSQL: def provider(action: str) -> PermissionSQL:
return PluginSQL( return PermissionSQL(
"allow_child", "allow_child",
""" """
SELECT :parent AS parent, :child AS child, 1 AS allow, SELECT :parent AS parent, :child AS child, 1 AS allow,
@ -100,8 +100,8 @@ def plugin_child_allow_for_user(user: str, parent: str, child: str) -> PluginPro
def plugin_root_deny_for_all() -> PluginProvider: def plugin_root_deny_for_all() -> PluginProvider:
def provider(action: str) -> PluginSQL: def provider(action: str) -> PermissionSQL:
return PluginSQL( return PermissionSQL(
"root_deny", "root_deny",
""" """
SELECT NULL AS parent, NULL AS child, 0 AS allow, 'root deny for all on ' || :action AS reason SELECT NULL AS parent, NULL AS child, 0 AS allow, 'root deny for all on ' || :action AS reason
@ -115,8 +115,8 @@ def plugin_root_deny_for_all() -> PluginProvider:
def plugin_conflicting_same_child_rules( def plugin_conflicting_same_child_rules(
user: str, parent: str, child: str user: str, parent: str, child: str
) -> List[PluginProvider]: ) -> List[PluginProvider]:
def allow_provider(action: str) -> PluginSQL: def allow_provider(action: str) -> PermissionSQL:
return PluginSQL( return PermissionSQL(
"conflict_child_allow", "conflict_child_allow",
""" """
SELECT :parent AS parent, :child AS child, 1 AS allow, SELECT :parent AS parent, :child AS child, 1 AS allow,
@ -126,8 +126,8 @@ def plugin_conflicting_same_child_rules(
{"parent": parent, "child": child, "user": user, "action": action}, {"parent": parent, "child": child, "user": user, "action": action},
) )
def deny_provider(action: str) -> PluginSQL: def deny_provider(action: str) -> PermissionSQL:
return PluginSQL( return PermissionSQL(
"conflict_child_deny", "conflict_child_deny",
""" """
SELECT :parent AS parent, :child AS child, 0 AS allow, SELECT :parent AS parent, :child AS child, 0 AS allow,
@ -141,14 +141,14 @@ def plugin_conflicting_same_child_rules(
def plugin_allow_all_for_action(user: str, allowed_action: str) -> PluginProvider: def plugin_allow_all_for_action(user: str, allowed_action: str) -> PluginProvider:
def provider(action: str) -> PluginSQL: def provider(action: str) -> PermissionSQL:
if action != allowed_action: if action != allowed_action:
return PluginSQL( return PermissionSQL(
f"allow_all_{allowed_action}_noop", f"allow_all_{allowed_action}_noop",
NO_RULES_SQL, NO_RULES_SQL,
{}, {},
) )
return PluginSQL( return PermissionSQL(
f"allow_all_{allowed_action}", f"allow_all_{allowed_action}",
""" """
SELECT NULL AS parent, NULL AS child, 1 AS allow, SELECT NULL AS parent, NULL AS child, 1 AS allow,