Skip to content

Commit

Permalink
🔧 Parse rule attributes into Operations
Browse files Browse the repository at this point in the history
This approach focuses on how the configuration will be used,
and (from reading `man 1 dbus-daemon`) it seems as though different
rule attributes are implemented during different phases,
which I've separated out into "operations": connect, own, receive, send
  • Loading branch information
jokeyrhyme committed Nov 27, 2024
1 parent 9cb500e commit 3372015
Showing 1 changed file with 123 additions and 15 deletions.
138 changes: 123 additions & 15 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ const STANDARD_SYSTEM_SERVICEDIRS: &[&str] = &[
"/lib/dbus-1/system-services",
];

#[derive(Clone, Debug, PartialEq)]
pub enum Access {
Allow,
Deny,
}

/// implements [`dbus-daemon`'s Configuration File](https://dbus.freedesktop.org/doc/dbus-daemon.1.html#configuration_file)
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
#[serde(try_from = "Document")]
Expand Down Expand Up @@ -76,7 +82,7 @@ pub struct BusConfig {
impl TryFrom<Document> for BusConfig {
type Error = Error;

fn try_from(value: Document) -> Result<Self> {
fn try_from(value: Document) -> std::result::Result<Self, Self::Error> {
let mut bc = BusConfig::default();

for element in value.busconfig {
Expand Down Expand Up @@ -298,6 +304,47 @@ pub struct Limits {
pub reply_timeout: Duration,
}

#[derive(Clone, Debug, PartialEq)]
pub enum Operation {
Connect,
Own,
Receive,
Send,
}
type OptionalOperation = Option<Operation>;
impl TryFrom<RuleAttributes> for OptionalOperation {
type Error = Error;

fn try_from(value: RuleAttributes) -> std::result::Result<Self, Self::Error> {
let has_connect = false;
let has_own = value.own.is_some();
let has_send = value.send_destination.is_some();
let has_receive =
value.receive_sender.is_some() || (!has_send && value.eavesdrop.is_some());

let operations_count: i8 = vec![has_connect, has_own, has_receive, has_send]
.into_iter()
.map(i8::from)
.sum();

if operations_count > 1 {
return Err(Error::msg(format!("do not mix rule attributes for connect, own, receive, and/or send operations in the same rule: {value:?}")));
}

if has_connect {
Ok(Some(Operation::Connect))
} else if has_own {
Ok(Some(Operation::Own))
} else if has_receive {
Ok(Some(Operation::Receive))
} else if has_send {
Ok(Some(Operation::Send))
} else {
Err(Error::msg(format!("rule must specify supported attributes for connect, own, receive, or send operations: {value:?}")))
}
}
}

#[derive(Clone, Debug, PartialEq)]
pub enum Policy {
DefaultContext(Vec<Rule>),
Expand All @@ -311,7 +358,7 @@ type OptionalPolicy = Option<Policy>;
impl TryFrom<PolicyElement> for OptionalPolicy {
type Error = Error;

fn try_from(value: PolicyElement) -> Result<Self> {
fn try_from(value: PolicyElement) -> std::result::Result<Self, Self::Error> {
match value {
PolicyElement {
at_console: Some(_),
Expand Down Expand Up @@ -389,10 +436,16 @@ type OptionalRule = Option<Rule>;
impl TryFrom<RuleElement> for OptionalRule {
type Error = Error;

fn try_from(value: RuleElement) -> Result<Self> {
fn try_from(value: RuleElement) -> std::result::Result<Self, Self::Error> {
match value {
RuleElement::Allow => Ok(Some(Rule::Allow)),
RuleElement::Deny => Ok(Some(Rule::Deny)),
RuleElement::Allow(attrs) => match OptionalOperation::try_from(attrs)? {
Some(some) => Ok(Some((Access::Allow, some))),
None => Ok(None),
},
RuleElement::Deny(attrs) => match OptionalOperation::try_from(attrs)? {
Some(some) => Ok(Some((Access::Deny, some))),
None => Ok(None),
},
}
}
}
Expand All @@ -408,17 +461,29 @@ fn rules_try_from_rule_elements(value: Vec<RuleElement>) -> Result<Vec<Rule>> {
Ok(rules)
}

#[derive(Clone, Debug, PartialEq)]
pub enum Rule {
Allow,
Deny,
pub type Rule = (Access, Operation);

#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
struct RuleAttributes {
#[serde(rename = "@eavesdrop")]
eavesdrop: Option<String>,
#[serde(rename = "@group")]
group: Option<String>,
#[serde(rename = "@own")]
own: Option<String>,
#[serde(rename = "@receive_sender")]
receive_sender: Option<String>,
#[serde(rename = "@send_destination")]
send_destination: Option<String>,
#[serde(rename = "@user")]
user: Option<String>,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
enum RuleElement {
Allow,
Deny,
Allow(RuleAttributes),
Deny(RuleAttributes),
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
Expand Down Expand Up @@ -620,16 +685,59 @@ mod tests {
busconfig,
BusConfig {
policies: vec![
Policy::DefaultContext(vec![Rule::Allow, Rule::Allow, Rule::Allow,]),
Policy::User(vec![Rule::Allow,], String::from("root")),
Policy::Group(vec![Rule::Allow, Rule::Allow,], String::from("network")),
Policy::MandatoryContext(vec![Rule::Deny]),
Policy::DefaultContext(vec![
(Access::Allow, Operation::Send),
(Access::Allow, Operation::Receive),
(Access::Allow, Operation::Own),
]),
Policy::User(
vec![(Access::Allow, Operation::Send),],
String::from("root")
),
Policy::Group(
vec![
(Access::Allow, Operation::Send),
(Access::Allow, Operation::Receive),
],
String::from("network")
),
Policy::MandatoryContext(vec![(Access::Deny, Operation::Send),]),
],
..Default::default()
}
);
}

#[should_panic]
#[test]
fn bus_config_parse_with_policies_with_send_and_receive_attributes_error() {
let input = r#"<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow send_destination="org.freedesktop.DBus" receive_sender="org.freedesktop.Avahi" />
</policy>
</busconfig>
"#;

BusConfig::parse(input).expect("should parse XML input");
}

#[should_panic]
#[test]
fn bus_config_parse_with_policies_without_attributes_error() {
let input = r#"<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow />
</policy>
</busconfig>
"#;

BusConfig::parse(input).expect("should parse XML input");
}

#[test]
fn bus_config_parse_with_servicedir_and_standard_session_servicedirs_ok() {
let input = r#"<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
Expand Down

0 comments on commit 3372015

Please sign in to comment.