tawasuyu

tawasuyu/brahman

Rust 506TOML 198Markdown 56JSON 197212 Definitions

⎇ bb21c28eb158d264c7b03f48fba731a75c1648f0 crates/protocol/brahman-auth/src/ticket.rs Rust · 139 lines · 4.5 KB History Who touched what raw

feat(mirada): mirada-greeter — greeter de login del escritorio carmen bb21c28eb · sergio ·

1//! El tiquet de sesión: lo que el greeter le entrega al compositor tras
2//! una autenticación exitosa.
3//!
4//! El greeter de carmen corre como proceso hijo del compositor. Cuando
5//! el login tiene éxito, imprime **una línea** de tiquet a su stdout; el
6//! compositor escanea las líneas del hijo buscando el prefijo
7//! [`TICKET_TAG`] y, al encontrarlo, hace el traspaso a modo sesión.
8
9use std::path::PathBuf;
10
11use crate::UserInfo;
12
13/// Etiqueta + versión de la línea de tiquet. El compositor sólo trata
14/// como tiquet las líneas que empiezan con esto — el resto del stdout
15/// del greeter (logs, ruido) se ignora.
16pub const TICKET_TAG: &str = "MIRADA-SESSION-TICKET-v1";
17
18/// Resultado de un login: la identidad autenticada más, opcionalmente,
19/// el comando de sesión elegido. El greeter lo produce; el compositor lo
20/// consume para arrancar la sesión (setuid al usuario + spawn).
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct SessionTicket {
23    /// Identidad del usuario autenticado.
24    pub user: UserInfo,
25    /// Comando de sesión a ejecutar como el usuario. Vacío = que el
26    /// compositor decida (su autostart por defecto).
27    pub session: String,
28}
29
30impl SessionTicket {
31    /// Crea un tiquet sin comando de sesión explícito.
32    pub fn new(user: UserInfo) -> Self {
33        Self {
34            user,
35            session: String::new(),
36        }
37    }
38
39    /// Fija el comando de sesión. Encadenable.
40    pub fn with_session(mut self, session: impl Into<String>) -> Self {
41        self.session = session.into();
42        self
43    }
44
45    /// Serializa el tiquet a una línea única, apta para stdout. Campos
46    /// separados por tabulador: ni los nombres de usuario, ni los paths,
47    /// ni los comandos de sesión suelen contener tabuladores.
48    pub fn to_line(&self) -> String {
49        format!(
50            "{TICKET_TAG}\t{}\t{}\t{}\t{}\t{}\t{}",
51            self.user.name,
52            self.user.uid,
53            self.user.gid,
54            self.user.home.display(),
55            self.user.shell.display(),
56            self.session,
57        )
58    }
59
60    /// Parsea una línea producida por [`to_line`]. `None` si la línea no
61    /// es un tiquet (otra salida del greeter) o está malformada.
62    pub fn from_line(line: &str) -> Option<SessionTicket> {
63        let mut f = line.trim_end_matches(['\r', '\n']).split('\t');
64        if f.next()? != TICKET_TAG {
65            return None;
66        }
67        let name = f.next()?.to_string();
68        let uid = f.next()?.parse().ok()?;
69        let gid = f.next()?.parse().ok()?;
70        let home = PathBuf::from(f.next()?);
71        let shell = PathBuf::from(f.next()?);
72        // El comando de sesión puede venir vacío.
73        let session = f.next().unwrap_or("").to_string();
74        Some(SessionTicket {
75            user: UserInfo {
76                name,
77                uid,
78                gid,
79                home,
80                shell,
81            },
82            session,
83        })
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    fn sample() -> UserInfo {
92        UserInfo {
93            name: "sergio".into(),
94            uid: 1000,
95            gid: 1000,
96            home: PathBuf::from("/home/sergio"),
97            shell: PathBuf::from("/usr/bin/bash"),
98        }
99    }
100
101    #[test]
102    fn round_trip_without_session() {
103        let t = SessionTicket::new(sample());
104        let back = SessionTicket::from_line(&t.to_line()).expect("parsea");
105        assert_eq!(back, t);
106        assert!(back.session.is_empty());
107    }
108
109    #[test]
110    fn round_trip_with_session() {
111        let t = SessionTicket::new(sample()).with_session("shuma-shell --launcher");
112        let back = SessionTicket::from_line(&t.to_line()).expect("parsea");
113        assert_eq!(back, t);
114        assert_eq!(back.session, "shuma-shell --launcher");
115    }
116
117    #[test]
118    fn from_line_ignores_non_ticket() {
119        assert!(SessionTicket::from_line("[INFO] arrancando greeter").is_none());
120        assert!(SessionTicket::from_line("").is_none());
121    }
122
123    #[test]
124    fn from_line_rejects_malformed() {
125        // Prefijo correcto pero faltan campos.
126        assert!(SessionTicket::from_line(&format!("{TICKET_TAG}\tsergio")).is_none());
127        // uid no numérico.
128        assert!(
129            SessionTicket::from_line(&format!("{TICKET_TAG}\tsergio\tXX\t1000\t/h\t/sh\t"))
130                .is_none()
131        );
132    }
133
134    #[test]
135    fn tolerates_trailing_newline() {
136        let line = format!("{}\n", SessionTicket::new(sample()).to_line());
137        assert!(SessionTicket::from_line(&line).is_some());
138    }
139}