use super::device_id::DeviceId;
use super::error::SecurityError;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::time::SystemTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Role {
Leader,
Member,
Observer,
Commander,
Admin,
}
impl fmt::Display for Role {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Role::Leader => write!(f, "Leader"),
Role::Member => write!(f, "Member"),
Role::Observer => write!(f, "Observer"),
Role::Commander => write!(f, "Commander"),
Role::Admin => write!(f, "Admin"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Permission {
JoinCell,
LeaveCell,
CreateCell,
DisbandCell,
SetCellLeader,
SetCellObjective,
AdvertiseCapability,
RequestCapability,
ReadCellState,
WriteCellState,
ReadNodeState,
WriteNodeState,
ReadTelemetry,
FormPlatoon,
AggregateToCompany,
ApproveFormation,
VetoCommand,
ConfigureNetwork,
ManageKeys,
ViewAuditLog,
}
impl fmt::Display for Permission {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Permission::JoinCell => write!(f, "JoinCell"),
Permission::LeaveCell => write!(f, "LeaveCell"),
Permission::CreateCell => write!(f, "CreateCell"),
Permission::DisbandCell => write!(f, "DisbandCell"),
Permission::SetCellLeader => write!(f, "SetCellLeader"),
Permission::SetCellObjective => write!(f, "SetCellObjective"),
Permission::AdvertiseCapability => write!(f, "AdvertiseCapability"),
Permission::RequestCapability => write!(f, "RequestCapability"),
Permission::ReadCellState => write!(f, "ReadCellState"),
Permission::WriteCellState => write!(f, "WriteCellState"),
Permission::ReadNodeState => write!(f, "ReadNodeState"),
Permission::WriteNodeState => write!(f, "WriteNodeState"),
Permission::ReadTelemetry => write!(f, "ReadTelemetry"),
Permission::FormPlatoon => write!(f, "FormPlatoon"),
Permission::AggregateToCompany => write!(f, "AggregateToCompany"),
Permission::ApproveFormation => write!(f, "ApproveFormation"),
Permission::VetoCommand => write!(f, "VetoCommand"),
Permission::ConfigureNetwork => write!(f, "ConfigureNetwork"),
Permission::ManageKeys => write!(f, "ManageKeys"),
Permission::ViewAuditLog => write!(f, "ViewAuditLog"),
}
}
}
#[derive(Debug, Clone)]
pub enum AuthenticatedEntity {
Device(DeviceIdentityInfo),
User(UserIdentityInfo),
}
impl AuthenticatedEntity {
pub fn id(&self) -> String {
match self {
AuthenticatedEntity::Device(info) => info.device_id.to_hex(),
AuthenticatedEntity::User(info) => info.username.clone(),
}
}
pub fn from_device_id(device_id: DeviceId) -> Self {
AuthenticatedEntity::Device(DeviceIdentityInfo {
device_id,
device_type: DeviceType::Unknown,
})
}
}
#[derive(Debug, Clone)]
pub struct DeviceIdentityInfo {
pub device_id: DeviceId,
pub device_type: DeviceType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeviceType {
Uav,
Ugv,
C2Station,
Sensor,
Relay,
Unknown,
}
#[derive(Debug, Clone)]
pub struct UserIdentityInfo {
pub username: String,
pub roles: HashSet<Role>,
}
#[derive(Debug, Clone)]
pub struct AuthorizationContext {
pub cell_id: Option<String>,
pub node_id: Option<String>,
pub hierarchy_level: Option<HierarchyLevel>,
pub timestamp: SystemTime,
pub cell_membership: Option<CellMembershipContext>,
}
impl AuthorizationContext {
pub fn for_cell(cell_id: &str) -> Self {
Self {
cell_id: Some(cell_id.to_string()),
node_id: None,
hierarchy_level: Some(HierarchyLevel::Squad),
timestamp: SystemTime::now(),
cell_membership: None,
}
}
pub fn for_node(node_id: &str) -> Self {
Self {
cell_id: None,
node_id: Some(node_id.to_string()),
hierarchy_level: None,
timestamp: SystemTime::now(),
cell_membership: None,
}
}
pub fn system() -> Self {
Self {
cell_id: None,
node_id: None,
hierarchy_level: None,
timestamp: SystemTime::now(),
cell_membership: None,
}
}
pub fn with_membership(mut self, membership: CellMembershipContext) -> Self {
self.cell_membership = Some(membership);
self
}
}
#[derive(Debug, Clone)]
pub struct CellMembershipContext {
pub leader_id: Option<String>,
pub member_ids: HashSet<String>,
}
impl CellMembershipContext {
pub fn new(leader_id: Option<String>, member_ids: HashSet<String>) -> Self {
Self {
leader_id,
member_ids,
}
}
pub fn is_leader(&self, device_id: &str) -> bool {
self.leader_id.as_ref() == Some(&device_id.to_string())
}
pub fn is_member(&self, device_id: &str) -> bool {
self.member_ids.contains(device_id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HierarchyLevel {
Node,
Squad,
Platoon,
Company,
Battalion,
}
#[derive(Debug, Clone)]
pub struct AuthorizationPolicy {
role_permissions: HashMap<Role, HashSet<Permission>>,
}
impl AuthorizationPolicy {
pub fn new() -> Self {
Self {
role_permissions: HashMap::new(),
}
}
pub fn default_policy() -> Self {
let mut policy = Self::new();
policy.grant_role(Role::Leader, Permission::SetCellObjective);
policy.grant_role(Role::Leader, Permission::SetCellLeader);
policy.grant_role(Role::Leader, Permission::RequestCapability);
policy.grant_role(Role::Leader, Permission::ReadCellState);
policy.grant_role(Role::Leader, Permission::WriteCellState);
policy.grant_role(Role::Leader, Permission::ReadNodeState);
policy.grant_role(Role::Leader, Permission::WriteNodeState);
policy.grant_role(Role::Leader, Permission::ReadTelemetry);
policy.grant_role(Role::Leader, Permission::DisbandCell);
policy.grant_role(Role::Member, Permission::JoinCell);
policy.grant_role(Role::Member, Permission::LeaveCell);
policy.grant_role(Role::Member, Permission::AdvertiseCapability);
policy.grant_role(Role::Member, Permission::ReadCellState);
policy.grant_role(Role::Member, Permission::WriteNodeState);
policy.grant_role(Role::Member, Permission::ReadNodeState);
policy.grant_role(Role::Member, Permission::ReadTelemetry);
policy.grant_role(Role::Observer, Permission::ReadCellState);
policy.grant_role(Role::Observer, Permission::ReadNodeState);
policy.grant_role(Role::Observer, Permission::ReadTelemetry);
policy.grant_role(Role::Commander, Permission::FormPlatoon);
policy.grant_role(Role::Commander, Permission::AggregateToCompany);
policy.grant_role(Role::Commander, Permission::ApproveFormation);
policy.grant_role(Role::Commander, Permission::VetoCommand);
policy.grant_role(Role::Commander, Permission::CreateCell);
policy.grant_role(Role::Commander, Permission::ReadCellState);
policy.grant_role(Role::Commander, Permission::WriteCellState);
policy.grant_role(Role::Commander, Permission::ReadNodeState);
policy.grant_role(Role::Commander, Permission::ReadTelemetry);
policy.grant_role(Role::Admin, Permission::ConfigureNetwork);
policy.grant_role(Role::Admin, Permission::ManageKeys);
policy.grant_role(Role::Admin, Permission::ViewAuditLog);
policy.grant_role(Role::Admin, Permission::CreateCell);
policy.grant_role(Role::Admin, Permission::DisbandCell);
policy
}
pub fn grant_role(&mut self, role: Role, permission: Permission) {
self.role_permissions
.entry(role)
.or_default()
.insert(permission);
}
pub fn revoke_role(&mut self, role: Role, permission: Permission) {
if let Some(permissions) = self.role_permissions.get_mut(&role) {
permissions.remove(&permission);
}
}
pub fn role_has_permission(&self, role: Role, permission: Permission) -> bool {
self.role_permissions
.get(&role)
.is_some_and(|perms| perms.contains(&permission))
}
pub fn get_permissions(&self, role: Role) -> HashSet<Permission> {
self.role_permissions
.get(&role)
.cloned()
.unwrap_or_default()
}
}
impl Default for AuthorizationPolicy {
fn default() -> Self {
Self::default_policy()
}
}
#[derive(Debug)]
pub struct AuthorizationController {
policy: AuthorizationPolicy,
}
impl AuthorizationController {
pub fn new(policy: AuthorizationPolicy) -> Self {
Self { policy }
}
pub fn with_default_policy() -> Self {
Self::new(AuthorizationPolicy::default_policy())
}
pub fn check_permission(
&self,
entity: &AuthenticatedEntity,
permission: Permission,
context: &AuthorizationContext,
) -> Result<(), SecurityError> {
let roles = self.get_roles(entity, context);
let granted = roles
.iter()
.any(|role| self.policy.role_has_permission(*role, permission));
if granted {
Ok(())
} else {
Err(SecurityError::PermissionDenied {
permission: permission.to_string(),
entity_id: entity.id(),
roles: roles.iter().map(|r| r.to_string()).collect(),
})
}
}
pub fn get_roles(
&self,
entity: &AuthenticatedEntity,
context: &AuthorizationContext,
) -> HashSet<Role> {
let mut roles = HashSet::new();
match entity {
AuthenticatedEntity::Device(device_info) => {
if let Some(membership) = &context.cell_membership {
let device_hex = device_info.device_id.to_hex();
if membership.is_leader(&device_hex) {
roles.insert(Role::Leader);
} else if membership.is_member(&device_hex) {
roles.insert(Role::Member);
} else {
roles.insert(Role::Observer);
}
} else {
roles.insert(Role::Observer);
}
}
AuthenticatedEntity::User(user_info) => {
roles = user_info.roles.clone();
}
}
roles
}
pub fn policy(&self) -> &AuthorizationPolicy {
&self.policy
}
}
impl Default for AuthorizationController {
fn default() -> Self {
Self::with_default_policy()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_device_id() -> DeviceId {
let keypair = crate::security::DeviceKeypair::generate();
keypair.device_id()
}
#[test]
fn test_role_display() {
assert_eq!(Role::Leader.to_string(), "Leader");
assert_eq!(Role::Member.to_string(), "Member");
assert_eq!(Role::Observer.to_string(), "Observer");
assert_eq!(Role::Commander.to_string(), "Commander");
assert_eq!(Role::Admin.to_string(), "Admin");
}
#[test]
fn test_permission_display() {
assert_eq!(Permission::JoinCell.to_string(), "JoinCell");
assert_eq!(Permission::SetCellObjective.to_string(), "SetCellObjective");
}
#[test]
fn test_default_policy_leader_permissions() {
let policy = AuthorizationPolicy::default_policy();
assert!(policy.role_has_permission(Role::Leader, Permission::SetCellObjective));
assert!(policy.role_has_permission(Role::Leader, Permission::SetCellLeader));
assert!(policy.role_has_permission(Role::Leader, Permission::WriteCellState));
assert!(!policy.role_has_permission(Role::Leader, Permission::ConfigureNetwork));
assert!(!policy.role_has_permission(Role::Leader, Permission::ManageKeys));
}
#[test]
fn test_default_policy_member_permissions() {
let policy = AuthorizationPolicy::default_policy();
assert!(policy.role_has_permission(Role::Member, Permission::JoinCell));
assert!(policy.role_has_permission(Role::Member, Permission::LeaveCell));
assert!(policy.role_has_permission(Role::Member, Permission::ReadCellState));
assert!(!policy.role_has_permission(Role::Member, Permission::SetCellObjective));
assert!(!policy.role_has_permission(Role::Member, Permission::SetCellLeader));
}
#[test]
fn test_default_policy_observer_permissions() {
let policy = AuthorizationPolicy::default_policy();
assert!(policy.role_has_permission(Role::Observer, Permission::ReadCellState));
assert!(policy.role_has_permission(Role::Observer, Permission::ReadNodeState));
assert!(policy.role_has_permission(Role::Observer, Permission::ReadTelemetry));
assert!(!policy.role_has_permission(Role::Observer, Permission::WriteCellState));
assert!(!policy.role_has_permission(Role::Observer, Permission::WriteNodeState));
}
#[test]
fn test_custom_policy() {
let mut policy = AuthorizationPolicy::new();
assert!(!policy.role_has_permission(Role::Member, Permission::CreateCell));
policy.grant_role(Role::Member, Permission::CreateCell);
assert!(policy.role_has_permission(Role::Member, Permission::CreateCell));
policy.revoke_role(Role::Member, Permission::CreateCell);
assert!(!policy.role_has_permission(Role::Member, Permission::CreateCell));
}
#[test]
fn test_authorization_controller_leader() {
let controller = AuthorizationController::with_default_policy();
let device_id = test_device_id();
let device_hex = device_id.to_hex();
let entity = AuthenticatedEntity::from_device_id(device_id);
let membership = CellMembershipContext::new(Some(device_hex), HashSet::new());
let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
assert!(controller
.check_permission(&entity, Permission::SetCellObjective, &context)
.is_ok());
assert!(controller
.check_permission(&entity, Permission::WriteCellState, &context)
.is_ok());
assert!(controller
.check_permission(&entity, Permission::ConfigureNetwork, &context)
.is_err());
}
#[test]
fn test_authorization_controller_member() {
let controller = AuthorizationController::with_default_policy();
let device_id = test_device_id();
let device_hex = device_id.to_hex();
let entity = AuthenticatedEntity::from_device_id(device_id);
let mut members = HashSet::new();
members.insert(device_hex);
let membership = CellMembershipContext::new(Some("other-leader".to_string()), members);
let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
assert!(controller
.check_permission(&entity, Permission::ReadCellState, &context)
.is_ok());
assert!(controller
.check_permission(&entity, Permission::WriteNodeState, &context)
.is_ok());
assert!(controller
.check_permission(&entity, Permission::SetCellObjective, &context)
.is_err());
}
#[test]
fn test_authorization_controller_observer() {
let controller = AuthorizationController::with_default_policy();
let device_id = test_device_id();
let entity = AuthenticatedEntity::from_device_id(device_id);
let membership =
CellMembershipContext::new(Some("some-leader".to_string()), HashSet::new());
let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
assert!(controller
.check_permission(&entity, Permission::ReadCellState, &context)
.is_ok());
assert!(controller
.check_permission(&entity, Permission::WriteCellState, &context)
.is_err());
assert!(controller
.check_permission(&entity, Permission::JoinCell, &context)
.is_err());
}
#[test]
fn test_authorization_controller_user_roles() {
let controller = AuthorizationController::with_default_policy();
let mut roles = HashSet::new();
roles.insert(Role::Commander);
let entity = AuthenticatedEntity::User(UserIdentityInfo {
username: "commander_alpha".to_string(),
roles,
});
let context = AuthorizationContext::for_cell("test-cell");
assert!(controller
.check_permission(&entity, Permission::ApproveFormation, &context)
.is_ok());
assert!(controller
.check_permission(&entity, Permission::FormPlatoon, &context)
.is_ok());
assert!(controller
.check_permission(&entity, Permission::ManageKeys, &context)
.is_err());
}
#[test]
fn test_get_roles_returns_correct_roles() {
let controller = AuthorizationController::with_default_policy();
let device_id = test_device_id();
let device_hex = device_id.to_hex();
let entity = AuthenticatedEntity::from_device_id(device_id);
let membership = CellMembershipContext::new(Some(device_hex.clone()), HashSet::new());
let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
let roles = controller.get_roles(&entity, &context);
assert!(roles.contains(&Role::Leader));
assert!(!roles.contains(&Role::Member));
let mut members = HashSet::new();
members.insert(device_hex);
let membership = CellMembershipContext::new(Some("other".to_string()), members);
let context = AuthorizationContext::for_cell("test-cell").with_membership(membership);
let roles = controller.get_roles(&entity, &context);
assert!(roles.contains(&Role::Member));
assert!(!roles.contains(&Role::Leader));
}
#[test]
fn test_permission_denied_error_contains_details() {
let controller = AuthorizationController::with_default_policy();
let device_id = test_device_id();
let entity = AuthenticatedEntity::from_device_id(device_id);
let context = AuthorizationContext::system();
let result = controller.check_permission(&entity, Permission::ConfigureNetwork, &context);
assert!(result.is_err());
if let Err(SecurityError::PermissionDenied {
permission,
entity_id,
..
}) = result
{
assert_eq!(permission, "ConfigureNetwork");
assert!(!entity_id.is_empty());
} else {
panic!("Expected PermissionDenied error");
}
}
}