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}