mannequin/src/auth/users.rs
use std::collections::HashMap;
use std::sync::Mutex;
use crate::oidc::OidcService;
use crate::pki::PkiService;
/// How a user authenticated.
#[derive(Debug, Clone, PartialEq)]
pub enum AuthMethod {
Oidc,
Mtls,
}
/// A resolved identity after successful auth.
#[derive(Debug, Clone)]
pub struct Identity {
pub subject: String,
pub method: AuthMethod,
pub roles: Vec<String>,
}
/// Fake user database + auth validation for testing.
/// Ties together OIDC tokens and client certs to identities.
pub struct AuthService {
users: Mutex<HashMap<String, UserEntry>>,
}
#[derive(Clone)]
struct UserEntry {
roles: Vec<String>,
}
impl Default for AuthService {
fn default() -> Self {
Self::new()
}
}
impl AuthService {
pub fn new() -> Self {
let mut users = HashMap::new();
// Seed some test users
users.insert(
"admin".into(),
UserEntry {
roles: vec!["admin".into(), "reader".into(), "writer".into()],
},
);
users.insert(
"reader".into(),
UserEntry {
roles: vec!["reader".into()],
},
);
users.insert(
"writer".into(),
UserEntry {
roles: vec!["reader".into(), "writer".into()],
},
);
users.insert(
"ci-agent".into(),
UserEntry {
roles: vec!["reader".into(), "writer".into()],
},
);
Self {
users: Mutex::new(users),
}
}
/// Validate a Bearer token via OIDC service, return identity.
pub fn validate_oidc_token(&self, token: &str, oidc: &OidcService) -> Option<Identity> {
let subject = oidc.validate_token(token)?;
let users = self.users.lock().unwrap();
let roles = users
.get(&subject)
.map(|u| u.roles.clone())
.unwrap_or_else(|| vec!["reader".into()]); // unknown users get reader
Some(Identity {
subject,
method: AuthMethod::Oidc,
roles,
})
}
/// Validate a client certificate CN, return identity.
pub fn validate_client_cert(&self, cn: &str) -> Option<Identity> {
let users = self.users.lock().unwrap();
let roles = users
.get(cn)
.map(|u| u.roles.clone())
.unwrap_or_else(|| vec!["reader".into()]);
Some(Identity {
subject: cn.to_string(),
method: AuthMethod::Mtls,
roles,
})
}
/// Issue credentials for a test user (token + client cert).
pub fn issue_credentials(
&self,
username: &str,
oidc: &OidcService,
pki: &PkiService,
) -> anyhow::Result<UserCredentials> {
let token_resp = oidc.issue_token(username);
let client_cert = pki.issue_client_cert(username)?;
Ok(UserCredentials {
username: username.to_string(),
access_token: token_resp.access_token,
client_cert_pem: client_cert.cert_pem,
client_key_pem: client_cert.key_pem,
})
}
pub fn list_users(&self) -> Vec<String> {
self.users.lock().unwrap().keys().cloned().collect()
}
pub fn add_user(&self, name: &str, roles: Vec<String>) {
self.users
.lock()
.unwrap()
.insert(name.to_string(), UserEntry { roles });
}
}
/// Credentials bundle for a test user.
#[derive(Debug, Clone)]
pub struct UserCredentials {
pub username: String,
pub access_token: String,
pub client_cert_pem: String,
pub client_key_pem: String,
}