actionable/
permissions.rs

1use std::{collections::HashMap, sync::Arc};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    statement::Configuration, Action, ActionNameList, Identifier, PermissionDenied, ResourceName,
7    Statement,
8};
9
10/// A collection of allowed permissions. This is constructed from a
11/// `Vec<`[`Statement`]`>`. By default, no actions are allowed on any resources.
12#[derive(Default, Debug, Clone, Serialize, Deserialize)]
13pub struct Permissions {
14    data: Arc<Data>,
15}
16
17#[derive(Default, Debug, Clone, Serialize, Deserialize)]
18struct Data {
19    children: Option<HashMap<Identifier<'static>, Data>>,
20    allowed: AllowedActions,
21    configuration: Option<HashMap<String, Configuration>>,
22}
23
24impl Permissions {
25    /// Returns a `Permisions` instance constructed with
26    /// [`Statement::allow_all()`].
27    #[must_use]
28    pub fn allow_all() -> Self {
29        Self::from(vec![Statement::allow_all_for_any_resource()])
30    }
31
32    /// Evaluate whether the `action` is allowed to be taken upon
33    /// `resource_name`. Returns `Ok` if permission is allowed.
34    ///
35    /// # Errors
36    ///
37    /// Returns `PermissionDenied` if permission is now allowed.
38    pub fn check<'a, R: AsRef<[Identifier<'a>]>, P: Action>(
39        &self,
40        resource_name: R,
41        action: &P,
42    ) -> Result<(), PermissionDenied> {
43        if self.data.allowed_to(resource_name.as_ref(), action) {
44            Ok(())
45        } else {
46            Err(PermissionDenied {
47                resource: ResourceName::from(resource_name.as_ref()).to_owned(),
48                action: action.name(),
49            })
50        }
51    }
52
53    /// Evaluate whether the `action` is allowed to be taken upon
54    /// `resource_name`. Returns true if the action should be allowed. If no
55    /// statements that match `resource_name` allow `action`, false will be
56    /// returned.
57    pub fn allowed_to<'a, R: AsRef<[Identifier<'a>]>, P: Action>(
58        &self,
59        resource_name: R,
60        action: &P,
61    ) -> bool {
62        self.data.allowed_to(resource_name, action)
63    }
64
65    /// Looks up a configured value for `resource_name`.
66    #[must_use]
67    pub fn get<'a: 's, 's, R: AsRef<[Identifier<'a>]>>(
68        &'s self,
69        resource_name: R,
70        key: &str,
71    ) -> Option<&'s Configuration> {
72        self.data.get(resource_name, key)
73    }
74
75    /// Returns a new instance that merges all allowed actions from
76    /// `permissions`.
77    #[must_use]
78    pub fn merged<'a>(permissions: impl IntoIterator<Item = &'a Self>) -> Self {
79        let mut combined = Data::default();
80        for incoming in permissions {
81            combined.add_permissions(&incoming.data);
82        }
83        Self {
84            data: Arc::new(combined),
85        }
86    }
87}
88
89impl Data {
90    fn add_permissions(&mut self, permissions: &Self) {
91        if let Some(children) = &permissions.children {
92            let our_children = self.children.get_or_insert_with(HashMap::new);
93            for (name, permissions) in children {
94                let our_permissions = our_children.entry(name.clone()).or_default();
95                our_permissions.add_permissions(permissions);
96            }
97        }
98
99        self.allowed.add_allowed(&permissions.allowed);
100        if let Some(incoming_configuration) = &permissions.configuration {
101            if let Some(configuration) = &mut self.configuration {
102                for (key, value) in incoming_configuration {
103                    configuration
104                        .entry(key.clone())
105                        .or_insert_with(|| value.clone());
106                }
107            } else {
108                self.configuration = permissions.configuration.clone();
109            }
110        }
111    }
112
113    fn allowed_to<'a, R: AsRef<[Identifier<'a>]>, P: Action>(
114        &self,
115        resource_name: R,
116        action: &P,
117    ) -> bool {
118        let resource_name = resource_name.as_ref();
119        // This function checks all possible matches of `resource_name` by using
120        // recursion to call itself for each entry in `resource_name`. This
121        // first block does the function call recursion. The second block checks
122        // `action`.
123        if let Some(resource) = resource_name.first() {
124            if let Some(children) = &self.children {
125                let remaining_resource = &resource_name[1..resource_name.len()];
126                // Check if there are entries for this resource segment.
127                if let Some(permissions) = children.get(resource) {
128                    if permissions.allowed_to(remaining_resource, action) {
129                        return true;
130                    }
131                }
132
133                // Check if there are entries for `Any`.
134                if let Some(permissions) = children.get(&Identifier::Any) {
135                    if permissions.allowed_to(remaining_resource, action) {
136                        return true;
137                    }
138                }
139            }
140        }
141
142        // When execution reaches here, either resource_name is empty, or none
143        // of the previous paths have reached an "allow" state. The purpose of
144        // this chunk of code is to determine if this action is allowed based on
145        // this node's list of approved actions. This is also evaluated
146        // recursively, but at any stage if we reach match (positive or
147        // negative), we we can return.
148        let mut allowed = &self.allowed;
149        for name in action.name().0 {
150            allowed = match allowed {
151                AllowedActions::None => return false,
152                AllowedActions::All => return true,
153                AllowedActions::Some(actions) => {
154                    if let Some(children_allowed) = actions.get(name.as_ref()) {
155                        children_allowed
156                    } else {
157                        return false;
158                    }
159                }
160            };
161        }
162        matches!(allowed, AllowedActions::All)
163    }
164
165    #[must_use]
166    pub fn get<'a: 's, 's, R: AsRef<[Identifier<'a>]>>(
167        &'s self,
168        resource_name: R,
169        key: &str,
170    ) -> Option<&'s Configuration> {
171        let resource_name = resource_name.as_ref();
172        // This function checks all possible matches of `resource_name` by using
173        // recursion to call itself for each entry in `resource_name`. This
174        // first block does the function call recursion. The second block checks
175        // `action`.
176        if let Some(resource) = resource_name.as_ref().first() {
177            if let Some(children) = &self.children {
178                let remaining_resource = &resource_name[1..resource_name.len()];
179                // Check if there are entries for this resource segment.
180                if let Some(permissions) = children.get(resource) {
181                    if let Some(config) = permissions.get(remaining_resource, key) {
182                        return Some(config);
183                    }
184                }
185
186                // Check if there are entries for `Any`.
187                if let Some(permissions) = children.get(&Identifier::Any) {
188                    if let Some(config) = permissions.get(remaining_resource, key) {
189                        return Some(config);
190                    }
191                }
192            }
193        }
194
195        self.configuration
196            .as_ref()
197            .and_then(|configs| configs.get(key))
198    }
199}
200
201impl From<Statement> for Permissions {
202    fn from(stmt: Statement) -> Self {
203        Self::from(vec![stmt])
204    }
205}
206
207impl From<Vec<Statement>> for Permissions {
208    fn from(statements: Vec<Statement>) -> Self {
209        let mut permissions = Data::default();
210        for statement in statements {
211            // Apply this statement to all resources
212            for resource in statement.resources {
213                let mut current_permissions = &mut permissions;
214                // Look up the permissions for the resource path
215                for name in resource {
216                    let permissions = current_permissions
217                        .children
218                        .get_or_insert_with(HashMap::default);
219                    current_permissions = permissions.entry(name).or_default();
220                }
221
222                // Apply the "allowed" status to each action in this resource.
223                match &statement.actions {
224                    Some(ActionNameList::List(actions)) =>
225                        for action in actions {
226                            let mut allowed = &mut current_permissions.allowed;
227                            for name in &action.0 {
228                                let action_map = match allowed {
229                                    AllowedActions::All | AllowedActions::None => {
230                                        *allowed = {
231                                            let mut action_map = HashMap::new();
232                                            action_map
233                                                .insert(name.to_string(), AllowedActions::None);
234                                            AllowedActions::Some(action_map)
235                                        };
236                                        if let AllowedActions::Some(action_map) = allowed {
237                                            action_map
238                                        } else {
239                                            unreachable!()
240                                        }
241                                    }
242                                    AllowedActions::Some(action_map) => action_map,
243                                };
244                                allowed = action_map.entry(name.to_string()).or_default();
245                            }
246                            *allowed = AllowedActions::All;
247                        },
248                    Some(ActionNameList::All) => {
249                        current_permissions.allowed = AllowedActions::All;
250                    }
251                    None => {}
252                }
253
254                if let Some(incoming_configs) = &statement.configuration {
255                    let configuration = current_permissions
256                        .configuration
257                        .get_or_insert_with(HashMap::default);
258                    for (key, value) in incoming_configs {
259                        configuration
260                            .entry(key.clone())
261                            .or_insert_with(|| value.clone());
262                    }
263                }
264            }
265        }
266        Self {
267            data: Arc::new(permissions),
268        }
269    }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
273enum AllowedActions {
274    None,
275    Some(HashMap<String, AllowedActions>),
276    All,
277}
278
279impl Default for AllowedActions {
280    fn default() -> Self {
281        Self::None
282    }
283}
284
285impl AllowedActions {
286    fn add_allowed(&mut self, other: &Self) {
287        match other {
288            Self::None => {}
289            Self::Some(actions) =>
290                if !matches!(self, Self::All) {
291                    if let Self::Some(our_allowed) = self {
292                        for (name, allowed) in actions {
293                            let our_entry = our_allowed.entry(name.clone()).or_default();
294                            our_entry.add_allowed(allowed);
295                        }
296                    } else {
297                        *self = Self::Some(actions.clone());
298                    }
299                },
300            Self::All => {
301                *self = Self::All;
302            }
303        }
304    }
305}