7
0
forked from yvv/decision

Compartimentation : isolation stricte des données par espace de travail
ci/woodpecker/push/woodpecker Pipeline was successful

- Ajout clause else IS NULL sur tous les endpoints list (protocols, decisions,
  mandates, documents, groups, votes) — sans X-Organization → données globales
  seulement, jamais le contenu d'un autre espace
- _get_protocol/_get_decision/_get_mandate : org_id propagé à tous les
  endpoints GET/PUT/DELETE/advance/assign/revoke/steps → 404 si UUID d'un
  autre espace
- votes.py : list_vote_sessions filtre via JOIN VotingProtocol.organization_id
- groups.py : suppression _org_id_from_header() mort, create_group assigne
  organization_id correctement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-04-26 18:52:16 +02:00
parent f56d84e76b
commit 59fff64f9e
6 changed files with 96 additions and 45 deletions
+18 -7
View File
@@ -34,13 +34,20 @@ router = APIRouter()
# ── Helpers ─────────────────────────────────────────────────────────────────
async def _get_protocol(db: AsyncSession, protocol_id: uuid.UUID) -> VotingProtocol:
"""Fetch a voting protocol by ID with its formula config, or raise 404."""
result = await db.execute(
async def _get_protocol(
db: AsyncSession, protocol_id: uuid.UUID, org_id: uuid.UUID | None = None
) -> VotingProtocol:
"""Fetch a voting protocol by ID within the active org scope, or raise 404."""
stmt = (
select(VotingProtocol)
.options(selectinload(VotingProtocol.formula_config))
.where(VotingProtocol.id == protocol_id)
)
if org_id is not None:
stmt = stmt.where(VotingProtocol.organization_id == org_id)
else:
stmt = stmt.where(VotingProtocol.organization_id.is_(None))
result = await db.execute(stmt)
protocol = result.scalar_one_or_none()
if protocol is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Protocole introuvable")
@@ -74,6 +81,8 @@ async def list_protocols(
if org_id is not None:
stmt = stmt.where(VotingProtocol.organization_id == org_id)
else:
stmt = stmt.where(VotingProtocol.organization_id.is_(None))
if vote_type is not None:
stmt = stmt.where(VotingProtocol.vote_type == vote_type)
@@ -111,7 +120,7 @@ async def create_protocol(
await db.refresh(protocol)
# Reload with formula config
protocol = await _get_protocol(db, protocol.id)
protocol = await _get_protocol(db, protocol.id, org_id)
return VotingProtocolOut.model_validate(protocol)
@@ -119,9 +128,10 @@ async def create_protocol(
async def get_protocol(
id: uuid.UUID,
db: AsyncSession = Depends(get_db),
org_id: uuid.UUID | None = Depends(get_active_org_id),
) -> VotingProtocolOut:
"""Get a single voting protocol with its formula configuration."""
protocol = await _get_protocol(db, id)
protocol = await _get_protocol(db, id, org_id)
return VotingProtocolOut.model_validate(protocol)
@@ -131,12 +141,13 @@ async def update_protocol(
payload: VotingProtocolUpdate,
db: AsyncSession = Depends(get_db),
identity: DuniterIdentity = Depends(get_current_identity),
org_id: uuid.UUID | None = Depends(get_active_org_id),
) -> VotingProtocolOut:
"""Update a voting protocol (meta-governance).
Only provided fields will be updated. Requires authentication.
"""
protocol = await _get_protocol(db, id)
protocol = await _get_protocol(db, id, org_id)
update_data = payload.model_dump(exclude_unset=True)
for field, value in update_data.items():
@@ -145,7 +156,7 @@ async def update_protocol(
await db.commit()
# Reload with formula config
protocol = await _get_protocol(db, protocol.id)
protocol = await _get_protocol(db, protocol.id, org_id)
return VotingProtocolOut.model_validate(protocol)