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/operator_claim.rs

//! **`Accounts.ClaimOperator` — the account the first admin's passkey IS**
//! (owner, 2026-10-04: "a YubiKey is used to register me as an admin user. I
//! can THEN select to ADD ssh keys to it" / "The UI shall not DO anything such
//! as create a user — it shall CALL gunnar-server").
//!
//! MEASURED on test2 the same morning: the console's first-admin registration
//! called `Access.AddPrincipal "rickard"` and gunnar refused it — "cannot create
//! the account namespace "rickard" for this user: namespace already exists" —
//! because a first push with the installer's admin key (`rickard/t2-scratch`)
//! had conjured that namespace with no owner. The passkey bound to nothing, and
//! every `/api/account/*` page answered 404. These rows drive the verb on the
//! wire, with authentication ON, on the test double; the fed name and the real
//! catalog are `gunnar-server`'s rows (`operator_claim.rs` there).

use std::sync::Arc;

use gunnar_grpc::{
    AccountsService, Caller, ControlPlane, EventBus, Identity as _, MemoryControlPlane,
    ServerConfig, ServiceContext,
};
use gunnar_proto::accounts_server::Accounts as _;
use gunnar_proto::*;
use tonic::{Code, Request};

const OPERATOR: &str = "rickard";

fn caller(id: &str) -> Caller {
    Caller {
        principal_id: id.to_owned(),
        fingerprint: format!("SHA256:{id}"),
        machine: false,
        authenticated: true,
    }
}

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

struct Rig {
    plane: Arc<MemoryControlPlane>,
    accounts: AccountsService,
    /// The console's own principal: a machine, server-wide Admin.
    console: Caller,
}

impl Rig {
    fn new() -> Rig {
        let plane = Arc::new(MemoryControlPlane::default());
        plane
            .add_principal("gunnar-ui", "gunnar-ui", PrincipalKind::Machine)
            .expect("the console's principal");
        plane.grant_server("gunnar-ui", GrantLevel::Admin);
        let control: Arc<dyn ControlPlane> = plane.clone();
        let ctx = ServiceContext::new(
            control,
            Arc::new(EventBus::new(16)),
            ServerConfig::default(),
        );
        Rig {
            accounts: AccountsService::new(ctx),
            plane,
            console: caller("gunnar-ui"),
        }
    }

    async fn claim(
        &self,
        who: &Caller,
        name: &str,
    ) -> Result<ClaimOperatorResponse, tonic::Status> {
        self.accounts
            .claim_operator(with(
                who,
                ClaimOperatorRequest {
                    name: name.into(),
                    ..Default::default()
                },
            ))
            .await
            .map(|r| r.into_inner())
    }
}

/// ★ **The first claim MAKES the operator: a person, named after the account,
/// the account's namespace with it, gunnar-admin — and no key.** The console's
/// own credential is then the principal's first key, which gunnar takes with no
/// attestation, and the person's SSH keys come after it.
#[tokio::test]
async fn the_first_claim_makes_the_operator_a_keyless_gunnar_admin_with_its_account() {
    let rig = Rig::new();
    let claimed = rig
        .claim(&rig.console, OPERATOR)
        .await
        .expect("the console may claim");
    let principal = claimed.principal.expect("the operator comes back");
    assert_eq!(principal.id, OPERATOR);
    assert_eq!(principal.name, OPERATOR);
    assert_eq!(
        principal.kind,
        PrincipalKind::User as i32,
        "the operator is a person"
    );
    assert!(claimed.created, "this call made it");
    assert!(
        !claimed.adopted_namespace,
        "there was no namespace to adopt"
    );
    assert_eq!(claimed.named_by, "asked", "the double keeps no feed");

    assert!(
        rig.plane.namespace(OPERATOR).expect("ask").is_some(),
        "the operator's account namespace was not made with it"
    );
    assert!(
        rig.plane
            .may(
                OPERATOR,
                &gunnar_grpc::auth::Access::Server,
                GrantLevel::Admin
            )
            .expect("ask"),
        "the operator is the estate's admin"
    );
    assert!(
        rig.plane
            .list_keys(Some(OPERATOR))
            .expect("keys")
            .is_empty(),
        "a key on the operator before the console's would make the console's owe an attestation"
    );
}

