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

nordisk/gunnar

crates/gunnar-grpc/tests/who_am_i.rs

//! **Who am I, and may I?** — `Access.WhoAmI` and `Access.Check` (owner,
//! 2026-10-04: the console is a thin layer; it calls gunnar AS THE PERSON and
//! draws only the controls gunnar would honour).
//!
//! Until these two existed the console kept its own copy of gunnar's grant
//! rule (`mirror_gate::Standing`, the account-admin rule in
//! `api::visibility`) to decide which buttons to draw, and its own role ladder
//! for whether a session "may write". Both RPCs answer for the CALLER only —
//! there is no principal to name, so neither is a reconnaissance tool — and
//! neither needs any grant beyond being authenticated.
//!
//! `Check` is the same decision the verbs make: the rows below ask it and then
//! ask the verb, and the two must agree.

use std::sync::{Arc, Mutex};

use gunnar_grpc::auth::AuthGate;
use gunnar_grpc::{
    sign_credential_for, AccessService, AuthMode, Caller, ControlPlane, EventBus,
    MemoryControlPlane, ReposService, ServerConfig, ServiceContext, AUDIT_TARGET,
    CREDENTIAL_METADATA_KEY,
};
use gunnar_proto::access_server::Access as _;
use gunnar_proto::repos_server::Repos as _;
use gunnar_proto::*;
use tonic::{Code, Request};
use tracing_subscriber::layer::SubscriberExt;

/// The account: a namespace named after the principal that owns it.
const ACCOUNT: &str = "acme";
const REPO: &str = "app";

fn mint() -> ssh_key::PrivateKey {
    ssh_key::PrivateKey::random(
        &mut rand::rand_core::UnwrapErr(rand::rngs::SysRng),
        ssh_key::Algorithm::Ed25519,
    )
    .expect("mint an ed25519 key")
}

fn now_ms() -> i64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("after 1970")
        .as_millis() as i64
}

struct Harness {
    plane: Arc<MemoryControlPlane>,
    ctx: ServiceContext,
}

impl Harness {
    fn new() -> Harness {
        let plane = Arc::new(MemoryControlPlane::with_repo(ACCOUNT, REPO));
        let control: Arc<dyn ControlPlane> = plane.clone();
        let ctx = ServiceContext::new(
            control,
            Arc::new(EventBus::new(64)),
            ServerConfig::default(),
        );
        assert_eq!(ctx.config.auth, AuthMode::Required, "the shipped default");
        Harness { plane, ctx }
    }

    /// A principal with one key, signed in through the real gate.
    fn person(&self, name: &str, nonce: &str) -> (Caller, ssh_key::PrivateKey) {
        let key = mint();
        self.plane
            .add_principal(name, name, PrincipalKind::User)
            .expect("principal added");
        self.plane
            .add_key(
                name,
                &key.public_key().to_openssh().expect("openssh"),
                name,
                None,
                "",
                None,
            )
            .expect("key registered");
        let credential = sign_credential_for(&key, now_ms(), nonce, "Admin.Health").expect("sign");
        let mut request = Request::new(());
        request.metadata_mut().insert(
            CREDENTIAL_METADATA_KEY,
            credential.to_header_value().parse().expect("ascii"),
        );
        let caller = AuthGate::new(Arc::clone(&self.ctx.control), self.ctx.config.auth)
            .authenticate_bound(request.metadata(), now_ms(), Some("Admin.Health"))
            .expect("a freshly signed credential from a registered key must verify");
        (caller, key)
    }

    fn authed<T>(&self, caller: &Caller, message: T) -> Request<T> {
        let mut request = Request::new(message);
        request.extensions_mut().insert(caller.clone());
        request
    }

    fn access(&self) -> AccessService {
        AccessService::new(self.ctx.clone())
    }

    async fn who(&self, caller: &Caller) -> WhoAmIResponse {
        self.access()
            .who_am_i(self.authed(caller, WhoAmIRequest {}))
            .await
            .expect("an authenticated caller is told who it is")
            .into_inner()
    }

    async fn check(
        &self,
        caller: &Caller,
        namespace: &str,
        repo: &str,
        level: GrantLevel,
    ) -> Result<CheckResponse, tonic::Status> {
        self.access()
            .check(self.authed(
                caller,
                CheckRequest {
                    namespace: namespace.into(),
                    repo: repo.into(),
                    level: level as i32,
                },
            ))
            .await
            .map(|r| r.into_inner())
    }

    async fn set_visibility(&self, caller: &Caller) -> Result<(), tonic::Status> {
        ReposService::new(self.ctx.clone())
            .set_visibility(self.authed(
                caller,
                SetVisibilityRequest {
                    path: Some(RepoPath::new(ACCOUNT, REPO)),
                    visibility: Visibility::Public as i32,
                },
            ))
            .await
            .map(|_| ())
    }
}

