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¶
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.