Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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,
}