#![doc = include_str!("../Documentation.md")]
use std::sync::Arc;
use kcode_k1_access_launch_nodes::{
AccessContext, AccessId, AccessPolicy, Authority, GroupId, K1AccessLaunchNodes, ProfileId,
TargetId, TargetName, TxId, UserId,
};
use serde::Deserialize;
const INVALID_SET: &str = "invalid SetLaunchNode arguments";
const INVALID_GET: &str = "invalid GetLaunchNode arguments";
#[derive(Clone)]
pub struct SetLaunchNodeKtool {
launch_nodes: Arc<K1AccessLaunchNodes>,
}
impl SetLaunchNodeKtool {
pub fn new(launch_nodes: Arc<K1AccessLaunchNodes>) -> Self {
Self { launch_nodes }
}
pub fn get_launch_node(&self) -> GetLaunchNodeKtool {
GetLaunchNodeKtool {
launch_nodes: Arc::clone(&self.launch_nodes),
}
}
pub fn launch(
&self,
context: &AccessContext,
profile_id: ProfileId,
policy: AccessPolicy,
arguments: &str,
) -> Result<String, String> {
let parsed: SetArguments = serde_json::from_str(arguments).map_err(|_| invalid_set())?;
let target_name = TargetName::new(parsed.target).map_err(|_| invalid_set())?;
let node = parsed
.node_id
.parse::<TxId>()
.map(AccessId::new)
.map_err(|_| invalid_set())?;
let target = TargetId::new(Authority::User(context.user()), target_name);
self.launch_nodes
.set(context, profile_id, policy, target, node)?;
Ok("success".to_owned())
}
}
#[derive(Clone)]
pub struct GetLaunchNodeKtool {
launch_nodes: Arc<K1AccessLaunchNodes>,
}
impl GetLaunchNodeKtool {
pub fn launch(&self, context: &AccessContext, arguments: &str) -> Result<String, String> {
let parsed: GetArguments = serde_json::from_str(arguments).map_err(|_| invalid_get())?;
let authority_id = parsed
.authority_id
.parse::<TxId>()
.map_err(|_| invalid_get())?;
let authority = match parsed.authority_kind.as_str() {
"user" => Authority::User(UserId::from_tx_id(authority_id)),
"group" => Authority::Group(GroupId::new(authority_id)),
_ => return Err(invalid_get()),
};
let target_name = TargetName::new(parsed.target).map_err(|_| invalid_get())?;
let node = self
.launch_nodes
.lookup(context, &TargetId::new(authority, target_name))?;
Ok(format!(r#"{{"node_id":"{}"}}"#, node.txid()))
}
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct SetArguments {
target: String,
node_id: String,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct GetArguments {
authority_kind: String,
authority_id: String,
target: String,
}
fn invalid_set() -> String {
INVALID_SET.to_owned()
}
fn invalid_get() -> String {
INVALID_GET.to_owned()
}
#[cfg(test)]
mod tests {
use super::*;
use kcode_k1_access::{
FilteredAuthorities, K1Access, ModelId, SubsystemId, Target, ViewerSubject,
};
use kcode_k1_groups::K1Groups;
use kcode_k1_launch_nodes::LaunchNodes;
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::K1TxnOrdering;
fn tx(value: u8) -> TxId {
TxId::from_bytes([value; 12])
}
#[test]
fn strict_set_arguments_reject_every_malformed_shape() {
let valid = r#"{"target":"Harness","node_id":"000000000000000000000001"}"#;
assert!(serde_json::from_str::<SetArguments>(valid).is_ok());
for arguments in [
"",
"{}",
"[]",
r#"{"target":"Harness"}"#,
r#"{"node_id":"000000000000000000000001"}"#,
r#"{"target":"Harness","node_id":"000000000000000000000001","extra":true}"#,
] {
assert_eq!(
serde_json::from_str::<SetArguments>(arguments)
.map(|_| ())
.map_err(|_| invalid_set()),
Err(INVALID_SET.to_owned())
);
}
}
#[test]
fn strict_get_arguments_reject_malformed_authority_selectors() {
let valid = r#"{"authority_kind":"user","authority_id":"000000000000000000000001","target":"KmapLaunchNode"}"#;
assert!(serde_json::from_str::<GetArguments>(valid).is_ok());
for arguments in [
"",
"{}",
r#"{"authority_kind":"user","authority_id":"000000000000000000000001"}"#,
r#"{"authority_kind":"user","authority_id":"000000000000000000000001","target":"KmapLaunchNode","extra":true}"#,
r#"{"authority_kind":"user","authority_kind":"group","authority_id":"000000000000000000000001","target":"KmapLaunchNode"}"#,
] {
assert_eq!(
serde_json::from_str::<GetArguments>(arguments)
.map(|_| ())
.map_err(|_| invalid_get()),
Err(INVALID_GET.to_owned())
);
}
for arguments in [
r#"{"authority_kind":"friend","authority_id":"000000000000000000000001","target":"KmapLaunchNode"}"#,
r#"{"authority_kind":"user","authority_id":"00000000000000000000000A","target":"KmapLaunchNode"}"#,
r#"{"authority_kind":"group","authority_id":"1","target":"KmapLaunchNode"}"#,
r#"{"authority_kind":"group","authority_id":"000000000000000000000001","target":" "}"#,
] {
let root = tempfile::tempdir().unwrap();
let ordering = Arc::new(K1TxnOrdering::open(&root.path().join("ordering")).unwrap());
let peering =
Arc::new(K1Peering::open(&root.path().join("peering"), ordering.clone()).unwrap());
let groups = Arc::new(
K1Groups::open(
&root.path().join("groups"),
ordering.clone(),
peering.clone(),
)
.unwrap(),
);
let access = Arc::new(
K1Access::open(
&root.path().join("access"),
ordering.clone(),
peering.clone(),
groups.clone(),
)
.unwrap(),
);
let bindings = Arc::new(
LaunchNodes::open(&root.path().join("launch-nodes"), ordering, peering).unwrap(),
);
let facade = Arc::new(K1AccessLaunchNodes::open(access, groups, bindings).unwrap());
let context = AccessContext::new(
UserId::from_tx_id(tx(1)),
ModelId::from_bytes([2; 32]),
FilteredAuthorities::new(Vec::new()).unwrap(),
)
.unwrap();
assert_eq!(
SetLaunchNodeKtool::new(facade)
.get_launch_node()
.launch(&context, arguments),
Err(INVALID_GET.to_owned())
);
}
}
#[test]
fn set_and_cross_user_get_share_the_facade_without_mutating_lookup() {
let root = tempfile::tempdir().unwrap();
let ordering = Arc::new(K1TxnOrdering::open(&root.path().join("ordering")).unwrap());
let peering =
Arc::new(K1Peering::open(&root.path().join("peering"), ordering.clone()).unwrap());
let groups = Arc::new(
K1Groups::open(
&root.path().join("groups"),
ordering.clone(),
peering.clone(),
)
.unwrap(),
);
let access = Arc::new(
K1Access::open(
&root.path().join("access"),
ordering.clone(),
peering.clone(),
groups.clone(),
)
.unwrap(),
);
let bindings = Arc::new(
LaunchNodes::open(&root.path().join("launch-nodes"), ordering, peering).unwrap(),
);
let facade =
Arc::new(K1AccessLaunchNodes::open(access.clone(), groups, bindings.clone()).unwrap());
let setter = SetLaunchNodeKtool::new(facade.clone());
let getter = setter.get_launch_node();
let caller = UserId::from_tx_id(tx(1));
let friend = UserId::from_tx_id(tx(8));
let model = ModelId::from_bytes([2; 32]);
let caller_context =
AccessContext::new(caller, model, FilteredAuthorities::new(Vec::new()).unwrap())
.unwrap();
let friend_context =
AccessContext::new(friend, model, FilteredAuthorities::new(Vec::new()).unwrap())
.unwrap();
let friend_profile = ProfileId::new(tx(3));
let friend_policy = AccessPolicy::new(
Authority::User(friend),
Vec::new(),
vec![ViewerSubject::User(caller), ViewerSubject::Model(model)],
)
.unwrap();
let node = access
.create(
&friend_context,
Target::new(SubsystemId::from_str("k1-kmap").unwrap(), vec![4; 12]),
friend_profile,
friend_policy.clone(),
)
.unwrap()
.access_id();
let target = TargetId::new(
Authority::User(friend),
TargetName::new("KmapLaunchNode".to_owned()).unwrap(),
);
facade
.create(
&friend_context,
friend_profile,
friend_policy,
target.clone(),
node,
)
.unwrap();
let before = bindings.get(&target).unwrap();
let arguments = format!(
r#"{{"authority_kind":"user","authority_id":"{}","target":"KmapLaunchNode"}}"#,
friend.as_tx_id()
);
assert_eq!(
getter.launch(&caller_context, &arguments),
Ok(format!(r#"{{"node_id":"{}"}}"#, node.txid()))
);
assert_eq!(bindings.get(&target).unwrap(), before);
assert_eq!(facade.lookup(&caller_context, &target), Ok(node));
}
#[test]
fn existing_set_create_and_update_use_the_context_user() {
let root = tempfile::tempdir().unwrap();
let ordering = Arc::new(K1TxnOrdering::open(&root.path().join("ordering")).unwrap());
let peering =
Arc::new(K1Peering::open(&root.path().join("peering"), ordering.clone()).unwrap());
let groups = Arc::new(
K1Groups::open(
&root.path().join("groups"),
ordering.clone(),
peering.clone(),
)
.unwrap(),
);
let access = Arc::new(
K1Access::open(
&root.path().join("access"),
ordering.clone(),
peering.clone(),
groups.clone(),
)
.unwrap(),
);
let bindings = Arc::new(
LaunchNodes::open(&root.path().join("launch-nodes"), ordering, peering).unwrap(),
);
let facade = Arc::new(K1AccessLaunchNodes::open(access.clone(), groups, bindings).unwrap());
let tool = SetLaunchNodeKtool::new(facade.clone());
let user = UserId::from_tx_id(tx(1));
let model = ModelId::from_bytes([2; 32]);
let context =
AccessContext::new(user, model, FilteredAuthorities::new(Vec::new()).unwrap()).unwrap();
let profile = ProfileId::new(tx(3));
let policy = AccessPolicy::new(
Authority::User(user),
Vec::new(),
vec![ViewerSubject::Model(model)],
)
.unwrap();
let subsystem = SubsystemId::from_str("k1-kmap").unwrap();
let first = access
.create(
&context,
Target::new(subsystem, vec![4; 12]),
profile,
policy.clone(),
)
.unwrap()
.access_id();
let second = access
.create(
&context,
Target::new(subsystem, vec![5; 12]),
profile,
policy.clone(),
)
.unwrap()
.access_id();
let target = TargetId::new(
Authority::User(user),
TargetName::new("Harness".to_owned()).unwrap(),
);
let first_arguments = format!(r#"{{"target":"Harness","node_id":"{}"}}"#, first.txid());
assert_eq!(
tool.launch(&context, profile, policy.clone(), &first_arguments),
Ok("success".to_owned())
);
assert_eq!(facade.lookup(&context, &target), Ok(first));
let second_arguments = format!(r#"{{"target":"Harness","node_id":"{}"}}"#, second.txid());
assert_eq!(
tool.launch(&context, profile, policy, &second_arguments),
Ok("success".to_owned())
);
assert_eq!(facade.lookup(&context, &target), Ok(second));
}
#[test]
fn complete_package_stays_below_the_managed_limit() {
let files = [
include_str!("../Cargo.toml"),
include_str!("../Documentation.md"),
include_str!("lib.rs"),
];
let count = files
.iter()
.flat_map(|file| file.lines())
.filter(|line| !line.trim().is_empty())
.count();
assert!(count < 500, "complete package has {count} nonblank lines");
}
}