Skip to main content

peat_protocol/security/
authorization.rs

1//! Role-Based Access Control (RBAC) for Peat Protocol.
2//!
3//! Implements ADR-006 Layer 4: Role-Based Authorization.
4//!
5//! # Overview
6//!
7//! This module provides role-based access control for Peat Protocol operations.
8//! Each authenticated entity (device or user) has roles that determine what
9//! permissions they have for various operations.
10//!
11//! # Roles
12//!
13//! - **Leader**: Squad/cell leader - can command cell, set objectives
14//! - **Member**: Squad/cell member - participates in missions
15//! - **Observer**: Read-only access to cell state
16//! - **Commander**: Mission commander - can direct multiple cells
17//! - **Admin**: System configuration access
18//!
19//! # Example
20//!
21//! ```ignore
22//! use peat_protocol::security::{
23//!     Role, Permission, AuthorizationController, AuthorizationContext,
24//!     AuthenticatedEntity,
25//! };
26//!
27//! let controller = AuthorizationController::with_default_policy();
28//!
29//! // Check if a leader can set cell objectives
30//! let entity = AuthenticatedEntity::Device(device_identity);
31//! let context = AuthorizationContext::for_cell(&cell_id);
32//!
33//! match controller.check_permission(&entity, Permission::SetCellObjective, &context) {
34//!     Ok(()) => println!("Permission granted"),
35//!     Err(e) => println!("Permission denied: {}", e),
36//! }
37//! ```
38
39use super::device_id::DeviceId;
40use super::error::SecurityError;
41use serde::{Deserialize, Serialize};
42use std::collections::{HashMap, HashSet};
43use std::fmt;
44use std::time::SystemTime;
45
46/// Roles in Peat Protocol.
47///
48/// Roles determine what permissions an entity has for various operations.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
50pub enum Role {
51    /// Squad/cell leader - can command cell, set objectives
52    Leader,
53
54    /// Squad/cell member - participates in missions
55    Member,
56
57    /// Observer - can view but not command
58    Observer,
59
60    /// Mission commander - can direct multiple cells
61    Commander,
62
63    /// Administrator - can configure system
64    Admin,
65}
66
67impl fmt::Display for Role {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        match self {
70            Role::Leader => write!(f, "Leader"),
71            Role::Member => write!(f, "Member"),
72            Role::Observer => write!(f, "Observer"),
73            Role::Commander => write!(f, "Commander"),
74            Role::Admin => write!(f, "Admin"),
75        }
76    }
77}
78
79/// Permissions that can be checked for authorization.
80///
81/// These permissions cover all security-relevant operations in Peat Protocol.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
83pub enum Permission {
84    // Cell operations
85    /// Permission to join a cell
86    JoinCell,
87    /// Permission to leave a cell
88    LeaveCell,
89    /// Permission to create a new cell
90    CreateCell,
91    /// Permission to disband an existing cell
92    DisbandCell,
93    /// Permission to set a cell's leader
94    SetCellLeader,
95    /// Permission to set a cell's objective
96    SetCellObjective,
97
98    // Capability operations
99    /// Permission to advertise capabilities
100    AdvertiseCapability,
101    /// Permission to request capabilities from others
102    RequestCapability,
103
104    // Data access
105    /// Permission to read cell state
106    ReadCellState,
107    /// Permission to write/modify cell state
108    WriteCellState,
109    /// Permission to read node state
110    ReadNodeState,
111    /// Permission to write/modify node state
112    WriteNodeState,
113    /// Permission to read telemetry data
114    ReadTelemetry,
115
116    // Hierarchical operations
117    /// Permission to form a platoon from cells
118    FormPlatoon,
119    /// Permission to aggregate data to company level
120    AggregateToCompany,
121
122    // Human-in-the-loop operations
123    /// Permission to approve cell formation
124    ApproveFormation,
125    /// Permission to veto autonomous commands
126    VetoCommand,
127
128    // Administration
129    /// Permission to configure network settings
130    ConfigureNetwork,
131    /// Permission to manage cryptographic keys
132    ManageKeys,
133    /// Permission to view audit logs
134    ViewAuditLog,
135}
136
137impl fmt::Display for Permission {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        match self {
140            Permission::JoinCell => write!(f, "JoinCell"),
141            Permission::LeaveCell => write!(f, "LeaveCell"),
142            Permission::CreateCell => write!(f, "CreateCell"),
143            Permission::DisbandCell => write!(f, "DisbandCell"),
144            Permission::SetCellLeader => write!(f, "SetCellLeader"),
145            Permission::SetCellObjective => write!(f, "SetCellObjective"),
146            Permission::AdvertiseCapability => write!(f, "AdvertiseCapability"),
147            Permission::RequestCapability => write!(f, "RequestCapability"),
148            Permission::ReadCellState => write!(f, "ReadCellState"),
149            Permission::WriteCellState => write!(f, "WriteCellState"),
150            Permission::ReadNodeState => write!(f, "ReadNodeState"),
151            Permission::WriteNodeState => write!(f, "WriteNodeState"),
152            Permission::ReadTelemetry => write!(f, "ReadTelemetry"),
153            Permission::FormPlatoon => write!(f, "FormPlatoon"),
154            Permission::AggregateToCompany => write!(f, "AggregateToCompany"),
155            Permission::ApproveFormation => write!(f, "ApproveFormation"),
156            Permission::VetoCommand => write!(f, "VetoCommand"),
157            Permission::ConfigureNetwork => write!(f, "ConfigureNetwork"),
158            Permission::ManageKeys => write!(f, "ManageKeys"),
159            Permission::ViewAuditLog => write!(f, "ViewAuditLog"),
160        }
161    }
162}
163
164/// An authenticated entity that can be authorized for operations.
165#[derive(Debug, Clone)]
166pub enum AuthenticatedEntity {
167    /// A device identified by its DeviceId
168    Device(DeviceIdentityInfo),
169
170    /// A human user (placeholder for Phase 3)
171    User(UserIdentityInfo),
172}
173
174impl AuthenticatedEntity {
175    /// Get the entity's identifier as a string.
176    pub fn id(&self) -> String {
177        match self {
178            AuthenticatedEntity::Device(info) => info.device_id.to_hex(),
179            AuthenticatedEntity::User(info) => info.username.clone(),
180        }
181    }
182
183    /// Create a device entity from a DeviceId.
184    pub fn from_device_id(device_id: DeviceId) -> Self {
185        AuthenticatedEntity::Device(DeviceIdentityInfo {
186            device_id,
187            device_type: DeviceType::Unknown,
188        })
189    }
190}
191
192/// Device identity information for authorization.
193#[derive(Debug, Clone)]
194pub struct DeviceIdentityInfo {
195    /// The device's unique identifier
196    pub device_id: DeviceId,
197
198    /// The type of device
199    pub device_type: DeviceType,
200}
201
202/// Types of devices in Peat Protocol.
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
204pub enum DeviceType {
205    /// Unmanned Aerial Vehicle
206    Uav,
207    /// Unmanned Ground Vehicle
208    Ugv,
209    /// Command and Control station
210    C2Station,
211    /// Sensor platform
212    Sensor,
213    /// Communications relay
214    Relay,
215    /// Unknown device type
216    Unknown,
217}
218
219/// User identity information (placeholder for Phase 3).
220#[derive(Debug, Clone)]
221pub struct UserIdentityInfo {
222    /// Username or call sign
223    pub username: String,
224
225    /// User's roles
226    pub roles: HashSet<Role>,
227}
228
229/// Context for authorization decisions.
230///
231/// Provides situational information needed to determine roles and permissions.
232#[derive(Debug, Clone)]
233pub struct AuthorizationContext {
234    /// Cell being accessed (if applicable)
235    pub cell_id: Option<String>,
236
237    /// Node being accessed (if applicable)
238    pub node_id: Option<String>,
239
240    /// Hierarchy level of the operation
241    pub hierarchy_level: Option<HierarchyLevel>,
242
243    /// Time of access
244    pub timestamp: SystemTime,
245
246    /// Additional context for role determination
247    pub cell_membership: Option<CellMembershipContext>,
248}
249
250impl AuthorizationContext {
251    /// Create a context for a cell operation.
252    pub fn for_cell(cell_id: &str) -> Self {
253        Self {
254            cell_id: Some(cell_id.to_string()),
255            node_id: None,
256            hierarchy_level: Some(HierarchyLevel::Squad),
257            timestamp: SystemTime::now(),
258            cell_membership: None,
259        }
260    }
261
262    /// Create a context for a node operation.
263    pub fn for_node(node_id: &str) -> Self {
264        Self {
265            cell_id: None,
266            node_id: Some(node_id.to_string()),
267            hierarchy_level: None,
268            timestamp: SystemTime::now(),
269            cell_membership: None,
270        }
271    }
272
273    /// Create an empty context (for system-wide operations).
274    pub fn system() -> Self {
275        Self {
276            cell_id: None,
277            node_id: None,
278            hierarchy_level: None,
279            timestamp: SystemTime::now(),
280            cell_membership: None,
281        }
282    }
283
284    /// Add cell membership information for role determination.
285    pub fn with_membership(mut self, membership: CellMembershipContext) -> Self {
286        self.cell_membership = Some(membership);
287        self
288    }
289}
290
291/// Context about cell membership for determining device roles.
292#[derive(Debug, Clone)]
293pub struct CellMembershipContext {
294    /// The leader's device ID (as hex string)
295    pub leader_id: Option<String>,
296
297    /// Member device IDs (as hex strings)
298    pub member_ids: HashSet<String>,
299}
300
301impl CellMembershipContext {
302    /// Create a new membership context.
303    pub fn new(leader_id: Option<String>, member_ids: HashSet<String>) -> Self {
304        Self {
305            leader_id,
306            member_ids,
307        }
308    }
309
310    /// Check if a device is the leader.
311    pub fn is_leader(&self, device_id: &str) -> bool {
312        self.leader_id.as_ref() == Some(&device_id.to_string())
313    }
314
315    /// Check if a device is a member.
316    pub fn is_member(&self, device_id: &str) -> bool {
317        self.member_ids.contains(device_id)
318    }
319}
320
321/// Organizational hierarchy levels.
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
323pub enum HierarchyLevel {
324    /// Individual node
325    Node,
326    /// Squad level (cell)
327    Squad,
328    /// Platoon level (aggregated squads)
329    Platoon,
330    /// Company level (aggregated platoons)
331    Company,
332    /// Battalion level
333    Battalion,
334}
335
336/// Authorization policy defining role-to-permission mappings.
337#[derive(Debug, Clone)]
338pub struct AuthorizationPolicy {
339    /// Role to permissions mapping
340    role_permissions: HashMap<Role, HashSet<Permission>>,
341}
342
343impl AuthorizationPolicy {
344    /// Create an empty policy.
345    pub fn new() -> Self {
346        Self {
347            role_permissions: HashMap::new(),
348        }
349    }
350
351    /// Create the default Peat Protocol authorization policy.
352    ///
353    /// This implements the policy defined in ADR-006.
354    pub fn default_policy() -> Self {
355        let mut policy = Self::new();
356
357        // Leader permissions - can command cell
358        policy.grant_role(Role::Leader, Permission::SetCellObjective);
359        policy.grant_role(Role::Leader, Permission::SetCellLeader);
360        policy.grant_role(Role::Leader, Permission::RequestCapability);
361        policy.grant_role(Role::Leader, Permission::ReadCellState);
362        policy.grant_role(Role::Leader, Permission::WriteCellState);
363        policy.grant_role(Role::Leader, Permission::ReadNodeState);
364        policy.grant_role(Role::Leader, Permission::WriteNodeState);
365        policy.grant_role(Role::Leader, Permission::ReadTelemetry);
366        policy.grant_role(Role::Leader, Permission::DisbandCell);
367
368        // Member permissions - participate in missions
369        policy.grant_role(Role::Member, Permission::JoinCell);
370        policy.grant_role(Role::Member, Permission::LeaveCell);
371        policy.grant_role(Role::Member, Permission::AdvertiseCapability);
372        policy.grant_role(Role::Member, Permission::ReadCellState);
373        policy.grant_role(Role::Member, Permission::WriteNodeState);
374        policy.grant_role(Role::Member, Permission::ReadNodeState);
375        policy.grant_role(Role::Member, Permission::ReadTelemetry);
376
377        // Observer permissions - read-only
378        policy.grant_role(Role::Observer, Permission::ReadCellState);
379        policy.grant_role(Role::Observer, Permission::ReadNodeState);
380        policy.grant_role(Role::Observer, Permission::ReadTelemetry);
381
382        // Commander permissions - hierarchical operations
383        policy.grant_role(Role::Commander, Permission::FormPlatoon);
384        policy.grant_role(Role::Commander, Permission::AggregateToCompany);
385        policy.grant_role(Role::Commander, Permission::ApproveFormation);
386        policy.grant_role(Role::Commander, Permission::VetoCommand);
387        policy.grant_role(Role::Commander, Permission::CreateCell);
388        policy.grant_role(Role::Commander, Permission::ReadCellState);
389        policy.grant_role(Role::Commander, Permission::WriteCellState);
390        policy.grant_role(Role::Commander, Permission::ReadNodeState);
391        policy.grant_role(Role::Commander, Permission::ReadTelemetry);
392
393        // Admin permissions - system-wide
394        policy.grant_role(Role::Admin, Permission::ConfigureNetwork);
395        policy.grant_role(Role::Admin, Permission::ManageKeys);
396        policy.grant_role(Role::Admin, Permission::ViewAuditLog);
397        policy.grant_role(Role::Admin, Permission::CreateCell);
398        policy.grant_role(Role::Admin, Permission::DisbandCell);
399
400        policy
401    }
402
403    /// Grant a permission to a role.
404    pub fn grant_role(&mut self, role: Role, permission: Permission) {
405        self.role_permissions
406            .entry(role)
407            .or_default()
408            .insert(permission);
409    }
410
411    /// Revoke a permission from a role.
412    pub fn revoke_role(&mut self, role: Role, permission: Permission) {
413        if let Some(permissions) = self.role_permissions.get_mut(&role) {
414            permissions.remove(&permission);
415        }
416    }
417
418    /// Check if a role has a permission.
419    pub fn role_has_permission(&self, role: Role, permission: Permission) -> bool {
420        self.role_permissions
421            .get(&role)
422            .is_some_and(|perms| perms.contains(&permission))
423    }
424
425    /// Get all permissions for a role.
426    pub fn get_permissions(&self, role: Role) -> HashSet<Permission> {
427        self.role_permissions
428            .get(&role)
429            .cloned()
430            .unwrap_or_default()
431    }
432}
433
434impl Default for AuthorizationPolicy {
435    fn default() -> Self {
436        Self::default_policy()
437    }
438}
439
440/// Role-based authorization controller.
441///
442/// Checks permissions for authenticated entities based on their roles
443/// and the authorization context.
444#[derive(Debug)]
445pub struct AuthorizationController {
446    /// The authorization policy
447    policy: AuthorizationPolicy,
448}
449
450impl AuthorizationController {
451    /// Create a controller with a custom policy.
452    pub fn new(policy: AuthorizationPolicy) -> Self {
453        Self { policy }
454    }
455
456    /// Create a controller with the default Peat Protocol policy.
457    pub fn with_default_policy() -> Self {
458        Self::new(AuthorizationPolicy::default_policy())
459    }
460
461    /// Check if an entity has a permission in the given context.
462    ///
463    /// Returns `Ok(())` if the permission is granted, or an error if denied.
464    pub fn check_permission(
465        &self,
466        entity: &AuthenticatedEntity,
467        permission: Permission,
468        context: &AuthorizationContext,
469    ) -> Result<(), SecurityError> {
470        // Get roles for the entity in this context
471        let roles = self.get_roles(entity, context);
472
473        // Check if any role grants the permission
474        let granted = roles
475            .iter()
476            .any(|role| self.policy.role_has_permission(*role, permission));
477
478        if granted {
479            Ok(())
480        } else {
481            Err(SecurityError::PermissionDenied {
482                permission: permission.to_string(),
483                entity_id: entity.id(),
484                roles: roles.iter().map(|r| r.to_string()).collect(),
485            })
486        }
487    }
488
489    /// Get the roles for an entity in the given context.
490    pub fn get_roles(
491        &self,
492        entity: &AuthenticatedEntity,
493        context: &AuthorizationContext,
494    ) -> HashSet<Role> {
495        let mut roles = HashSet::new();
496
497        match entity {
498            AuthenticatedEntity::Device(device_info) => {
499                // Devices get roles based on cell membership
500                if let Some(membership) = &context.cell_membership {
501                    let device_hex = device_info.device_id.to_hex();
502
503                    if membership.is_leader(&device_hex) {
504                        roles.insert(Role::Leader);
505                    } else if membership.is_member(&device_hex) {
506                        roles.insert(Role::Member);
507                    } else {
508                        // Not a member - observer only
509                        roles.insert(Role::Observer);
510                    }
511                } else {
512                    // No cell context - default to observer
513                    roles.insert(Role::Observer);
514                }
515            }
516            AuthenticatedEntity::User(user_info) => {
517                // Users have explicit roles
518                roles = user_info.roles.clone();
519            }
520        }
521
522        roles
523    }
524
525    /// Get the underlying policy.
526    pub fn policy(&self) -> &AuthorizationPolicy {
527        &self.policy
528    }
529}
530
531impl Default for AuthorizationController {
532    fn default() -> Self {
533        Self::with_default_policy()
534    }
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540
541    fn test_device_id() -> DeviceId {
542        let keypair = crate::security::DeviceKeypair::generate();
543        keypair.device_id()
544    }
545
546    #[test]
547    fn test_role_display() {
548        assert_eq!(Role::Leader.to_string(), "Leader");
549        assert_eq!(Role::Member.to_string(), "Member");
550        assert_eq!(Role::Observer.to_string(), "Observer");
551        assert_eq!(Role::Commander.to_string(), "Commander");
552        assert_eq!(Role::Admin.to_string(), "Admin");
553    }
554
555    #[test]
556    fn test_permission_display() {
557        assert_eq!(Permission::JoinCell.to_string(), "JoinCell");
558        assert_eq!(Permission::SetCellObjective.to_string(), "SetCellObjective");
559    }
560
561    #[test]
562    fn test_default_policy_leader_permissions() {
563        let policy = AuthorizationPolicy::default_policy();
564
565        // Leaders should have these permissions
566        assert!(policy.role_has_permission(Role::Leader, Permission::SetCellObjective));
567        assert!(policy.role_has_permission(Role::Leader, Permission::SetCellLeader));
568        assert!(policy.role_has_permission(Role::Leader, Permission::WriteCellState));
569
570        // Leaders should NOT have admin permissions
571        assert!(!policy.role_has_permission(Role::Leader, Permission::ConfigureNetwork));
572        assert!(!policy.role_has_permission(Role::Leader, Permission::ManageKeys));
573    }
574
575    #[test]
576    fn test_default_policy_member_permissions() {
577        let policy = AuthorizationPolicy::default_policy();
578
579        // Members should have these permissions
580        assert!(policy.role_has_permission(Role::Member, Permission::JoinCell));
581        assert!(policy.role_has_permission(Role::Member, Permission::LeaveCell));
582        assert!(policy.role_has_permission(Role::Member, Permission::ReadCellState));
583
584        // Members should NOT have leader permissions
585        assert!(!policy.role_has_permission(Role::Member, Permission::SetCellObjective));
586        assert!(!policy.role_has_permission(Role::Member, Permission::SetCellLeader));
587    }
588
589    #[test]
590    fn test_default_policy_observer_permissions() {
591        let policy = AuthorizationPolicy::default_policy();
592
593        // Observers should only have read permissions
594        assert!(policy.role_has_permission(Role::Observer, Permission::ReadCellState));
595        assert!(policy.role_has_permission(Role::Observer, Permission::ReadNodeState));
596        assert!(policy.role_has_permission(Role::Observer, Permission::ReadTelemetry));
597
598        // Observers should NOT have write permissions
599        assert!(!policy.role_has_permission(Role::Observer, Permission::WriteCellState));
600        assert!(!policy.role_has_permission(Role::Observer, Permission::WriteNodeState));
601    }
602
603    #[test]
604    fn test_custom_policy() {
605        let mut policy = AuthorizationPolicy::new();
606
607        // Initially no permissions
608        assert!(!policy.role_has_permission(Role::Member, Permission::CreateCell));
609
610        // Grant permission
611        policy.grant_role(Role::Member, Permission::CreateCell);
612        assert!(policy.role_has_permission(Role::Member, Permission::CreateCell));
613
614        // Revoke permission
615        policy.revoke_role(Role::Member, Permission::CreateCell);
616        assert!(!policy.role_has_permission(Role::Member, Permission::CreateCell));
617    }
618
619    #[test]
620    fn test_authorization_controller_leader() {
621        let controller = AuthorizationController::with_default_policy();
622        let device_id = test_device_id();
623        let device_hex = device_id.to_hex();
624
625        let entity = AuthenticatedEntity::from_device_id(device_id);
626
627        // Create context where device is the leader
628        let membership = CellMembershipContext::new(Some(device_hex), HashSet::new());
629        let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
630
631        // Leader should be able to set objectives
632        assert!(controller
633            .check_permission(&entity, Permission::SetCellObjective, &context)
634            .is_ok());
635
636        // Leader should be able to write cell state
637        assert!(controller
638            .check_permission(&entity, Permission::WriteCellState, &context)
639            .is_ok());
640
641        // Leader should NOT be able to configure network (admin only)
642        assert!(controller
643            .check_permission(&entity, Permission::ConfigureNetwork, &context)
644            .is_err());
645    }
646
647    #[test]
648    fn test_authorization_controller_member() {
649        let controller = AuthorizationController::with_default_policy();
650        let device_id = test_device_id();
651        let device_hex = device_id.to_hex();
652
653        let entity = AuthenticatedEntity::from_device_id(device_id);
654
655        // Create context where device is a member (not leader)
656        let mut members = HashSet::new();
657        members.insert(device_hex);
658        let membership = CellMembershipContext::new(Some("other-leader".to_string()), members);
659        let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
660
661        // Member should be able to read cell state
662        assert!(controller
663            .check_permission(&entity, Permission::ReadCellState, &context)
664            .is_ok());
665
666        // Member should be able to write node state
667        assert!(controller
668            .check_permission(&entity, Permission::WriteNodeState, &context)
669            .is_ok());
670
671        // Member should NOT be able to set objectives (leader only)
672        assert!(controller
673            .check_permission(&entity, Permission::SetCellObjective, &context)
674            .is_err());
675    }
676
677    #[test]
678    fn test_authorization_controller_observer() {
679        let controller = AuthorizationController::with_default_policy();
680        let device_id = test_device_id();
681
682        let entity = AuthenticatedEntity::from_device_id(device_id);
683
684        // Create context where device is neither leader nor member (observer)
685        let membership =
686            CellMembershipContext::new(Some("some-leader".to_string()), HashSet::new());
687        let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
688
689        // Observer should be able to read
690        assert!(controller
691            .check_permission(&entity, Permission::ReadCellState, &context)
692            .is_ok());
693
694        // Observer should NOT be able to write
695        assert!(controller
696            .check_permission(&entity, Permission::WriteCellState, &context)
697            .is_err());
698
699        // Observer should NOT be able to join (they need member role first)
700        assert!(controller
701            .check_permission(&entity, Permission::JoinCell, &context)
702            .is_err());
703    }
704
705    #[test]
706    fn test_authorization_controller_user_roles() {
707        let controller = AuthorizationController::with_default_policy();
708
709        let mut roles = HashSet::new();
710        roles.insert(Role::Commander);
711
712        let entity = AuthenticatedEntity::User(UserIdentityInfo {
713            username: "commander_alpha".to_string(),
714            roles,
715        });
716
717        let context = AuthorizationContext::for_cell("test-cell");
718
719        // Commander should be able to approve formation
720        assert!(controller
721            .check_permission(&entity, Permission::ApproveFormation, &context)
722            .is_ok());
723
724        // Commander should be able to form platoon
725        assert!(controller
726            .check_permission(&entity, Permission::FormPlatoon, &context)
727            .is_ok());
728
729        // Commander should NOT be able to manage keys (admin only)
730        assert!(controller
731            .check_permission(&entity, Permission::ManageKeys, &context)
732            .is_err());
733    }
734
735    #[test]
736    fn test_get_roles_returns_correct_roles() {
737        let controller = AuthorizationController::with_default_policy();
738        let device_id = test_device_id();
739        let device_hex = device_id.to_hex();
740
741        let entity = AuthenticatedEntity::from_device_id(device_id);
742
743        // As leader
744        let membership = CellMembershipContext::new(Some(device_hex.clone()), HashSet::new());
745        let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
746        let roles = controller.get_roles(&entity, &context);
747        assert!(roles.contains(&Role::Leader));
748        assert!(!roles.contains(&Role::Member));
749
750        // As member
751        let mut members = HashSet::new();
752        members.insert(device_hex);
753        let membership = CellMembershipContext::new(Some("other".to_string()), members);
754        let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
755        let roles = controller.get_roles(&entity, &context);
756        assert!(roles.contains(&Role::Member));
757        assert!(!roles.contains(&Role::Leader));
758    }
759
760    #[test]
761    fn test_permission_denied_error_contains_details() {
762        let controller = AuthorizationController::with_default_policy();
763        let device_id = test_device_id();
764
765        let entity = AuthenticatedEntity::from_device_id(device_id);
766        let context = AuthorizationContext::system();
767
768        let result = controller.check_permission(&entity, Permission::ConfigureNetwork, &context);
769        assert!(result.is_err());
770
771        if let Err(SecurityError::PermissionDenied {
772            permission,
773            entity_id,
774            ..
775        }) = result
776        {
777            assert_eq!(permission, "ConfigureNetwork");
778            assert!(!entity_id.is_empty());
779        } else {
780            panic!("Expected PermissionDenied error");
781        }
782    }
783}