security.access_grants

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

Per-record access grants — set-based authorization for lists and named policies.

Chirp's flatuser.permissionsgate answers route-level questions; this module adds resource-level grants stored in anaccess_grantstable and wired through the…

Per-record access grants — set-based authorization for lists and named policies.

Chirp's flatuser.permissionsgate answers route-level questions; this module adds resource-level grants stored in anaccess_grantstable and wired through the existing named-policy seam (access_policy()) and accessible_to().

Usage::

from chirp.data import Database, Query, migrate
from chirp.security.access_grants import (
    access_grants_ddl,
    access_policy,
    check_access,
    create_grant,
    register_access_policy,
    require_access,
)

# One-time migration — dialect-aware DDL (sqlite | postgresql).
# Copy access_grants_ddl("sqlite") or access_grants_ddl("postgresql") into
# migrations/NNN_access_grants.sql, then:
await migrate(db, "migrations/")
# Or apply directly: await db.execute_script(access_grants_ddl("postgresql"))

app.register_permission("documents.grant.read")
register_access_policy(app, "owns:document", "document", param="document_id", perm="read")

# List — one query, no N+1
docs = await (
    Query(Doc, "documents")
    .accessible_to(user, "read", resource_type="document")
    .fetch(db)
)

# Detail route — AuthSpec(policy="owns:document") or imperative guard
if not await check_access(db, user, "document", doc_id, "write"):
    ...

security.access_grants

Name Type Default Description
type
qualified_name
element_type
description
source_file
line_number
is_autodoc
autodoc_element
_autodoc_template
_autodoc_url_path
_autodoc_page_type
title
doc_content_hash

Symbols on this page

function access_grants_ddl

Return portableaccess_grantsCREATE TABLE / INDEX SQL for dialect.

dialect is"sqlite" or "postgresql" ("postgres"accepted). SQLite usesINTEGER PRIMARY KEY…

Jump to symbol
class AccessGrant

One row fromaccess_grants.

Jump to symbol
class SharingEscalationError

Raised when a user attempts to grant access beyond their feature permissions.

Jump to symbol
function sharing_escalation_errors

Return form field errors when user may not grant permission on resource_type.

A user may grantread when they hold grant.reador {resource_type}.grant.read…

Jump to symbol
function access_exists_clause

Build a correlatedEXISTSWHERE fragment for set-based grant filtering.

Jump to symbol
async function check_access

Return whether user holds perm on the given resource via grants.

Jump to symbol
async function require_access

RaiseHTTPError(403)and emit a security event when access is denied.

Jump to symbol
async function create_grant

Insert a grant after validating sharing escalation bounds.

RaisesSharingEscalationErrorwhen granter lacks the feature permission to create a grant at permissionlevel (fail…

Jump to symbol
function access_policy

Build a named-policy callable forAuthSpec(policy=...) / @requires.

Readsrequest.path_params[param]as the resource id and checks grants. When owner_paramis set, a matching…

Jump to symbol
function register_access_policy

Registeraccess_policy(...) under name via app.register_policy.

Jump to symbol
access_grants_ddl
function
def access_grants_ddl(dialect: str = 'sqlite') -> str

Return portableaccess_grantsCREATE TABLE / INDEX SQL for dialect.

dialect is"sqlite" or "postgresql" ("postgres"accepted). SQLite usesINTEGER PRIMARY KEY(implicit autoincrement; no AUTOINCREMENT keyword). PostgreSQL uses SERIAL PRIMARY KEY. Both forms are compatible withcreate_grant(), which reads the inserted row viaINSERT ... RETURNING rather than SQLite rowid.

Copy the returned SQL into a numbered migration, or apply it with execute_script().

Parameters

Name Type Default Description
dialect str 'sqlite'
AccessGrant
class

One row fromaccess_grants.

SharingEscalationError
class

Raised when a user attempts to grant access beyond their feature permissions.

sharing_escalation_errors
function
def sharing_escalation_errors(user: Any, permission: str, *, resource_type: str) -> dict[str, list[str]]

Return form field errors when user may not grant permission on resource_type.

A user may grantread when they hold grant.reador {resource_type}.grant.read; writerequires the corresponding grant.write / {resource_type}.grant.writeentries in user.permissions. Fail loud — never silently narrow the grant.

Parameters

Name Type Default Description
user Any
permission str
resource_type str
access_exists_clause
function
def access_exists_clause(*, table: str, resource_column: str, resource_type: str, perm: str, user_id: str, group_ids: tuple[str, ...], grants_table: str = GRANTS_TABLE) -> tuple[str, tuple[object, ...]]

Build a correlatedEXISTSWHERE fragment for set-based grant filtering.

Parameters

Name Type Default Description
table str
resource_column str
resource_type str
perm str
user_id str
group_ids tuple[str, ...]
grants_table str GRANTS_TABLE
check_access
function async
async def check_access(db: Database, user: Any, resource_type: str, resource_id: str, perm: str, *, group_ids: frozenset[str] | None = None, grants_table: str = GRANTS_TABLE) -> bool

Return whether user holds perm on the given resource via grants.

Parameters

Name Type Default Description
db Database
user Any
resource_type str
resource_id str
perm str
group_ids frozenset[str] | None None
grants_table str GRANTS_TABLE
require_access
function async
async def require_access(db: Database, user: Any, resource_type: str, resource_id: str, perm: str, *, request: Any | None = None, group_ids: frozenset[str] | None = None) -> None

RaiseHTTPError(403)and emit a security event when access is denied.

Parameters

Name Type Default Description
db Database
user Any
resource_type str
resource_id str
perm str
request Any | None None
group_ids frozenset[str] | None None
create_grant
function async
async def create_grant(db: Database, *, granter: Any, resource_type: str, resource_id: str, principal_type: PrincipalType, principal_id: str, permission: GrantPermission) -> AccessGrant

Insert a grant after validating sharing escalation bounds.

RaisesSharingEscalationErrorwhen granter lacks the feature permission to create a grant at permission level (fail loud, not silent strip).

Parameters

Name Type Default Description
db Database
granter Any
resource_type str
resource_id str
principal_type PrincipalType
principal_id str
permission GrantPermission
access_policy
function
def access_policy(resource_type: str, *, param: str, perm: str = PERM_READ, owner_param: str | None = None, grants_table: str = GRANTS_TABLE) -> PolicyCallable

Build a named-policy callable forAuthSpec(policy=...) / @requires.

Readsrequest.path_params[param]as the resource id and checks grants. When owner_param is set, a matching path param equal touser.idallows access without a grant row (explicit owner bypass at the call site).

Parameters

Name Type Default Description
resource_type str
param str
perm str PERM_READ
owner_param str | None None
grants_table str GRANTS_TABLE
register_access_policy
function
def register_access_policy(app: Any, name: str, resource_type: str, *, param: str, perm: str = PERM_READ, owner_param: str | None = None) -> None

Registeraccess_policy(...) under name via app.register_policy.

Parameters

Name Type Default Description
app Any
name str
resource_type str
param str
perm str PERM_READ
owner_param str | None None

View source · /home/runner/work/chirp/chirp/site/../src/chirp/security/access_grants.py:1