backbone-bucket 0.3.1

Bucket Bounded Context: File Storage Module for Backbone Framework
Documentation
//! Permission guard for FileShare
//!
//! Generated by backbone-schema

use super::{Action, FieldRestriction, PermissionChecker, PermissionError, PermissionResult};
use std::collections::{HashMap, HashSet};

#[derive(Debug, Clone, Default)]
struct FileShareRoleRules {
    allowed_actions: HashSet<Action>,
    denied_actions: HashSet<Action>,
    field_restrictions: HashMap<Action, FieldRestriction>,
}

/// Permission guard for FileShare resources
#[derive(Debug, Clone)]
pub struct FileSharePermissions {
    rules: HashMap<String, FileShareRoleRules>,
}

impl FileSharePermissions {
    /// Create a new permission guard with default rules
    pub fn new() -> Self {
        let mut rules = HashMap::new();

        // super_admin role
        {
            let mut role_perms = FileShareRoleRules::default();
            for action in Action::all() {
                role_perms.allowed_actions.insert(action);
            }
            rules.insert("super_admin".to_string(), role_perms);
        }
        // admin role
        {
            let mut role_perms = FileShareRoleRules::default();
            role_perms.allowed_actions.insert(Action::Read);
            role_perms.allowed_actions.insert(Action::Create);
            role_perms.allowed_actions.insert(Action::Update);
            role_perms.allowed_actions.insert(Action::Delete);
            role_perms.allowed_actions.insert(Action::List);
            // TODO: Custom action 'revoke' - implement separately
            rules.insert("admin".to_string(), role_perms);
        }
        // user role
        {
            let mut role_perms = FileShareRoleRules::default();
            role_perms.allowed_actions.insert(Action::Create);
            role_perms.allowed_actions.insert(Action::Read);
            role_perms.allowed_actions.insert(Action::Update);
            // TODO: Custom action 'revoke' - implement separately
            role_perms.allowed_actions.insert(Action::List);
            role_perms.denied_actions.insert(Action::Delete);
            rules.insert("user".to_string(), role_perms);
        }
        // guest role
        {
            let mut role_perms = FileShareRoleRules::default();
            // TODO: Custom action 'access' - implement separately
            role_perms.denied_actions.insert(Action::Create);
            role_perms.denied_actions.insert(Action::Update);
            role_perms.denied_actions.insert(Action::Delete);
            // TODO: Custom action 'revoke' - implement separately
            role_perms.denied_actions.insert(Action::List);
            rules.insert("guest".to_string(), role_perms);
        }

        Self { rules }
    }

    /// Check and authorize an action
    pub fn authorize(&self, role: &str, action: Action) -> PermissionResult {
        if !self.can(role, action) {
            return Err(PermissionError::ActionNotAllowed {
                action,
                role: role.to_string(),
            });
        }
        Ok(())
    }

    /// Check and authorize field access
    pub fn authorize_field(&self, role: &str, action: Action, field: &str) -> PermissionResult {
        self.authorize(role, action)?;

        if !self.can_access_field(role, action, field) {
            return Err(PermissionError::FieldNotAccessible {
                field: field.to_string(),
                action,
            });
        }
        Ok(())
    }

    /// Get field restrictions for a role and action
    pub fn field_restrictions(&self, role: &str, action: Action) -> Option<&FieldRestriction> {
        self.rules.get(role).and_then(|r| r.field_restrictions.get(&action))
    }
}

impl Default for FileSharePermissions {
    fn default() -> Self {
        Self::new()
    }
}

impl PermissionChecker for FileSharePermissions {
    fn can(&self, role: &str, action: Action) -> bool {
        let Some(role_perms) = self.rules.get(role) else {
            return false;
        };

        // Deny takes precedence over allow
        if role_perms.denied_actions.contains(&action) {
            return false;
        }

        role_perms.allowed_actions.contains(&action)
    }

    fn can_access_field(&self, role: &str, action: Action, field: &str) -> bool {
        if !self.can(role, action) {
            return false;
        }

        let Some(role_perms) = self.rules.get(role) else {
            return false;
        };

        match role_perms.field_restrictions.get(&action) {
            Some(restriction) => restriction.is_allowed(field),
            None => true, // No restrictions means all fields allowed
        }
    }

    fn allowed_fields(&self, role: &str, action: Action) -> Vec<String> {
        let Some(role_perms) = self.rules.get(role) else {
            return vec![];
        };

        match role_perms.field_restrictions.get(&action) {
            Some(FieldRestriction::Only(fields)) => fields.iter().cloned().collect(),
            _ => vec![], // Return empty for All or Except (requires field list)
        }
    }

    fn roles(&self) -> Vec<&str> {
        self.rules.keys().map(|s| s.as_str()).collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_permissions() {
        let perms = FileSharePermissions::new();
        let roles = perms.roles();
        assert!(!roles.is_empty());
    }

    #[test]
    fn test_super_admin_role() {
        let _perms = FileSharePermissions::new();
    }

    #[test]
    fn test_admin_role() {
        let perms = FileSharePermissions::new();
        assert!(perms.can("admin", Action::Read));
    }

    #[test]
    fn test_user_role() {
        let perms = FileSharePermissions::new();
        assert!(perms.can("user", Action::Create));
        assert!(!perms.can("user", Action::Delete));
    }

    #[test]
    fn test_guest_role() {
        let perms = FileSharePermissions::new();
        assert!(!perms.can("guest", Action::Create));
    }

    #[test]
    fn test_unknown_role() {
        let perms = FileSharePermissions::new();
        assert!(!perms.can("unknown_role", Action::Read));
    }
}