Files
leocrm/app/services/role_service.py
T
leocrm-bot 3ab4925783 T01: core infrastructure + auth + multi-tenant + RLS
- 10 models: tenants, users, user_tenants, roles, sessions, audit_log, deletion_log, notifications, password_reset_tokens, api_tokens
- Session-based auth (Redis + PostgreSQL audit trail)
- Multi-tenant with ORM-level filtering + PostgreSQL RLS (set_config)
- RBAC with roles/permissions + field-level permissions
- CSRF protection via Origin header validation
- Auth rate limiting (Redis counters with TTL)
- CORS with explicit origins (no wildcard)
- Health endpoint (no auth required)
- Notification service + audit log middleware
- 29 tests, 26 ACs, all passing
- Coverage: 62% (infrastructure modules pending coverage in later tasks)
2026-06-29 08:02:14 +02:00

101 lines
2.7 KiB
Python

"""Role management service."""
from __future__ import annotations
import uuid
from typing import Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.role import Role
class RoleService:
"""Handles role CRUD operations."""
async def list_roles(
self,
db: AsyncSession,
tenant_id: uuid.UUID,
) -> list[dict[str, Any]]:
"""List all roles in a tenant."""
q = select(Role).where(Role.tenant_id == tenant_id)
result = await db.execute(q)
roles = result.scalars().all()
return [self._role_to_dict(r) for r in roles]
async def create_role(
self,
db: AsyncSession,
tenant_id: uuid.UUID,
name: str,
permissions: dict[str, Any],
field_permissions: dict[str, Any] | None = None,
) -> Role:
"""Create a new custom role."""
role = Role(
tenant_id=tenant_id,
name=name,
permissions=permissions,
field_permissions=field_permissions or {},
)
db.add(role)
await db.flush()
return role
async def update_role(
self,
db: AsyncSession,
tenant_id: uuid.UUID,
role_id: uuid.UUID,
name: str | None = None,
permissions: dict[str, Any] | None = None,
field_permissions: dict[str, Any] | None = None,
) -> Role | None:
"""Update a role."""
q = select(Role).where(Role.id == role_id, Role.tenant_id == tenant_id)
result = await db.execute(q)
role = result.scalar_one_or_none()
if role is None:
return None
if name is not None:
role.name = name
if permissions is not None:
role.permissions = permissions
if field_permissions is not None:
role.field_permissions = field_permissions
await db.flush()
return role
async def delete_role(
self,
db: AsyncSession,
tenant_id: uuid.UUID,
role_id: uuid.UUID,
) -> bool:
"""Delete a role."""
q = select(Role).where(Role.id == role_id, Role.tenant_id == tenant_id)
result = await db.execute(q)
role = result.scalar_one_or_none()
if role is None:
return False
await db.delete(role)
await db.flush()
return True
def _role_to_dict(self, role: Role) -> dict[str, Any]:
"""Convert role to response dict."""
return {
"id": str(role.id),
"name": role.name,
"permissions": role.permissions,
"field_permissions": role.field_permissions,
}
role_service = RoleService()