/// ★ **WhoAmI names the caller, its key, and the highest server-wide level it
/// holds** — nothing for a person with no server grant.
#[tokio::test]
async fn who_am_i_names_the_caller_its_key_and_its_server_level() {
    let harness = Harness::new();
    let (kim, kim_key) = harness.person("kim", "nonce-who-kim-0001");
    let (boss, _) = harness.person("boss", "nonce-who-boss-001");
    harness.plane.grant_server("boss", GrantLevel::Admin);
    let (ops, _) = harness.person("ops", "nonce-who-ops-0001");
    harness.plane.grant_server("ops", GrantLevel::Write);

    let me = harness.who(&kim).await;
    let principal = me.principal.expect("the caller's principal");
    assert_eq!(principal.id, "kim");
    assert_eq!(principal.name, "kim");
    assert_eq!(
        me.fingerprint,
        kim_key
            .public_key()
            .fingerprint(ssh_key::HashAlg::Sha256)
            .to_string()
    );
    assert_eq!(me.server_level, GrantLevel::Unspecified as i32);
    assert_eq!(
        harness.who(&boss).await.server_level,
        GrantLevel::Admin as i32
    );
    assert_eq!(
        harness.who(&ops).await.server_level,
        GrantLevel::Write as i32
    );
}

/// ★ **WhoAmI names the account the caller acts in and what it is to it**
/// (2026-10-07, the members model: a customer is an ACCOUNT that pays; its
/// members use its space and never buy). A console draws the buying doors
/// from this — for an individual and an account admin, never a member — and
/// a member's storage from the ACCOUNT's room, never a phantom of his own.
#[tokio::test]
async fn who_am_i_names_the_account_and_what_the_caller_is_to_it() {
    let harness = Harness::new();
    let (acme, _) = harness.person(ACCOUNT, "nonce-who-acme-0001");
    harness
        .plane
        .create_namespace("kim", "kim's own")
        .expect("kim's account");
    let (kim, _) = harness.person("kim", "nonce-who-kim-00002");
    // A principal with no namespace and no employer: nothing to buy for.
    let (relay, _) = harness.person("relay", "nonce-who-relay-001");

    // Before acme has any member, it is an individual like kim.
    let me = harness.who(&acme).await;
    assert_eq!(
        (me.account.as_str(), me.account_role.as_str()),
        (ACCOUNT, "individual"),
        "{me:?}"
    );

    harness
        .plane
        .enroll_employee(
            ACCOUNT,
            &gunnar_grpc::EmployeeAsk {
                id: "ben".into(),
                name: "Ben".into(),
                ..Default::default()
            },
        )
        .expect("acme enrols ben");
    let ben_key = mint();
    harness
        .plane
        .add_key(
            "ben",
            &ben_key.public_key().to_openssh().expect("openssh"),
            "ben",
            None,
            "",
            None,
        )
        .expect("ben's key");
    let credential = sign_credential_for(&ben_key, now_ms(), "nonce-who-ben-00001", "Admin.Health")
        .expect("sign");
    let mut request = Request::new(());
    request.metadata_mut().insert(
        CREDENTIAL_METADATA_KEY,
        credential.to_header_value().parse().expect("ascii"),
    );
    let ben = AuthGate::new(Arc::clone(&harness.ctx.control), harness.ctx.config.auth)
        .authenticate_bound(request.metadata(), now_ms(), Some("Admin.Health"))
        .expect("ben's key verifies");

    let admin = harness.who(&acme).await;
    assert_eq!(
        (
            admin.account.as_str(),
            admin.account_role.as_str(),
            admin.account_admin.as_str()
        ),
        (ACCOUNT, "admin", ACCOUNT),
        "an account with a member is its admin's: {admin:?}"
    );
    let member = harness.who(&ben).await;
    assert_eq!(
        (member.account.as_str(), member.account_role.as_str(), member.account_admin.as_str()),
        (ACCOUNT, "employee", ACCOUNT),
        "a member acts in the account that enrolled him, never in a namespace of his own: {member:?}"
    );
    assert_eq!(member.principal.expect("ben").id, "ben");

    // ★ An APPOINTED account admin (owner 2026-10-08): acme gives ben Admin over its namespace.
    // He decides for the account's repositories — he may publish — and WhoAmI says so; the
    // account, and whom it is, stay acme's. He never buys (members_never_buy).
    harness
        .plane
        .grant("ben", ACCOUNT, "", GrantLevel::Admin)
        .expect("acme appoints ben");
    let appointed = harness.who(&ben).await;
    assert_eq!(
        (
            appointed.account.as_str(),
            appointed.account_role.as_str(),
            appointed.account_admin.as_str()
        ),
        (ACCOUNT, "admin-appointed", ACCOUNT),
        "an appointed admin acts for the account that enrolled him: {appointed:?}"
    );
    harness
        .set_visibility(&ben)
        .await
        .expect("an appointed admin publishes for the account");
    let individual = harness.who(&kim).await;
    assert_eq!(
        (
            individual.account.as_str(),
            individual.account_role.as_str(),
            individual.account_admin.as_str()
        ),
        ("kim", "individual", "kim"),
        "{individual:?}"
    );
    let none = harness.who(&relay).await;
    assert_eq!(
        (
            none.account.as_str(),
            none.account_role.as_str(),
            none.account_admin.as_str()
        ),
        ("", "", ""),
        "no namespace and no employer: no account to speak for: {none:?}"
    );
}

