feat(auth): brahman-auth — autenticación del escritorio (PAM + mock)
Base del DM/greeter de carmen. Contrato Authenticator agnóstico: authenticate(usuario, secreto) -> UserInfo (uid/gid/home/shell). PamAuthenticator verifica contra PAM (/etc/pam.d/carmen); MockAuthenticator con credenciales en memoria para tests. AuthError grueso: BadCredentials vs AccountUnavailable, sin filtrar existencia de cuentas. resolve_user vía getpwnam. data/carmen como servicio PAM; ejemplo auth-probe. 11 tests; el camino PAM real se ejercita. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
//! Resolución de la identidad de un usuario del sistema.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::AuthError;
|
||||
|
||||
/// Identidad de un usuario en el sistema: lo que el compositor necesita
|
||||
/// para arrancar una sesión — fijar uid/gid, `cd` al home, ejecutar el
|
||||
/// shell o la sesión de escritorio.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UserInfo {
|
||||
/// Nombre de login.
|
||||
pub name: String,
|
||||
/// User ID.
|
||||
pub uid: u32,
|
||||
/// Group ID primario.
|
||||
pub gid: u32,
|
||||
/// Directorio personal.
|
||||
pub home: PathBuf,
|
||||
/// Shell de login.
|
||||
pub shell: PathBuf,
|
||||
}
|
||||
|
||||
impl UserInfo {
|
||||
/// Identidad sintética para tests y para cajas donde el usuario no
|
||||
/// está en `/etc/passwd`. **No** representa a un usuario real del SO
|
||||
/// — no usar para fijar privilegios de un proceso real.
|
||||
pub fn synthetic(name: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
uid: 1000,
|
||||
gid: 1000,
|
||||
home: PathBuf::from(format!("/home/{name}")),
|
||||
shell: PathBuf::from("/bin/sh"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resuelve un usuario por nombre vía `getpwnam`. `Err` si no existe o
|
||||
/// si la consulta a `/etc/passwd` (o NSS) falla.
|
||||
pub fn resolve_user(name: &str) -> Result<UserInfo, AuthError> {
|
||||
match nix::unistd::User::from_name(name) {
|
||||
Ok(Some(u)) => Ok(UserInfo {
|
||||
name: u.name,
|
||||
uid: u.uid.as_raw(),
|
||||
gid: u.gid.as_raw(),
|
||||
home: u.dir,
|
||||
shell: u.shell,
|
||||
}),
|
||||
Ok(None) => Err(AuthError::UnresolvedUser(name.to_string())),
|
||||
Err(e) => Err(AuthError::Pam(format!("getpwnam({name}): {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn resolves_root() {
|
||||
// root (uid 0) existe en todo sistema Unix.
|
||||
let info = resolve_user("root").expect("root debe existir");
|
||||
assert_eq!(info.uid, 0);
|
||||
assert_eq!(info.name, "root");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_user_errs() {
|
||||
let r = resolve_user("usuario-que-no-existe-xyzzy");
|
||||
assert!(matches!(r, Err(AuthError::UnresolvedUser(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_has_home_under_slash_home() {
|
||||
let info = UserInfo::synthetic("prueba");
|
||||
assert_eq!(info.home, PathBuf::from("/home/prueba"));
|
||||
assert_eq!(info.uid, 1000);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user