Lines
93.58 %
Functions
32.14 %
Branches
100 %
use std::{collections::HashMap, sync::Arc};
use serde::{Deserialize, Serialize};
use crate::{
statement::Configuration, Action, ActionNameList, Identifier, PermissionDenied, ResourceName,
Statement,
};
/// A collection of allowed permissions. This is constructed from a
/// `Vec<`[`Statement`]`>`. By default, no actions are allowed on any resources.
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct Permissions {
data: Arc<Data>,
}
struct Data {
children: Option<HashMap<Identifier<'static>, Data>>,
allowed: AllowedActions,
configuration: Option<HashMap<String, Configuration>>,
impl Permissions {
/// Returns a `Permisions` instance constructed with
/// [`Statement::allow_all()`].
#[must_use]
pub fn allow_all() -> Self {
Self::from(vec![Statement::allow_all_for_any_resource()])
/// Evaluate whether the `action` is allowed to be taken upon
/// `resource_name`. Returns `Ok` if permission is allowed.
///
/// # Errors
/// Returns `PermissionDenied` if permission is now allowed.
pub fn check<'a, R: AsRef<[Identifier<'a>]>, P: Action>(
&self,
resource_name: R,
action: &P,
) -> Result<(), PermissionDenied> {
if self.data.allowed_to(resource_name.as_ref(), action) {
Ok(())
} else {
Err(PermissionDenied {
resource: ResourceName::from(resource_name.as_ref()).to_owned(),
action: action.name(),
})
/// `resource_name`. Returns true if the action should be allowed. If no
/// statements that match `resource_name` allow `action`, false will be
/// returned.
pub fn allowed_to<'a, R: AsRef<[Identifier<'a>]>, P: Action>(
) -> bool {
self.data.allowed_to(resource_name, action)
/// Looks up a configured value for `resource_name`.
pub fn get<'a: 's, 's, R: AsRef<[Identifier<'a>]>>(
&'s self,
key: &str,
) -> Option<&'s Configuration> {
self.data.get(resource_name, key)
/// Returns a new instance that merges all allowed actions from
/// `permissions`.
pub fn merged<'a>(permissions: impl IntoIterator<Item = &'a Self>) -> Self {
let mut combined = Data::default();
for incoming in permissions {
combined.add_permissions(&incoming.data);
Self {
data: Arc::new(combined),
impl Data {
fn add_permissions(&mut self, permissions: &Self) {
if let Some(children) = &permissions.children {
let our_children = self.children.get_or_insert_with(HashMap::new);
for (name, permissions) in children {
let our_permissions = our_children.entry(name.clone()).or_default();
our_permissions.add_permissions(permissions);
self.allowed.add_allowed(&permissions.allowed);
if let Some(incoming_configuration) = &permissions.configuration {
if let Some(configuration) = &mut self.configuration {
for (key, value) in incoming_configuration {
configuration
.entry(key.clone())
.or_insert_with(|| value.clone());
self.configuration = permissions.configuration.clone();
fn allowed_to<'a, R: AsRef<[Identifier<'a>]>, P: Action>(
let resource_name = resource_name.as_ref();
// This function checks all possible matches of `resource_name` by using
// recursion to call itself for each entry in `resource_name`. This
// first block does the function call recursion. The second block checks
// `action`.
if let Some(resource) = resource_name.first() {
if let Some(children) = &self.children {
let remaining_resource = &resource_name[1..resource_name.len()];
// Check if there are entries for this resource segment.
if let Some(permissions) = children.get(resource) {
if permissions.allowed_to(remaining_resource, action) {
return true;
// Check if there are entries for `Any`.
if let Some(permissions) = children.get(&Identifier::Any) {
// When execution reaches here, either resource_name is empty, or none
// of the previous paths have reached an "allow" state. The purpose of
// this chunk of code is to determine if this action is allowed based on
// this node's list of approved actions. This is also evaluated
// recursively, but at any stage if we reach match (positive or
// negative), we we can return.
let mut allowed = &self.allowed;
for name in action.name().0 {
allowed = match allowed {
AllowedActions::None => return false,
AllowedActions::All => return true,
AllowedActions::Some(actions) => {
if let Some(children_allowed) = actions.get(name.as_ref()) {
children_allowed
return false;
matches!(allowed, AllowedActions::All)
if let Some(resource) = resource_name.as_ref().first() {
if let Some(config) = permissions.get(remaining_resource, key) {
return Some(config);
self.configuration
.as_ref()
.and_then(|configs| configs.get(key))
impl From<Statement> for Permissions {
fn from(stmt: Statement) -> Self {
Self::from(vec![stmt])
impl From<Vec<Statement>> for Permissions {
fn from(statements: Vec<Statement>) -> Self {
let mut permissions = Data::default();
for statement in statements {
// Apply this statement to all resources
for resource in statement.resources {
let mut current_permissions = &mut permissions;
// Look up the permissions for the resource path
for name in resource {
let permissions = current_permissions
.children
.get_or_insert_with(HashMap::default);
current_permissions = permissions.entry(name).or_default();
// Apply the "allowed" status to each action in this resource.
match &statement.actions {
Some(ActionNameList::List(actions)) =>
for action in actions {
let mut allowed = &mut current_permissions.allowed;
for name in &action.0 {
let action_map = match allowed {
AllowedActions::All | AllowedActions::None => {
*allowed = {
let mut action_map = HashMap::new();
action_map
.insert(name.to_string(), AllowedActions::None);
AllowedActions::Some(action_map)
if let AllowedActions::Some(action_map) = allowed {
unreachable!()
AllowedActions::Some(action_map) => action_map,
allowed = action_map.entry(name.to_string()).or_default();
*allowed = AllowedActions::All;
},
Some(ActionNameList::All) => {
current_permissions.allowed = AllowedActions::All;
None => {}
if let Some(incoming_configs) = &statement.configuration {
let configuration = current_permissions
.configuration
for (key, value) in incoming_configs {
data: Arc::new(permissions),
#[derive(Debug, Clone, Serialize, Deserialize)]
enum AllowedActions {
None,
Some(HashMap<String, AllowedActions>),
All,
impl Default for AllowedActions {
fn default() -> Self {
Self::None
impl AllowedActions {
fn add_allowed(&mut self, other: &Self) {
match other {
Self::None => {}
Self::Some(actions) =>
if !matches!(self, Self::All) {
if let Self::Some(our_allowed) = self {
for (name, allowed) in actions {
let our_entry = our_allowed.entry(name.clone()).or_default();
our_entry.add_allowed(allowed);
*self = Self::Some(actions.clone());
Self::All => {
*self = Self::All;