/// ★ **Check is the account-admin rule, for the caller, and it agrees with the
/// verb.** The account's owner may set a repository's visibility, and Check
/// says so; a repository Admin who is not the account admin may not, and
/// Check says so in the verb's own kind of sentence — while the same person
/// is allowed what their grant does reach.
#[tokio::test]
async fn check_answers_the_account_admin_rule_and_agrees_with_the_verb() {
    let harness = Harness::new();
    let (owner, _) = harness.person(ACCOUNT, "nonce-check-owner-1");
    let (kim, _) = harness.person("kim", "nonce-check-kim-001");
    harness
        .plane
        .grant("kim", ACCOUNT, REPO, GrantLevel::Admin)
        .expect("kim is admin of one repository");

    let yes = harness
        .check(&owner, ACCOUNT, "", GrantLevel::Admin)
        .await
        .expect("an answer");
    assert!(yes.allowed, "{yes:?}");
    assert!(yes.reason.is_empty(), "{yes:?}");
    harness
        .set_visibility(&owner)
        .await
        .expect("the verb agrees: the owner sets visibility");

    let no = harness
        .check(&kim, ACCOUNT, "", GrantLevel::Admin)
        .await
        .expect("an answer");
    assert!(!no.allowed, "{no:?}");
    assert!(
        no.reason.contains("kim"),
        "the refusal names who was refused: {no:?}"
    );
    let verb = harness
        .set_visibility(&kim)
        .await
        .expect_err("the verb agrees: a repository admin is not the account admin");
    assert_eq!(verb.code(), Code::PermissionDenied);

    let repo = harness
        .check(&kim, ACCOUNT, REPO, GrantLevel::Admin)
        .await
        .expect("an answer");
    assert!(repo.allowed, "kim's grant reaches the repository: {repo:?}");
    let server = harness
        .check(&kim, "", "", GrantLevel::Read)
        .await
        .expect("an answer");
    assert!(!server.allowed, "{server:?}");
}

/// A question gunnar cannot ask is refused as one, not answered `false`.
#[tokio::test]
async fn check_refuses_a_question_it_cannot_ask() {
    let harness = Harness::new();
    let (kim, _) = harness.person("kim", "nonce-check-bad-001");
    let no_namespace = harness
        .check(&kim, "", REPO, GrantLevel::Read)
        .await
        .expect_err("a repository needs its namespace");
    assert_eq!(
        no_namespace.code(),
        Code::InvalidArgument,
        "{no_namespace:?}"
    );
    let no_level = harness
        .check(&kim, ACCOUNT, "", GrantLevel::Unspecified)
        .await
        .expect_err("a level is required");
    assert_eq!(no_level.code(), Code::InvalidArgument, "{no_level:?}");
}

/// **A `false` from Check is not a refused call**: asking "may I?" before
/// drawing a button must not put a `decision="deny"` WARN line in the audit
/// trail an operator greps for real refusals.
#[test]
fn a_check_answered_no_leaves_no_refusal_in_the_audit_trail() {
    #[derive(Clone, Default)]
    struct Log(Arc<Mutex<Vec<String>>>);
    struct Layer(Log);
    impl<S: tracing::Subscriber> tracing_subscriber::Layer<S> for Layer {
        fn on_event(
            &self,
            event: &tracing::Event<'_>,
            _: tracing_subscriber::layer::Context<'_, S>,
        ) {
            if event.metadata().target() != AUDIT_TARGET {
                return;
            }
            struct Visit(String);
            impl tracing::field::Visit for Visit {
                fn record_debug(
                    &mut self,
                    field: &tracing::field::Field,
                    value: &dyn std::fmt::Debug,
                ) {
                    self.0.push_str(&format!("{}={:?} ", field.name(), value));
                }
            }
            let mut visit = Visit(String::new());
            event.record(&mut visit);
            self.0 .0.lock().unwrap().push(visit.0);
        }
    }
    let log = Log::default();
    let subscriber = tracing_subscriber::registry().with(Layer(log.clone()));
    let runtime = tokio::runtime::Builder::new_current_thread()
        .build()
        .expect("runtime");
    tracing::subscriber::with_default(subscriber, || {
        let harness = Harness::new();
        let (kim, _) = harness.person("kim", "nonce-check-audit-1");
        log.0.lock().unwrap().clear();
        let no = runtime
            .block_on(harness.check(&kim, ACCOUNT, "", GrantLevel::Admin))
            .expect("an answer");
        assert!(!no.allowed, "{no:?}");
        let lines = log.0.lock().unwrap().clone();
        assert!(
            !lines.iter().any(|l| l.contains("decision=\"deny\"")),
            "a check wrote a refusal into the audit trail: {lines:#?}"
        );
    });
}