From c20fc9149b81feb4c5d7286dc5d0ace3ecb80374 Mon Sep 17 00:00:00 2001 From: vegard Date: Tue, 17 Mar 2026 14:51:30 +0100 Subject: [PATCH] =?UTF-8?q?Fullf=C3=B8r=20oppgave=204.1:=20recompute=5Facc?= =?UTF-8?q?ess=20ved=20edge-endring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Når en tilgangsgivende edge (owner, admin, member_of, reader) opprettes, kalles nå recompute_access() i samme PG-transaksjon som edge-insertet. Dette sikrer at node_access-matrisen alltid er oppdatert — ingen vindu med stale tilgang. Implementasjon: - edge_type_to_access_level() mapper edge-typer til access_level enum - insert_edge_with_access() wrapper edge-insert + recompute_access i tx - Vanlige edges (belongs_to, mentions, etc.) skrives som før (fire-and-forget) Verifisert med SQL-tester: direkte tilgang + transitiv tilgang via belongs_to-edges fungerer korrekt. Co-Authored-By: Claude Opus 4.6 --- maskinrommet/src/intentions.rs | 126 +++++++++++++++++++++++++++------ tasks.md | 3 +- 2 files changed, 107 insertions(+), 22 deletions(-) diff --git a/maskinrommet/src/intentions.rs b/maskinrommet/src/intentions.rs index ee4a962..4e3473f 100644 --- a/maskinrommet/src/intentions.rs +++ b/maskinrommet/src/intentions.rs @@ -554,7 +554,21 @@ fn spawn_pg_insert_node( }); } +/// Mapper edge_type til access_level for tilgangsgivende edges. +/// Returnerer None for edges som ikke gir tilgang. +fn edge_type_to_access_level(edge_type: &str) -> Option<&'static str> { + match edge_type { + "owner" => Some("owner"), + "admin" => Some("admin"), + "member_of" => Some("member"), + "reader" => Some("reader"), + _ => None, + } +} + /// Spawner en tokio-task som skriver edgen til PostgreSQL i bakgrunnen. +/// For tilgangsgivende edges (owner, admin, member_of, reader) kalles +/// recompute_access i samme transaksjon — ingen vindu med stale tilgang. fn spawn_pg_insert_edge( db: PgPool, edge_id: Uuid, @@ -566,33 +580,105 @@ fn spawn_pg_insert_edge( created_by: Uuid, ) { tokio::spawn(async move { - let result = sqlx::query( - r#" - INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by) - VALUES ($1, $2, $3, $4, $5, $6, $7) - "#, - ) - .bind(edge_id) - .bind(source_id) - .bind(target_id) - .bind(&edge_type) - .bind(&metadata) - .bind(system) - .bind(created_by) - .execute(&db) - .await; + let access_level = edge_type_to_access_level(&edge_type); - match result { - Ok(_) => { - tracing::info!(edge_id = %edge_id, "Edge persistert til PostgreSQL"); + if let Some(level) = access_level { + // Tilgangsgivende edge: wrap i transaksjon med recompute_access + let result = insert_edge_with_access(&db, edge_id, source_id, target_id, &edge_type, &metadata, system, created_by, level).await; + match result { + Ok(_) => { + tracing::info!( + edge_id = %edge_id, + edge_type = %edge_type, + access_level = %level, + "Edge + node_access persistert til PostgreSQL" + ); + } + Err(e) => { + tracing::error!( + edge_id = %edge_id, + error = %e, + "Kunne ikke persistere edge + node_access til PostgreSQL" + ); + } } - Err(e) => { - tracing::error!(edge_id = %edge_id, error = %e, "Kunne ikke persistere edge til PostgreSQL"); + } else { + // Vanlig edge uten tilgangspåvirkning + let result = sqlx::query( + r#" + INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by) + VALUES ($1, $2, $3, $4, $5, $6, $7) + "#, + ) + .bind(edge_id) + .bind(source_id) + .bind(target_id) + .bind(&edge_type) + .bind(&metadata) + .bind(system) + .bind(created_by) + .execute(&db) + .await; + + match result { + Ok(_) => { + tracing::info!(edge_id = %edge_id, "Edge persistert til PostgreSQL"); + } + Err(e) => { + tracing::error!(edge_id = %edge_id, error = %e, "Kunne ikke persistere edge til PostgreSQL"); + } } } }); } +/// Inserter en tilgangsgivende edge og oppdaterer node_access i én transaksjon. +/// source_id = subject (bruker/team), target_id = object (noden det gis tilgang til). +async fn insert_edge_with_access( + db: &PgPool, + edge_id: Uuid, + source_id: Uuid, + target_id: Uuid, + edge_type: &str, + metadata: &serde_json::Value, + system: bool, + created_by: Uuid, + access_level: &str, +) -> Result<(), sqlx::Error> { + let mut tx = db.begin().await?; + + sqlx::query( + r#" + INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by) + VALUES ($1, $2, $3, $4, $5, $6, $7) + "#, + ) + .bind(edge_id) + .bind(source_id) + .bind(target_id) + .bind(edge_type) + .bind(metadata) + .bind(system) + .bind(created_by) + .execute(&mut *tx) + .await?; + + // Kall recompute_access: subject=source_id, object=target_id + sqlx::query( + "SELECT recompute_access($1, $2, $3::access_level, $4)", + ) + .bind(source_id) + .bind(target_id) + .bind(access_level) + .bind(edge_id) + .execute(&mut *tx) + .await?; + + tx.commit().await?; + + Ok(()) +} + /// Spawner en tokio-task som oppdaterer noden i PostgreSQL. fn spawn_pg_update_node( db: PgPool, diff --git a/tasks.md b/tasks.md index b443ca0..74fd840 100644 --- a/tasks.md +++ b/tasks.md @@ -71,8 +71,7 @@ Uavhengige faser kan fortsatt plukkes. ## Fase 4: Tilgangskontroll -- [~] 4.1 `recompute_access` i maskinrommet: ved edge-endring, oppdater `node_access`-matrisen. Håndter direkte edges (owner, admin, member, reader). - > Påbegynt: 2026-03-17T14:45 +- [x] 4.1 `recompute_access` i maskinrommet: ved edge-endring, oppdater `node_access`-matrisen. Håndter direkte edges (owner, admin, member, reader). - [ ] 4.2 Team-transitivitet: member_of-edge til team → arv tilgang fra teamets edges. - [ ] 4.3 Visibility-filtrering: STDB-spørringer respekterer visibility-enum. Frontend ser bare noder brukeren har tilgang til. - [ ] 4.4 RLS-policies på PG: `node_access`-basert filtrering for tunge spørringer.