Skip to main content

xrust/
security.rs

1//! Support for security policies.
2//!
3//! A security policy allows a module to limit, or constrain, access to a resource.
4//! The resource is named, using a [QName], and the module will call into the in-force policy to retrieve the limitation set on the resource.
5//! The limitation is returned as a [SecurityResult].
6//! The module may provide [ActualParameters] to the feature, refer to the module's documentation for details.
7//!
8//! ```rust
9//! # use std::rc::Rc;
10//! use xrust::security::{SecurityResult, Policy, Feature};
11//! use xrust::{Error, ErrorKind, Node};
12//! use xrust::item::Item;
13//! use xrust::value::Value;
14//! use xrust::transform::Transform;
15//! use xrust::transform::callable::ActualParameters;
16//! use qualname::{QName, NcName};
17//!
18//! fn get_feature<N: Node>(policy: &Policy<N>) -> Result<Option<String>, Error> {
19//!    match policy.get(
20//!       &QName::from_local_name(NcName::try_from("my_security_feature").unwrap()),
21//!       ActualParameters::Named(vec![
22//!          (QName::from_local_name(NcName::try_from("input").unwrap()),
23//!           Transform::Literal(Item::Value(Rc::new(Value::from("value")))))
24//!       ])
25//!    )? {
26//!        SecurityResult::NotPermitted => Err(Error::new(ErrorKind::NotPermitted, "access denied")),
27//!        SecurityResult::Permitted(None) => Ok(None),
28//!        SecurityResult::Permitted(Some(v)) => Ok(Some(v)),
29//!    }
30//! }
31//! ```
32//!
33//! If a policy does not define a limit or constraint for a resource,
34//! then the module will define a default value. The module should set a default that has minimal security implications for the application.
35//! Most likely this will be to deny access to the resource.
36//!
37//! Security policies are named. Many named policies can be loaded into the system.
38//! The application can nominate which policy it wants to be in force ("activated").
39//!
40//! Resource constraints may be specified either as an absolute value or with a template.
41//! Templates use the same syntax as XSLT templates.
42//!
43//! In this example, a security policy is created with the feature set to "permitted with no limits".
44//!
45//! ```rust
46//! use xrust::security::{Feature, Policy};
47//! use xrust::trees::smite::RNode;
48//! use qualname::{QName, NcName};
49//!
50//! let mut policy: Policy<RNode> = Policy::new(QName::from_local_name(
51//!    NcName::try_from("test_policy").unwrap(),
52//! ));
53//! policy.add(
54//!    QName::from_local_name(
55//!        NcName::try_from("my_security_feature").unwrap(),
56//!    ),
57//!    Feature::Permitted(None),
58//! );
59//! ```
60
61use std::collections::HashMap;
62
63use crate::item::{Node, SequenceTrait};
64use crate::transform::Transform;
65use crate::transform::callable::ActualParameters;
66use crate::transform::context::{Context, StaticContextBuilder};
67use crate::xdmerror::{Error, ErrorKind};
68use qualname::QName;
69
70/// The result of determining the limitation or constraint for a security feature.
71/// Permitted means that the application is allowed to access the resource.
72/// The contained value is a limit on the usage of the resource.
73/// If it is None then there is no limit on resource usage, or the module may impose a default limit.
74/// NotPermitted means that the application is not allowed to access the resource at all, or the module may impose a default limit.
75#[derive(Clone, Debug, PartialEq)]
76pub enum SecurityResult {
77    Permitted(Option<String>),
78    NotPermitted,
79}
80
81/// All of the security policies available for use, indexed by name.
82/// One of these policies may be in force (or "active").
83#[derive(Clone, Debug)]
84pub struct SecurityPolicies<N: Node> {
85    policies: HashMap<QName, Policy<N>>,
86    in_force: Option<QName>,
87}
88
89impl<N: Node> SecurityPolicies<N> {
90    /// Create a new set of security policies.
91    pub fn new() -> Self {
92        Self {
93            policies: HashMap::new(),
94            in_force: None,
95        }
96    }
97    /// Add a new security policy
98    pub fn push(&mut self, policy: Policy<N>) {
99        self.policies.insert(policy.name.clone(), policy);
100    }
101    /// Look up a security policy by name
102    pub fn find(&self, name: QName) -> Option<&Policy<N>> {
103        self.policies.get(&name)
104    }
105    /// Make the named security policy the "in force" (or "active") policy.
106    pub fn activate(&mut self, name: &QName) -> Option<&Policy<N>> {
107        self.policies.get_key_value(name).map(|(_k, v)| {
108            self.in_force = Some(name.clone());
109            v
110        })
111    }
112    /// Determine whether a feature, in the in-force policy, is permitted.
113    /// All parameters must be named, i.e. positional parameters are ignored.
114    pub fn get(&self, f: &QName, a: ActualParameters<N>) -> Result<SecurityResult, Error> {
115        // If there is no in-force security policy then all features are not permitted
116        if self.in_force.is_none() {
117            return Ok(SecurityResult::NotPermitted);
118        }
119
120        // Does the in-force security policy have the requested feature?
121        // If not then it is not permitted
122        if let Some(p) = self.policies.get(&self.in_force.as_ref().unwrap()) {
123            p.get(f, a)
124        } else {
125            Ok(SecurityResult::NotPermitted)
126        }
127    }
128}
129
130/// A security policy. Security policies contain a number of security [Feature]s.
131#[derive(Clone, Debug)]
132pub struct Policy<N: Node> {
133    name: QName,
134    features: HashMap<QName, Feature<N>>,
135}
136
137impl<N: Node> Policy<N> {
138    /// Create a new security policy
139    pub fn new(name: QName) -> Self {
140        Self {
141            name,
142            features: HashMap::new(),
143        }
144    }
145    /// Get the name of the security policy
146    pub fn name(&self) -> QName {
147        self.name.clone()
148    }
149    /// Add a [Feature] to the security policy
150    pub fn add(&mut self, name: QName, f: Feature<N>) {
151        self.features.insert(name, f);
152    }
153    /// Get a [Feature] of the security policy
154    pub fn feature(&self, name: &QName) -> Option<&Feature<N>> {
155        self.features.get(name)
156    }
157    /*
158    /// Get all of the [Feature]s of the security policy
159    /// TODO: make this an iterator
160    pub fn all_features(&self) -> Vec<&Feature<N>> {
161        self.features.iter().map(|(_, f)| f).collect()
162    }
163    */
164    /// Resolve the setting of a security [Feature].
165    pub fn get(&self, name: &QName, a: ActualParameters<N>) -> Result<SecurityResult, Error> {
166        self.features
167            .get(name)
168            .map_or_else(|| Ok(SecurityResult::NotPermitted), |f| f.get(a))
169    }
170}
171
172/// A security feature. These limit or constrain acccess to a resource.
173/// Access to a resource may, or may not, be permitted.
174/// If access is permitted, then it may also be constrained so some maximum value.
175/// This value is computed dynamically using a [Transform].
176/// If no [Transform] is given then the access to the resource is unlimited.
177#[derive(Clone, Debug)]
178pub enum Feature<N: Node> {
179    Permitted(Option<Transform<N>>),
180    NotPermitted,
181}
182
183impl<N: Node> Feature<N> {
184    /// Determine whether this security feature is permitted,
185    /// and if so then to what limit, i.e. a maximum value.
186    pub fn get(&self, a: ActualParameters<N>) -> Result<SecurityResult, Error> {
187        match self {
188            Feature::NotPermitted => Ok(SecurityResult::NotPermitted),
189            Feature::Permitted(o) => Ok(SecurityResult::Permitted(if let Some(t) = o {
190                // The template is a callable.
191                // If the transformation results in an error then that it propegated back via the result
192                let mut stctxt = StaticContextBuilder::new()
193                    .message(|_| Ok(()))
194                    .parser(|_| Err(Error::new(ErrorKind::NotImplemented, "not implemented")))
195                    .fetcher(|_: &_| Err(Error::new(ErrorKind::NotImplemented, "not implemented")))
196                    .build();
197                let mut ctxt = Context::new();
198                //let mut actuals = HashMap::new();
199                if let ActualParameters::Named(ap) = a {
200                    ap.iter().try_for_each(|(an, av)| {
201                        ctxt.var_push(an.to_string(), ctxt.dispatch(&mut stctxt, av)?);
202                        //actuals.insert(an, ctxt.dispatch(&mut stctxt, av)?);
203                        Ok(())
204                    })?
205                }
206                // Now evaluate the template.
207                // How to decide whether to return a (Not)Permitted result or a value?
208                // If the singleton result is a boolean, then (Not)Permitted otherwise value.
209                let r = ctxt.dispatch(&mut stctxt, t)?;
210                // TODO: should the return value be the original Sequence?
211                Some(r.to_string())
212            } else {
213                None
214            })),
215        }
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use crate::item::Item;
223    use crate::trees::nullo::Nullo;
224    use crate::value::Value;
225    use std::rc::Rc;
226
227    #[test]
228    fn feature_get_np() {
229        let f: Feature<Nullo> = Feature::NotPermitted;
230        assert_eq!(
231            f.get(ActualParameters::Named(vec![]))
232                .expect("unable to determine status of security feature"),
233            SecurityResult::NotPermitted
234        )
235    }
236
237    #[test]
238    fn feature_get_unlimited() {
239        let f: Feature<Nullo> = Feature::Permitted(None);
240        assert_eq!(
241            f.get(ActualParameters::Named(vec![]))
242                .expect("unable to determine status of security feature"),
243            SecurityResult::Permitted(None)
244        )
245    }
246
247    #[test]
248    fn feature_get_limited() {
249        let f: Feature<Nullo> = Feature::Permitted(Some(Transform::Literal(Item::Value(Rc::new(
250            Value::from(1234),
251        )))));
252        assert_eq!(
253            f.get(ActualParameters::Named(vec![]))
254                .expect("unable to determine status of security feature"),
255            SecurityResult::Permitted(Some(String::from("1234")))
256        )
257    }
258}