use std::collections::HashSet;
use mcpmesh_net::EndpointId;
use mcpmesh_trust::roster::validate::RosterView;
use crate::allowlist::PeerStore;
pub(crate) fn endpoints_for_principal(
store: &PeerStore,
roster: Option<&RosterView>,
principal: &str,
) -> anyhow::Result<HashSet<EndpointId>> {
let mut out = HashSet::new();
for entry in store.list()? {
let eid = EndpointId::from_bytes(entry.endpoint_id);
if eid.principal() == principal || entry.user_id.as_deref() == Some(principal) {
out.insert(eid);
}
}
if let Some(view) = roster {
for (bytes, device) in view.devices() {
let eid = EndpointId::from_bytes(*bytes);
if eid.principal() == principal
|| device.user_id == principal
|| device.groups.iter().any(|g| g == principal)
{
out.insert(eid);
}
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::allowlist::PeerEntry;
fn store_with(rows: &[(u8, Option<&str>, &str)]) -> (PeerStore, tempfile::TempDir) {
let tmp = tempfile::tempdir().expect("tempdir");
let store = PeerStore::open(&tmp.path().join("state.redb")).expect("open store");
for (b, user_id, nickname) in rows {
store
.add(PeerEntry {
endpoint_id: [*b; 32],
nickname: (*nickname).to_string(),
services: vec![],
paired_at: None,
user_id: user_id.map(|u| u.to_string()),
last_addr: None,
})
.expect("add peer");
}
(store, tmp)
}
fn eid(b: u8) -> EndpointId {
EndpointId::from_bytes([b; 32])
}
fn set(ids: &[u8]) -> HashSet<EndpointId> {
ids.iter().map(|b| eid(*b)).collect()
}
#[test]
fn endpoints_for_principal_resolves_eid_and_user_id_and_nothing_else() {
let (store, _tmp) = store_with(&[
(1, Some("b64u:ann"), "ann-laptop"),
(2, Some("b64u:ann"), "ann-phone"),
(3, None, "bob"),
]);
assert_eq!(
endpoints_for_principal(&store, None, &eid(1).principal()).unwrap(),
set(&[1]),
"an eid: principal names exactly its own device"
);
assert_eq!(
endpoints_for_principal(&store, None, "b64u:ann").unwrap(),
set(&[1, 2]),
"a user_id principal names every device of that person"
);
assert_eq!(
endpoints_for_principal(&store, None, &eid(3).principal()).unwrap(),
set(&[3]),
"a device with no user_id still resolves by its eid:"
);
assert!(
endpoints_for_principal(&store, None, "b64u:nobody")
.unwrap()
.is_empty(),
"an unknown principal must sever nothing"
);
assert!(
endpoints_for_principal(&store, None, "bob")
.unwrap()
.is_empty(),
"a display nickname must never resolve — it never admits either (#38)"
);
}
#[test]
fn endpoints_for_principal_resolves_roster_user_ids_and_groups() {
use mcpmesh_trust::roster::sign::mint_signed;
use mcpmesh_trust::roster::validate::load_installed;
use mcpmesh_trust::roster::{Roster, RosterDevice, RosterUser, encode_b64u};
let root = ed25519_dalek::SigningKey::from_bytes(&[9u8; 32]);
let user = |uid: &str, group: &str, dev: u8| RosterUser {
user_id: uid.into(),
display_name: uid.into(),
user_pk: encode_b64u(&[1u8; 32]),
groups: vec![group.into()],
devices: vec![RosterDevice {
endpoint_id: encode_b64u(&[dev; 32]),
label: "device".into(),
role: "primary".into(),
}],
};
let signed = mint_signed(
&root,
Roster {
format: "mcpmesh-roster/1".into(),
org_id: "acme".into(),
serial: 1,
issued_at: "2000-01-01T00:00:00Z".into(),
expires_at: "2999-01-01T00:00:00Z".into(),
groups: vec!["team-eng".into(), "team-ops".into()],
users: vec![user("alice", "team-eng", 10), user("carol", "team-ops", 11)],
revoked_endpoints: vec![],
sig: String::new(),
},
);
let view = load_installed(&signed, &root.verifying_key()).expect("valid roster view");
let (store, _tmp) = store_with(&[]);
assert_eq!(
endpoints_for_principal(&store, Some(&view), "alice").unwrap(),
set(&[10]),
"a roster user_id resolves to that user's devices"
);
assert_eq!(
endpoints_for_principal(&store, Some(&view), "team-eng").unwrap(),
set(&[10]),
"a roster GROUP name resolves to every device in the group"
);
assert_eq!(
endpoints_for_principal(&store, Some(&view), &eid(11).principal()).unwrap(),
set(&[11]),
"a roster device also resolves by its eid: principal"
);
assert!(
endpoints_for_principal(&store, Some(&view), "team-nope")
.unwrap()
.is_empty(),
"an unknown group severs nothing"
);
}
}