Saltar a contenido

Permisos y roles

El fichero de un realm

Quién puede hacer qué se declara en rbac/<realm>.py, un fichero por realm, que es tuyo: lo crea tai-api auth init o tai-api rbac new-realm y ningún comando lo pisa.

# rbac/main.py
from tai_api.rbac import source, role, app_permission, rls

source(realm_name="main", dbschemas=["public"])

ver_panel = app_permission("ver-panel", description="Ve el panel de control")

role(name="Administrador", api_permissions=["sudo", "main-admin"])
role(name="lector", api_permissions=["publicacion-read", "autor-read"], app_permissions=[ver_panel])
role(name="editor", api_permissions=["publicacion-create", "publicacion-update"], subroles=["lector"])

rls(target_schema="public", target_model="publicacion", target_field="autor_id")
Función Qué declara
source(realm_name, dbschemas) Los permisos de API: los de cada tabla de esos schemas, más los globales. Es la única forma de declararlos
app_permission(name, description?, attributes?) Un permiso de aplicación: no lo exige ningún endpoint; viaja al frontend para decidir qué enseña
role(name, description?, api_permissions?, app_permissions?, subroles?) Un rol, con sus permisos
rls(target_schema, target_model, target_field) Una regla de filtrado por fila

realm_name tiene que coincidir con el nombre del fichero: de ahí sale el permiso <realm>-admin.

Los permisos de API

source() registra, por cada tabla, <tabla>-admin, -create, -read, -update y -delete; por cada vista, <vista>-admin y -read. Con el nombre de la tabla con guion bajo (etiqueta_publicacion-read), no con los guiones de la ruta.

Endpoint Pide
Lecturas y agregaciones <tabla>-read
POST <tabla>-create
PATCH <tabla>-update
DELETE <tabla>-delete
Enumerados solo estar autenticado

Las escrituras anidadas piden además el permiso de su tabla: un POST /receta con sus pasos_receta dentro pide receta-create y paso_receta-create; un PATCH /receta/{id} con {"pasos_receta": {"delete": [...]}}, receta-update y paso_receta-delete. Hasta tai-api 0.6.2 bastaba el del padre: un rol que edita filas hijas desde el formulario del padre necesita ahora también los de esas tablas. Ver Escrituras anidadas.

Tres permisos valen por más: sudo vale por todo, <tabla>-admin por cualquier acción sobre esa tabla y allresources-<acción> por esa acción sobre todas. <realm>-admin da acceso a la gestión de usuarios del realm (/rbac/<realm>).

Los subroles

Un subrol hereda los permisos de su padre: con role("editor", subroles=["lector"]), quien tiene lector recibe también los de editor. No es al revés.

Llevarlo al backend

tai-api rbac show        # qué hay declarado y qué cambiaría, sin tocar nada
tai-api rbac push        # lo aplica

push compara el fichero con lo que hay en la base de datos o en Keycloak y enseña los cambios antes de aplicarlos. Lo que está en el backend y no en el fichero, se borra. Antes de borrar algo que tiene usuarios —un rol asignado, una regla de RLS con valores— avisa y pregunta, con la respuesta por defecto en «no».

Después de cambiar el fichero

rbac push lleva los permisos y los roles al backend. Las reglas de RLS, además, se escriben en el código de la API: un cambio en rls() necesita también tai-api generate.

En vez de escribir los nombres a mano, se pueden usar las constantes de rbac/tables/<realm>/, que generate regenera a partir de las tablas de source():

from rbac.tables.main import AllResources, Publicacion

role(name="lector", api_permissions=[Publicacion.READ, AllResources.READ])

Se importan por su nombre, no con *. Una tabla recién añadida al schema no tiene constante hasta el siguiente tai-api generate, y hasta entonces cargar el fichero da un error que lo dice.

Los endpoints de RBAC

Para gestionar los usuarios de un realm desde una aplicación. Los puede llamar quien tenga <realm>-admin, sudo o allrealms-admin; con <realm>-admin, solo en su propio realm.

Método Ruta Qué hace
POST /rbac/{realm}/user Da de alta: { username, password, first_name?, last_name?, email?, is_active, roles, "rls.<schema>.<tabla>.<columna>": [...] }
GET /rbac/{realm}/user Lista, por nombre y paginada (meta.pagination): ?limit=100&offset=0&is_active&username&role. Sin limit, los 100 primeros
GET · PATCH · DELETE /rbac/{realm}/user/{username} Lee, cambia o borra un usuario
POST /rbac/{realm}/user/{username}/role Da un rol: { "role_name": "editor" }
DELETE /rbac/{realm}/user/{username}/role/{role_name} Quita un rol, sin cuerpo
DELETE /rbac/{realm}/user/{username}/role Quita un rol con { "role_name": … } en el cuerpo. Se conserva para quien ya la usa; hay proxies y clientes que descartan el cuerpo de un DELETE, así que lo nuevo usa la de arriba
GET /rbac/{realm}/role Los roles, con sus permisos y su padre
GET /rbac/{realm}/rls Las reglas de RLS que se pueden asignar

Desactivar a un usuario o cambiarle la contraseña cierra sus sesiones.

Un permiso de aplicación no abre /rbac: una pantalla de gestión de usuarios que se enseña con gestion_usuarios-access y llama a la API recibe un 403 si el usuario no tiene además <realm>-admin. En el cliente TypeScript están api.rbac(realm) y canAdminRealm(user, realm), que sigue la regla de la API.

En el frontend

GET /auth/me —y la respuesta del login— trae los permisos del usuario, los de API y los de aplicación. El cliente TypeScript tiene session.can(tabla, acción) con la misma regla. Sirven para ocultar, nunca para proteger.