tawasuyu
⎇ 6aee9254d410696bd376e339fef8a51afe60a7ff crates/ente-zero/src/graph/capabilities.rs Rust · 81 líneas · 2.8 KB Historia Quién tocó qué crudo

Tests audit replay, métricas chain, TTL por cap, brain snapshot 6aee9254d · Sergio ·

1//! Mediación de capabilities: emisión, renovación, revocación de tokens.
2//!
3//! Los grants tienen TTL (`DEFAULT_GRANT_TTL`). El cliente debe renovarlos
4//! periódicamente con `renew_grant(token)`; en caso contrario, el background
5//! task `purge_expired_grants` los revoca al vencimiento.
6
7use super::{ttl_for_capability, EnteGraph, GrantedCapability};
8use crate::events::CapabilityGrant;
9use ente_card::Capability;
10use std::time::Instant;
11use tokio::sync::oneshot;
12use tracing::debug;
13use ulid::Ulid;
14
15impl EnteGraph {
16    pub async fn mediate_capability(
17        &mut self,
18        from: Ulid,
19        cap: Capability,
20        reply: oneshot::Sender<CapabilityGrant>,
21    ) {
22        let grant = match self.providers.get(&cap).and_then(|s| s.iter().next().copied()) {
23            None => CapabilityGrant::NoProvider,
24            Some(provider) => {
25                let token = self.next_token;
26                self.next_token += 1;
27                // TTL específico por variante de capability — caps escaladas
28                // (Spawn, FilesystemRoot) viven menos.
29                let ttl = ttl_for_capability(&cap);
30                let expires_at = Instant::now() + ttl;
31                self.grants.insert(token, GrantedCapability {
32                    cap: cap.clone(),
33                    provider,
34                    holder: from,
35                    expires_at,
36                });
37                CapabilityGrant::Granted { token }
38            }
39        };
40        let _ = reply.send(grant);
41    }
42
43    /// Extiende un grant existente. Devuelve `true` si renovó. Si el token
44    /// no existe o ya expiró, `false` (el cliente debe re-acquire).
45    /// Usa el TTL específico de la cap del grant.
46    pub fn renew_grant(&mut self, token: u64) -> bool {
47        let now = Instant::now();
48        if let Some(g) = self.grants.get_mut(&token) {
49            if g.expires_at > now {
50                g.expires_at = now + ttl_for_capability(&g.cap);
51                return true;
52            }
53            // Expired — purgamos aquí mismo.
54            self.grants.remove(&token);
55        }
56        false
57    }
58
59    /// GC: elimina grants vencidos. Devuelve cuántos fueron purgados.
60    pub fn purge_expired_grants(&mut self) -> usize {
61        let now = Instant::now();
62        let expired: Vec<u64> = self.grants.iter()
63            .filter(|(_, g)| g.expires_at <= now)
64            .map(|(t, _)| *t)
65            .collect();
66        for t in &expired {
67            self.grants.remove(t);
68        }
69        if !expired.is_empty() {
70            debug!(count = expired.len(), "grants expirados purgados");
71        }
72        expired.len()
73    }
74
75    /// Cuenta de grants vivos (no expirados). Usado por métricas.
76    pub fn active_grants_count(&self) -> usize {
77        let now = Instant::now();
78        self.grants.values().filter(|g| g.expires_at > now).count()
79    }
80}
81