use std::{borrow::Cow, collections::HashMap, fmt::Display, ops::Deref, path::PathBuf};
use bon::Builder;
use log::debug;
use rootasrole_core::{
Cred,
database::{
score::{ActorMatchMin, CmdMin, Score, SecurityMin, TaskScore},
structs::SetBehavior,
},
util::StorageMethod,
};
use serde::{
Deserialize,
de::{DeserializeSeed, IgnoredAny, Visitor},
};
use serde_json::Value;
use crate::{
Cli,
finder::{
de::{cred::CredData, roles::RoleListFinderDeserializer},
options::DPathOptions,
},
};
use super::options::Opt;
pub mod commands;
pub mod cred;
pub mod roles;
pub mod settings;
pub mod tasks;
#[cfg_attr(test, derive(Builder))]
#[derive(PartialEq, Eq, Debug, Default)]
pub struct DConfigFinder<'a> {
pub options: Option<Opt<'a>>,
pub roles: Vec<DRoleFinder<'a>>,
}
#[cfg_attr(test, derive(Builder))]
#[derive(Debug, PartialEq, Eq)]
pub struct DRoleFinder<'a> {
#[cfg_attr(test, builder(default))]
pub user_min: ActorMatchMin,
#[cfg_attr(test, builder(into))]
pub role: Cow<'a, str>,
#[cfg_attr(test, builder(default))]
pub tasks: Vec<DTaskFinder<'a>>,
pub options: Option<Opt<'a>>,
#[cfg_attr(test, builder(default))]
pub extra_values: HashMap<Cow<'a, str>, Value>,
}
#[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
#[serde(untagged)]
pub enum IdTask<'a> {
Name(#[serde(borrow)] Cow<'a, str>),
Number(usize),
}
impl Display for IdTask<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IdTask::Name(name) => write!(f, "{name}"),
IdTask::Number(num) => write!(f, "{num}"),
}
}
}
#[derive(Debug, Builder, PartialEq, Eq)]
pub struct DTaskFinder<'a> {
pub id: IdTask<'a>,
#[builder(default)]
pub score: TaskScore,
pub cred: CredData<'a>,
pub commands: Option<DCommandList<'a>>,
pub options: Option<Opt<'a>>,
pub final_path: Option<PathBuf>,
}
#[cfg_attr(test, derive(Builder))]
#[derive(PartialEq, Eq, Debug)]
pub struct DCommandList<'a> {
#[cfg_attr(test, builder(start_fn, into))]
pub default_behavior: Option<SetBehavior>,
#[cfg_attr(test, builder(default, into))]
pub add: Cow<'a, [DCommand<'a>]>,
#[cfg_attr(test, builder(default, into))]
pub del: Cow<'a, [DCommand<'a>]>,
}
#[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
#[serde(untagged)]
pub enum DCommand<'a> {
Simple(#[serde(borrow)] Cow<'a, str>),
Complex(Value),
}
#[cfg(test)]
impl<'a> DCommand<'a> {
pub fn simple(cmd: &'a str) -> Self {
DCommand::Simple(Cow::Borrowed(cmd))
}
pub fn complex(cmd: Value) -> Self {
DCommand::Complex(cmd)
}
}
#[inline]
const fn to_storage_m(is_human_readable: bool) -> StorageMethod {
if is_human_readable {
StorageMethod::JSON
} else {
StorageMethod::CBOR
}
}
impl<'a> DConfigFinder<'a> {
pub fn roles<'s>(&'s self) -> impl Iterator<Item = DLinkedRole<'s, 'a>> {
self.roles.iter().map(|role| DLinkedRole::new(self, role))
}
#[cfg(any(feature = "hierarchy", feature = "ssd"))]
pub fn role<'s>(&'s self, role_name: &str) -> Option<DLinkedRole<'s, 'a>> {
self.roles
.iter()
.find(|r| r.role == role_name)
.map(|role| DLinkedRole::new(self, role))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DLinkedRole<'c, 'a> {
parent: &'c DConfigFinder<'a>,
role: &'c DRoleFinder<'a>,
}
impl<'c, 'a> DLinkedRole<'c, 'a> {
const fn new(parent: &'c DConfigFinder<'a>, role: &'c DRoleFinder<'a>) -> Self {
Self { parent, role }
}
pub fn tasks<'t>(&'t self) -> impl Iterator<Item = DLinkedTask<'t, 'c, 'a>> {
self.role
.tasks
.iter()
.map(|task| DLinkedTask::new(self, task))
}
pub const fn role(&self) -> &DRoleFinder<'a> {
self.role
}
pub const fn config(&self) -> &DConfigFinder<'a> {
self.parent
}
}
#[derive(Clone, Copy, Debug)]
pub struct DLinkedTask<'t, 'c, 'a> {
parent: &'t DLinkedRole<'c, 'a>,
pub task: &'t DTaskFinder<'a>,
}
impl<'t, 'c, 'a> DLinkedTask<'t, 'c, 'a> {
const fn new(parent: &'t DLinkedRole<'c, 'a>, task: &'t DTaskFinder<'a>) -> Self {
Self { parent, task }
}
pub fn commands<'l>(&'l self) -> Option<DLinkedCommandList<'l, 't, 'c, 'a>> {
self.task
.commands
.as_ref()
.map(|list| DLinkedCommandList::new(self, list))
}
pub const fn role(&self) -> &DLinkedRole<'c, 'a> {
self.parent
}
pub const fn task(&self) -> &DTaskFinder<'a> {
self.task
}
pub fn score(&self, cmd_min: CmdMin, security_min: SecurityMin) -> Score {
Score::builder()
.user_min(self.role().role.user_min)
.caps_min(self.score.caps_min)
.cmd_min(cmd_min)
.security_min(security_min)
.setuser_min(self.score.setuser_min)
.build()
}
}
impl<'a> Deref for DLinkedTask<'_, '_, 'a> {
type Target = DTaskFinder<'a>;
fn deref(&self) -> &Self::Target {
self.task
}
}
pub struct DLinkedCommandList<'l, 't, 'c, 'a> {
#[allow(dead_code)] parent: &'l DLinkedTask<'t, 'c, 'a>,
command_list: &'l DCommandList<'a>,
}
impl<'l, 't, 'c, 'a> DLinkedCommandList<'l, 't, 'c, 'a> {
const fn new(parent: &'l DLinkedTask<'t, 'c, 'a>, list: &'l DCommandList<'a>) -> Self {
Self {
parent,
command_list: list,
}
}
pub fn add<'d>(&'d self) -> impl Iterator<Item = DLinkedCommand<'d, 'l, 't, 'c, 'a>> {
self.command_list
.add
.iter()
.map(|cmd| DLinkedCommand::new(self, cmd))
}
pub fn del<'d>(&'d self) -> impl Iterator<Item = DLinkedCommand<'d, 'l, 't, 'c, 'a>> {
self.command_list
.del
.iter()
.map(|cmd| DLinkedCommand::new(self, cmd))
}
}
impl<'a> Deref for DLinkedCommandList<'_, '_, '_, 'a> {
type Target = DCommandList<'a>;
fn deref(&self) -> &Self::Target {
self.command_list
}
}
pub struct DLinkedCommand<'d, 'l, 't, 'c, 'a> {
#[allow(dead_code)] parent: &'d DLinkedCommandList<'l, 't, 'c, 'a>,
pub command: &'d DCommand<'a>,
}
impl<'d, 'l, 't, 'c, 'a> DLinkedCommand<'d, 'l, 't, 'c, 'a> {
const fn new(
parent: &'d DLinkedCommandList<'l, 't, 'c, 'a>,
command: &'d DCommand<'a>,
) -> Self {
Self { parent, command }
}
#[allow(dead_code)] pub const fn task(&self) -> &DLinkedTask<'t, 'c, 'a> {
self.parent.parent
}
}
impl<'a> Deref for DLinkedCommand<'_, '_, '_, '_, 'a> {
type Target = DCommand<'a>;
fn deref(&self) -> &Self::Target {
self.command
}
}
pub struct ConfigFinderDeserializer<'a> {
pub cli: &'a Cli,
pub cred: &'a Cred,
pub env_path: &'a [&'a str],
}
impl<'de: 'a, 'a> DeserializeSeed<'de> for ConfigFinderDeserializer<'a> {
type Value = DConfigFinder<'a>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
#[repr(u8)]
enum Field<'a> {
#[serde(alias = "o")]
Options,
#[serde(alias = "r")]
Roles,
#[serde(untagged, borrow)]
#[allow(dead_code)]
Unknown(Cow<'a, str>),
}
struct ConfigFinderVisitor<'a> {
cli: &'a Cli,
cred: &'a Cred,
env_path: &'a [&'a str],
}
impl<'de: 'a, 'a> Visitor<'de> for ConfigFinderVisitor<'a> {
type Value = DConfigFinder<'a>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("policy")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: serde::de::MapAccess<'de>,
{
let mut options = None;
let mut roles = Vec::new();
let mut spath = DPathOptions::default_path();
while let Some(key) = map.next_key()? {
match key {
Field::Options => {
debug!("ConfigFinderVisitor: options");
options = Some(map.next_value()?);
}
Field::Roles => {
debug!("ConfigFinderVisitor: roles");
roles = map.next_value_seed(RoleListFinderDeserializer {
cli: self.cli,
cred: self.cred,
spath: &mut spath,
env_path: self.env_path,
})?;
}
Field::Unknown(_) => {
debug!("ConfigFinderVisitor: unknown");
let _ = map.next_value::<IgnoredAny>();
}
}
}
Ok(DConfigFinder { options, roles })
}
}
deserializer.deserialize_map(ConfigFinderVisitor {
cli: self.cli,
cred: self.cred,
env_path: self.env_path,
})
}
}
#[cfg(test)]
mod tests {
use std::fs;
use crate::finder::de::tasks::TaskListFinderDeserializer;
use super::*;
use cbor4ii::core::utils::SliceReader;
use nix::unistd::{getgid, getuid};
use rootasrole_core::database::{
actor::SGroups,
score::{CapsMin, SetUserMin, SetgidMin, SetuidMin},
};
use test_log::test;
pub fn get_non_root_uid(nth: usize) -> Option<u32> {
let passwd = fs::read_to_string("/etc/passwd").unwrap();
let passwd: Vec<&str> = passwd.split('\n').collect();
passwd
.iter()
.map(|line| {
let line: Vec<&str> = line.split(':').collect();
line[2].parse::<u32>().unwrap()
})
.filter(|uid| *uid != 0)
.nth(nth)
}
pub fn get_non_root_gid(nth: usize) -> Option<u32> {
let passwd = fs::read_to_string("/etc/group").unwrap();
let passwd: Vec<&str> = passwd.split('\n').collect();
passwd
.iter()
.map(|line| {
let line: Vec<&str> = line.split(':').collect();
line[2].parse::<u32>().unwrap()
})
.filter(|uid| *uid != 0)
.nth(nth)
}
pub fn convert_json_to_cbor(json: &str) -> Vec<u8> {
let value: Value = serde_json::from_str(json).unwrap();
cbor4ii::serde::to_vec(Vec::new(), &value).unwrap()
}
#[test]
fn test_idtask_display() {
let name = IdTask::Name(Cow::Borrowed("test"));
let number = IdTask::Number(42);
assert_eq!(format!("{name}"), "test");
assert_eq!(format!("{number}"), "42");
}
#[test]
fn test_dcommandlist_deserialize_seq() {
let json = r#"["ls", "cat"]"#;
let list: DCommandList = serde_json::from_str(json).unwrap();
assert_eq!(list.add.len(), 2);
assert!(matches!(list.add[0], DCommand::Simple(_)));
}
#[test]
fn test_dcommandlist_deserialize_map() {
let json = r#"{"default": "all", "add": ["ls"], "del": ["rm"]}"#;
let list: DCommandList = serde_json::from_str(json).unwrap();
assert_eq!(list.default_behavior.unwrap(), SetBehavior::All);
assert_eq!(list.add.len(), 1);
assert_eq!(list.del.len(), 1);
}
#[test]
fn test_dcommandlist_deserialize_all_or_none() {
let json = "\"all\"";
let list: DCommandList = serde_json::from_str(json).unwrap();
assert_eq!(list.default_behavior, Some(SetBehavior::All));
assert_eq!(list.add.len(), 0);
assert_eq!(list.del.len(), 0);
let json = "\"none\"";
let list: DCommandList = serde_json::from_str(json).unwrap();
assert_eq!(list.default_behavior, Some(SetBehavior::None));
assert_eq!(list.add.len(), 0);
assert_eq!(list.del.len(), 0);
}
#[test]
fn test_dcommandlist_deserialize_empty() {
let json = "{}";
let list: DCommandList = serde_json::from_str(json).unwrap();
assert_eq!(list.default_behavior, None);
assert_eq!(list.add.len(), 0);
assert_eq!(list.del.len(), 0);
}
#[test]
fn test_dcommandlist_deserialize_invalid() {
let json = r#"{"default": "invalid", "add": ["ls"], "del": ["rm"]}"#;
let result: Result<DCommandList, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[test]
fn test_config_finder_deserializer() {
let json = format!(
r#"{{"roles":[{{"name":"r_test","actors":[{{"type": "user", "id": {}}}], "tasks": [{{"name": "test", "cred": {{"setuid":"0", "setgid":["0", 0], "caps": []}}, "commands": ["/usr/bin/ls"]}}]}}]}}"#,
getuid().as_raw()
);
let cli = Cli::builder().cmd_path("ls").build();
let deserializer = ConfigFinderDeserializer {
cli: &cli,
env_path: &["/usr/bin"],
cred: &Cred::builder()
.curdir()
.unwrap()
.groups()
.unwrap()
.user()
.unwrap()
.build(),
};
let result = deserializer.deserialize(&mut serde_json::Deserializer::from_str(&json));
assert!(result.is_ok(), "Failed to deserialize: {result:?}");
let config = result.unwrap();
assert_eq!(config.roles.len(), 1);
assert_eq!(config.roles[0].role, "r_test");
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_config_finder_implementation() {
let json = format!(
r#"{{"roles":[{{"name":"r_test","actors":[{{"type":"user","id":{}}}],"tasks":[{{"name":"test","cred":{{"setuid":"0","setgid":["0",0],"caps":[]}},"commands":["/usr/bin/ls"]}},{{"name":"test2","cred":{{"setuid":"0","setgid":["0",0],"caps":[]}},"commands":["/usr/bin/ls","/usr/bin/cat"]}}]}},{{"name":"r_test2","actors":[{{"type":"group","names":[{}, {}]}}],"tasks":[{{"name":"test3","cred":{{"setuid":"0","setgid":["0",0],"caps":[]}},"commands":["/usr/bin/cat","/usr/bin/ls"]}}]}}]}}"#,
getuid().as_raw(),
getgid().as_raw(),
getgid().as_raw()
);
let cli = Cli::builder().cmd_path("ls").build();
let deserializer = ConfigFinderDeserializer {
cli: &cli,
env_path: &["/usr/bin"],
cred: &Cred::builder()
.curdir()
.unwrap()
.groups()
.unwrap()
.user()
.unwrap()
.build(),
};
let result = deserializer.deserialize(&mut serde_json::Deserializer::from_str(&json));
assert!(result.is_ok(), "Failed to deserialize: {result:?}");
let config = result.unwrap();
let mut roles = config.roles();
let role_a = roles.next().unwrap();
assert_eq!(role_a.role().role, "r_test");
let mut tasks = role_a.tasks();
let task_a = tasks.next().unwrap();
assert_eq!(task_a.task().id, IdTask::Name("test".into()));
let commands = task_a.commands().unwrap();
assert_eq!(commands.add().count(), 1);
assert_eq!(
*commands.add().next().unwrap().command,
DCommand::Simple("/usr/bin/ls".into())
);
let task_b = tasks.next().unwrap();
assert_eq!(task_b.task().id, IdTask::Name("test2".into()));
let commands = task_b.commands().unwrap();
assert_eq!(commands.add().count(), 2);
assert_eq!(
*commands.add().next().unwrap().command,
DCommand::Simple("/usr/bin/ls".into())
);
assert_eq!(
*commands.add().nth(1).unwrap().command,
DCommand::Simple("/usr/bin/cat".into())
);
assert!(tasks.next().is_none());
let role_b = roles.next().unwrap();
assert_eq!(role_b.role().role, "r_test2");
let mut tasks = role_b.tasks();
let task_a = tasks.next().unwrap();
assert_eq!(task_a.task().id, IdTask::Name("test3".into()));
let commands = task_a.commands().unwrap();
assert_eq!(commands.add().count(), 2);
assert_eq!(
*commands.add().next().unwrap().command,
DCommand::Simple("/usr/bin/cat".into())
);
assert_eq!(
*commands.add().nth(1).unwrap().command,
DCommand::Simple("/usr/bin/ls".into())
);
assert_eq!(commands.del().count(), 0);
assert!(tasks.next().is_none());
assert!(roles.next().is_none());
assert!(config.options.is_none());
assert!(config.roles[0].options.is_none());
assert!(config.roles[0].tasks[0].options.is_none());
assert!(config.roles[0].tasks[1].options.is_none());
assert!(config.roles[1].options.is_none());
assert!(config.roles[1].tasks[0].options.is_none());
assert!(config.role("r_test").is_some());
assert!(config.role("r_test2").is_some());
assert!(config.role("r_test3").is_none());
assert_eq!(*config.role("r_test").unwrap().config(), config);
assert_eq!(*config.role("r_test2").unwrap().config(), config);
assert_eq!(
*config
.role("r_test")
.unwrap()
.tasks()
.next()
.unwrap()
.role(),
config.role("r_test").unwrap()
);
assert_eq!(
*config
.role("r_test2")
.unwrap()
.tasks()
.next()
.unwrap()
.role(),
config.role("r_test2").unwrap()
);
assert_eq!(
config
.role("r_test")
.unwrap()
.tasks()
.next()
.unwrap()
.score(CmdMin::MATCH, SecurityMin::empty()),
Score::builder()
.user_min(ActorMatchMin::UserMatch)
.setuser_min(SetUserMin {
uid: Some(SetuidMin::from(0)),
gid: Some(SetgidMin::from(SGroups::from(vec![0])))
})
.caps_min(CapsMin::NoCaps)
.security_min(SecurityMin::empty())
.cmd_min(CmdMin::MATCH)
.build()
);
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_config_with_options() {
let json = format!(
r#"{{
"options": {{
"timeout": {{
"type": "ppid",
"duration": "00:05:00"
}},
"path": {{
"default": "delete",
"add": [
"/usr/bin"
]
}},
"env": {{
"default": "delete",
"override_behavior": false,
"keep": [
"keep1"
],
"check": [
"check1"
],
"delete": [
"del1"
],
"set": {{
"set1": "value1",
"set2": "value2"
}}
}},
"root": "user",
"bounding": "strict"
}},
"roles": [
{{
"options": {{
"timeout": {{
"type": "ppid",
"duration": "00:06:00"
}},
"path": {{
"default": "delete",
"add": [
"/usr/bin"
]
}},
"env": {{
"default": "delete",
"override_behavior": false,
"keep": [
"keep2"
],
"check": [
"check2"
],
"delete": [
"del2"
],
"set": {{
"set1": "value2",
"set3": "value3"
}}
}},
"root": "user",
"bounding": "strict"
}},
"name": "role1",
"actors": [
{{
"type": "user",
"id": {}
}}
],
"tasks": [
{{
"options": {{
"timeout": {{
"type": "ppid",
"duration": "00:07:00"
}},
"path": {{
"default": "delete",
"add": [
"/usr/bin"
]
}},
"env": {{
"default": "delete",
"override_behavior": false,
"keep": [
"keep3"
],
"check": [
"check3"
],
"delete": [
"del3"
],
"set": {{
"set1": "value3",
"set4": "value4"
}}
}},
"root": "user",
"bounding": "strict"
}},
"name": "task1",
"cred": {{
"setuid": 0,
"setgid": 0,
"caps": [
"CAP_SYS_ADMIN",
"CAP_SYS_RESOURCE"
]
}}
}}
]
}}
]
}}"#,
getuid().as_raw()
);
let cli = Cli::builder().cmd_path("ls").build();
let deserializer = ConfigFinderDeserializer {
cli: &cli,
env_path: &["/usr/bin"],
cred: &Cred::builder()
.curdir()
.unwrap()
.groups()
.unwrap()
.user()
.unwrap()
.build(),
};
let result = deserializer.deserialize(&mut serde_json::Deserializer::from_str(&json));
assert!(result.is_ok(), "Failed to deserialize: {result:?}");
let config = result.unwrap();
assert_eq!(config.roles.len(), 1);
assert_eq!(config.roles[0].role, "role1");
assert_eq!(config.roles[0].tasks.len(), 1);
assert_eq!(config.roles[0].tasks[0].id, IdTask::Name("task1".into()));
assert!(config.options.is_some());
assert!(config.roles[0].options.is_some());
assert!(config.roles[0].tasks[0].options.is_some());
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_config_optimized_with_options() {
let json = format!(
r#"{{
"options": {{
"timeout": {{
"type": "ppid",
"duration": "00:05:00"
}},
"path": {{
"default": "delete",
"add": [
"/usr/bin"
]
}},
"env": {{
"default": "delete",
"override_behavior": false,
"keep": [
"keep1"
],
"check": [
"check1"
],
"delete": [
"del1"
],
"set": {{
"set1": "value1",
"set2": "value2"
}}
}},
"root": "user",
"bounding": "strict"
}},
"roles": [
{{
"options": {{
"timeout": {{
"type": "ppid",
"duration": "00:06:00"
}},
"path": {{
"default": "delete",
"add": [
"/usr/bin"
]
}},
"env": {{
"default": "delete",
"override_behavior": false,
"keep": [
"keep2"
],
"check": [
"check2"
],
"delete": [
"del2"
],
"set": {{
"set1": "value2",
"set3": "value3"
}}
}},
"root": "user",
"bounding": "strict"
}},
"name": "role1",
"actors": [
{{
"type": "group",
"id": {}
}}
],
"tasks": [
{{
"options": {{
"timeout": {{
"type": "ppid",
"duration": "00:07:00"
}},
"path": {{
"default": "delete",
"add": [
"/usr/bin"
]
}},
"env": {{
"default": "delete",
"override_behavior": false,
"keep": [
"keep3"
],
"check": [
"check3"
],
"delete": [
"del3"
],
"set": {{
"set1": "value3",
"set4": "value4"
}}
}},
"root": "user",
"bounding": "strict"
}},
"name": "task1",
"cred": {{
"setuid": 0,
"setgid": 0,
"caps": [
"CAP_SYS_ADMIN",
"CAP_SYS_RESOURCE"
]
}},
"commands": ["/usr/bin/ls"]
}}
]
}}
]
}}"#,
getgid().as_raw()
);
let cbor = convert_json_to_cbor(&json);
let cli = Cli::builder().cmd_path("ls").build();
let deserializer = ConfigFinderDeserializer {
cli: &cli,
env_path: &["/usr/bin"],
cred: &Cred::builder()
.curdir()
.unwrap()
.groups()
.unwrap()
.user()
.unwrap()
.build(),
};
let result: Result<DConfigFinder<'_>, _> = deserializer.deserialize(
&mut cbor4ii::serde::Deserializer::new(SliceReader::new(cbor.as_slice())),
);
assert!(result.is_ok(), "Failed to deserialize: {result:?}");
let config = result.unwrap();
assert_eq!(config.roles.len(), 1);
assert_eq!(config.roles[0].role, "role1");
assert_eq!(config.roles[0].tasks.len(), 1);
assert_eq!(config.roles[0].tasks[0].id, IdTask::Name("task1".into()));
assert!(config.options.is_some());
assert!(config.roles[0].options.is_some());
assert!(config.roles[0].tasks[0].options.is_some());
assert_eq!(config.roles[0].user_min, ActorMatchMin::GroupMatch(1));
assert_eq!(config.roles[0].tasks[0].score.cmd_min, CmdMin::MATCH);
assert_eq!(
config.roles[0].tasks[0].score.setuser_min.uid,
Some(SetuidMin::from(&0.into()))
);
assert_eq!(
config.roles[0].tasks[0].score.setuser_min.gid,
Some(SetgidMin::from(&vec![0]))
);
assert_eq!(
config.roles[0].tasks[0].score.caps_min,
CapsMin::CapsAdmin(2)
);
assert!(config.roles[0].tasks[0].commands.is_none());
assert_eq!(
config.roles[0].tasks[0].final_path,
Some(PathBuf::from("/usr/bin/ls"))
);
}
#[test]
fn test_optimized_config() {
let uid = getuid().as_raw();
let json = format!(
r#"{{"roles":[{{"name":"r_test","actors":[{{"type": "user", "id": {uid}}}], "tasks": [{{"name": "test", "cred": {{"setuid":"0", "setgid":["0"], "caps": []}}, "commands": ["/usr/bin/ls"]}}]}}]}}"#
);
let cbor = convert_json_to_cbor(&json);
let cli = Cli::builder().cmd_path("ls").build();
let deserializer = ConfigFinderDeserializer {
cli: &cli,
env_path: &["/usr/bin"],
cred: &Cred::builder()
.curdir()
.unwrap()
.groups()
.unwrap()
.user()
.unwrap()
.build(),
};
let result: Result<DConfigFinder<'_>, _> = deserializer.deserialize(
&mut cbor4ii::serde::Deserializer::new(SliceReader::new(cbor.as_slice())),
);
assert!(result.is_ok(), "Failed to deserialize: {result:?}");
let config = result.unwrap();
assert_eq!(config.roles[0].user_min, ActorMatchMin::UserMatch);
assert_eq!(config.roles[0].tasks[0].score.cmd_min, CmdMin::MATCH);
assert_eq!(
config.roles[0].tasks[0].score.setuser_min.uid,
Some(SetuidMin::from(&0.into()))
);
assert_eq!(
config.roles[0].tasks[0].score.setuser_min.gid,
Some(SetgidMin::from(&vec![0]))
);
assert_eq!(config.roles[0].tasks[0].score.caps_min, CapsMin::NoCaps);
assert!(config.roles[0].tasks[0].commands.is_none());
assert_eq!(
config.roles[0].tasks[0].final_path,
Some(PathBuf::from("/usr/bin/ls"))
);
}
#[test]
fn test_expecting_error() {
let seq = "[1, 2, 3]";
let map = "{\"1\": 2, \"3\": 4}";
let cli = Cli::builder().build();
let cred = Cred::builder()
.curdir()
.unwrap()
.groups()
.unwrap()
.user()
.unwrap()
.build();
let config_finder = ConfigFinderDeserializer {
cli: &cli,
env_path: &[],
cred: &cred,
};
let result = config_finder.deserialize(&mut serde_json::Deserializer::from_str(seq));
assert!(result.is_err(), "Expected error, got: {result:?}");
let role_list = RoleListFinderDeserializer {
cli: &cli,
env_path: &[],
cred: &Cred::builder()
.curdir()
.unwrap()
.groups()
.unwrap()
.user()
.unwrap()
.build(),
spath: &mut DPathOptions::default(),
};
let result = role_list.deserialize(&mut serde_json::Deserializer::from_str(map));
assert!(result.is_err(), "Expected error, got: {result:?}");
let task_list = TaskListFinderDeserializer {
cli: &cli,
env_path: &[],
spath: &mut DPathOptions::default(),
};
let result = task_list.deserialize(&mut serde_json::Deserializer::from_str(map));
assert!(result.is_err(), "Expected error, got: {result:?}");
}
#[test]
fn test_unknown_type() {
let json = r#"{"unknown": "unknown"}"#;
let cli = Cli::builder().build();
let deserializer = ConfigFinderDeserializer {
cli: &cli,
env_path: &[],
cred: &Cred::builder()
.curdir()
.unwrap()
.groups()
.unwrap()
.user()
.unwrap()
.build(),
};
let result = deserializer.deserialize(&mut serde_json::Deserializer::from_str(json));
assert!(
result.is_ok(),
"Expected config with nothing in it, got: {result:?}"
);
}
}