/// ★ **The test2 morning: an ownerless namespace of the operator's name is
/// ADOPTED, not refused.** Before this the first admin's passkey bound to
/// nothing, for ever.
#[tokio::test]
async fn an_ownerless_namespace_of_the_operators_name_is_adopted_not_refused() {
    let rig = Rig::new();
    rig.plane
        .create_namespace(OPERATOR, "conjured by a first push")
        .expect("the namespace a first push conjured");
    let claimed = rig
        .claim(&rig.console, OPERATOR)
        .await
        .expect("the claim succeeds");
    assert!(claimed.created);
    assert!(
        claimed.adopted_namespace,
        "the conjured namespace was not adopted"
    );
    assert_eq!(claimed.principal.expect("principal").id, OPERATOR);
}

/// **Idempotent**: a second claim answers the same row and makes nothing.
#[tokio::test]
async fn a_second_claim_answers_the_same_operator_and_makes_nothing() {
    let rig = Rig::new();
    let first = rig.claim(&rig.console, OPERATOR).await.expect("first");
    let again = rig.claim(&rig.console, OPERATOR).await.expect("again");
    assert!(!again.created);
    assert!(!again.adopted_namespace);
    assert_eq!(
        first.principal, again.principal,
        "the pin is to the same row and stamp"
    );
}

/// **Only a server Admin claims.** The account a person owns intrinsically is
/// not enough, and nobody unauthenticated gets near it.
#[tokio::test]
async fn only_a_server_admin_may_claim_the_operator() {
    let rig = Rig::new();
    rig.plane
        .add_principal("anna", "anna", PrincipalKind::User)
        .expect("anna");
    rig.plane
        .create_namespace("anna", "")
        .expect("anna's account");
    let refused = rig
        .claim(&caller("anna"), OPERATOR)
        .await
        .expect_err("anna is not the estate's admin");
    assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
    let refused = rig
        .accounts
        .claim_operator(Request::new(ClaimOperatorRequest {
            name: OPERATOR.into(),
            ..Default::default()
        }))
        .await
        .expect_err("no caller at all");
    assert_eq!(refused.code(), Code::Unauthenticated, "{refused:?}");
    assert!(
        rig.plane
            .principal_by_name(OPERATOR)
            .expect("ask")
            .is_none(),
        "a refused claim made something"
    );
}

/// **A machine is never the operator**, and no name at all is no claim.
#[tokio::test]
async fn a_machine_under_the_name_or_no_name_at_all_is_a_precondition() {
    let rig = Rig::new();
    rig.plane
        .add_principal("monetize", "monetize", PrincipalKind::Machine)
        .expect("monetize");
    let refused = rig
        .claim(&rig.console, "monetize")
        .await
        .expect_err("a machine");
    assert_eq!(refused.code(), Code::FailedPrecondition, "{refused:?}");
    let refused = rig.claim(&rig.console, "  ").await.expect_err("nobody");
    assert_eq!(refused.code(), Code::FailedPrecondition, "{refused:?}");
}

/// The one rule both planes keep for WHICH name: the fed one wins.
#[test]
fn the_fed_name_wins_over_the_asked_one() {
    use gunnar_grpc::seam::operator_name;
    assert_eq!(
        operator_name(Some("rickard"), "anna").unwrap(),
        ("rickard", "fed")
    );
    assert_eq!(
        operator_name(Some("  "), "anna").unwrap(),
        ("anna", "asked")
    );
    assert_eq!(operator_name(None, " anna ").unwrap(), ("anna", "asked"));
    assert!(operator_name(None, "").is_err());
}