use crate::error::{IronError, Result};
use serde::{Serialize, Deserialize};
use std::collections::{HashMap, HashSet};
use std::time::SystemTime;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AdminOperation {
CreateChannel {
channel: String,
settings: ChannelSettings,
},
SetTopic {
channel: String,
topic: String,
},
SetMode {
channel: String,
mode: ChannelMode,
enabled: bool,
},
MemberOperation {
channel: String,
target: String,
operation: MemberOperation,
},
BanOperation {
channel: String,
target: String,
operation: BanOperation,
duration: Option<SystemTime>,
},
KeyOperation {
channel: String,
operation: KeyOperation,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MemberOperation {
Invite,
Kick { reason: Option<String> },
Op,
Deop,
Voice,
Devoice,
SetRole { role: MemberRole },
Mute { duration: Option<SystemTime> },
Unmute,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BanOperation {
Add { reason: Option<String> },
Remove,
List,
Check,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum KeyOperation {
Rotate,
Backup,
Restore { backup_id: String },
Generate,
ExportPublic,
ImportPublic { user_id: String, public_key: Vec<u8> },
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MemberRole {
Founder,
Owner,
Admin,
Operator,
HalfOp,
Voice,
Member,
Restricted,
Muted,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ChannelMode {
Moderated,
InviteOnly,
NoExternal,
TopicProtected,
Secret,
Private,
KeyRotation,
History,
Anonymous,
RateLimit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelSettings {
pub topic: Option<String>,
pub topic_set_by: Option<(String, SystemTime)>,
pub modes: HashSet<ChannelMode>,
pub member_limit: Option<usize>,
pub password: Option<String>,
pub invite_list: HashSet<String>,
pub exception_list: HashSet<String>,
pub quiet_list: HashSet<String>,
pub rate_limit: Option<RateLimit>,
pub key_rotation_interval: Option<u64>,
pub history_retention: Option<u64>,
pub created_at: SystemTime,
pub last_activity: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimit {
pub messages: u32,
pub window: u64,
pub burst: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelBan {
pub pattern: String,
pub reason: Option<String>,
pub set_by: String,
pub set_at: SystemTime,
pub expires_at: Option<SystemTime>,
pub ban_type: BanType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BanType {
Full,
Quiet,
Invite,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminResult {
pub operation: AdminOperation,
pub success: bool,
pub message: String,
pub data: Option<AdminData>,
pub timestamp: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AdminData {
MemberList(Vec<ChannelMember>),
BanList(Vec<ChannelBan>),
ChannelInfo(ChannelInfo),
KeyInfo(KeyInfo),
Permissions(HashSet<Permission>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelMember {
pub user_id: String,
pub nickname: String,
pub role: MemberRole,
pub joined_at: SystemTime,
pub last_activity: SystemTime,
pub public_key: Option<Vec<u8>>,
pub custom_permissions: Option<HashSet<Permission>>,
pub is_online: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelInfo {
pub name: String,
pub settings: ChannelSettings,
pub member_count: usize,
pub topic: Option<String>,
pub modes: HashSet<ChannelMode>,
pub stats: ChannelStats,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelStats {
pub total_messages: u64,
pub messages_today: f64,
pub avg_messages_per_day: f64,
pub most_active_member: Option<String>,
pub key_rotations: u64,
pub last_key_rotation: Option<SystemTime>,
}
impl Default for ChannelStats {
fn default() -> Self {
Self {
total_messages: 0,
messages_today: 0.0,
avg_messages_per_day: 0.0,
most_active_member: None,
key_rotations: 0,
last_key_rotation: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyInfo {
pub key_version: u64,
pub created_at: SystemTime,
pub rotation_schedule: Option<SystemTime>,
pub member_key_count: usize,
pub has_backup: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Permission {
SendMessage,
ReadMessage,
JoinChannel,
LeaveChannel,
KickMember,
BanMember,
UnbanMember,
MuteMember,
UnmuteMember,
SetTopic,
SetMode,
InviteMember,
GrantVoice,
GrantOp,
ManageChannel,
ManageRoles,
ViewLogs,
ManageBans,
RotateKeys,
BackupKeys,
RestoreKeys,
ManageKeys,
TransferOwnership,
DestroyChannel,
ManageAdmins,
}
impl Default for ChannelSettings {
fn default() -> Self {
Self {
topic: None,
topic_set_by: None,
modes: HashSet::new(),
member_limit: None,
password: None,
invite_list: HashSet::new(),
exception_list: HashSet::new(),
quiet_list: HashSet::new(),
rate_limit: None,
key_rotation_interval: Some(86400), history_retention: Some(2592000), created_at: SystemTime::now(),
last_activity: SystemTime::now(),
}
}
}
impl MemberRole {
pub fn has_permission(&self, permission: &Permission) -> bool {
match self {
MemberRole::Founder => true, MemberRole::Owner => !matches!(permission, Permission::TransferOwnership),
MemberRole::Admin => matches!(permission,
Permission::SendMessage | Permission::ReadMessage | Permission::JoinChannel | Permission::LeaveChannel |
Permission::KickMember | Permission::BanMember | Permission::UnbanMember | Permission::MuteMember | Permission::UnmuteMember |
Permission::SetTopic | Permission::SetMode | Permission::InviteMember | Permission::GrantVoice | Permission::GrantOp |
Permission::ManageChannel | Permission::ManageRoles | Permission::ViewLogs | Permission::ManageBans |
Permission::RotateKeys | Permission::BackupKeys | Permission::RestoreKeys | Permission::ManageKeys
),
MemberRole::Operator => matches!(permission,
Permission::SendMessage | Permission::ReadMessage | Permission::JoinChannel | Permission::LeaveChannel |
Permission::KickMember | Permission::BanMember | Permission::UnbanMember | Permission::MuteMember | Permission::UnmuteMember |
Permission::SetTopic | Permission::InviteMember | Permission::GrantVoice |
Permission::ViewLogs | Permission::ManageBans
),
MemberRole::HalfOp => matches!(permission,
Permission::SendMessage | Permission::ReadMessage | Permission::JoinChannel | Permission::LeaveChannel |
Permission::MuteMember | Permission::UnmuteMember | Permission::InviteMember |
Permission::ViewLogs
),
MemberRole::Voice => matches!(permission,
Permission::SendMessage | Permission::ReadMessage | Permission::JoinChannel | Permission::LeaveChannel
),
MemberRole::Member => matches!(permission,
Permission::SendMessage | Permission::ReadMessage | Permission::JoinChannel | Permission::LeaveChannel
),
MemberRole::Restricted => matches!(permission,
Permission::ReadMessage | Permission::JoinChannel | Permission::LeaveChannel
),
MemberRole::Muted => matches!(permission,
Permission::ReadMessage | Permission::JoinChannel | Permission::LeaveChannel
),
}
}
pub fn permissions(&self) -> HashSet<Permission> {
use Permission::*;
let all_permissions = vec![
SendMessage, ReadMessage, JoinChannel, LeaveChannel,
KickMember, BanMember, UnbanMember, MuteMember, UnmuteMember,
SetTopic, SetMode, InviteMember, GrantVoice, GrantOp,
ManageChannel, ManageRoles, ViewLogs, ManageBans,
RotateKeys, BackupKeys, RestoreKeys, ManageKeys,
TransferOwnership, DestroyChannel, ManageAdmins,
];
all_permissions.into_iter()
.filter(|p| self.has_permission(p))
.collect()
}
pub fn can_manage_role(&self, target_role: &MemberRole) -> bool {
use MemberRole::*;
match (self, target_role) {
(Founder, _) => true,
(Owner, Founder) => false,
(Owner, _) => true,
(Admin, Founder | Owner) => false,
(Admin, _) => true,
(Operator, Founder | Owner | Admin) => false,
(Operator, _) => true,
_ => false,
}
}
pub fn hierarchy_level(&self) -> u8 {
match self {
MemberRole::Founder => 100,
MemberRole::Owner => 90,
MemberRole::Admin => 80,
MemberRole::Operator => 70,
MemberRole::HalfOp => 60,
MemberRole::Voice => 50,
MemberRole::Member => 40,
MemberRole::Restricted => 30,
MemberRole::Muted => 20,
}
}
}
impl ChannelBan {
pub fn is_active(&self) -> bool {
match self.expires_at {
Some(expires) => SystemTime::now() < expires,
None => true, }
}
pub fn matches_pattern(&self, pattern: &str) -> bool {
if self.pattern.contains('*') || self.pattern.contains('?') {
self.wildcard_match(&self.pattern, pattern)
} else {
self.pattern == pattern
}
}
fn wildcard_match(&self, pattern: &str, text: &str) -> bool {
let pattern_chars: Vec<char> = pattern.chars().collect();
let text_chars: Vec<char> = text.chars().collect();
self.match_recursive(&pattern_chars, &text_chars, 0, 0)
}
fn match_recursive(&self, pattern: &[char], text: &[char], p_idx: usize, t_idx: usize) -> bool {
if p_idx >= pattern.len() {
return t_idx >= text.len();
}
match pattern[p_idx] {
'*' => {
for i in t_idx..=text.len() {
if self.match_recursive(pattern, text, p_idx + 1, i) {
return true;
}
}
false
},
'?' => {
if t_idx < text.len() {
self.match_recursive(pattern, text, p_idx + 1, t_idx + 1)
} else {
false
}
},
c => {
if t_idx < text.len() && text[t_idx] == c {
self.match_recursive(pattern, text, p_idx + 1, t_idx + 1)
} else {
false
}
}
}
}
}
pub struct ChannelAdmin {
user_id: String,
user_role: MemberRole,
user_permissions: HashSet<Permission>,
}
impl ChannelAdmin {
pub fn new(user_id: String, user_role: MemberRole, user_permissions: HashSet<Permission>) -> Self {
Self {
user_id,
user_role,
user_permissions,
}
}
pub fn can_perform(&self, operation: &AdminOperation, target_channel: &ChannelSettings) -> bool {
match operation {
AdminOperation::CreateChannel { .. } => {
true
},
AdminOperation::SetTopic { .. } => {
self.has_permission(&Permission::SetTopic) &&
(!target_channel.modes.contains(&ChannelMode::TopicProtected) ||
self.user_role.hierarchy_level() >= MemberRole::Operator.hierarchy_level())
},
AdminOperation::SetMode { .. } => {
self.has_permission(&Permission::SetMode)
},
AdminOperation::MemberOperation { operation, .. } => {
match operation {
MemberOperation::Invite => self.has_permission(&Permission::InviteMember),
MemberOperation::Kick { .. } => self.has_permission(&Permission::KickMember),
MemberOperation::Op | MemberOperation::Deop => self.has_permission(&Permission::GrantOp),
MemberOperation::Voice | MemberOperation::Devoice => self.has_permission(&Permission::GrantVoice),
MemberOperation::SetRole { .. } => self.has_permission(&Permission::ManageRoles),
MemberOperation::Mute { .. } | MemberOperation::Unmute => self.has_permission(&Permission::MuteMember),
}
},
AdminOperation::BanOperation { operation, .. } => {
match operation {
BanOperation::Add { .. } => self.has_permission(&Permission::BanMember),
BanOperation::Remove => self.has_permission(&Permission::UnbanMember),
BanOperation::List | BanOperation::Check => self.has_permission(&Permission::ViewLogs),
}
},
AdminOperation::KeyOperation { operation, .. } => {
match operation {
KeyOperation::Rotate => self.has_permission(&Permission::RotateKeys),
KeyOperation::Backup => self.has_permission(&Permission::BackupKeys),
KeyOperation::Restore { .. } => self.has_permission(&Permission::RestoreKeys),
KeyOperation::Generate => self.has_permission(&Permission::ManageKeys),
KeyOperation::ExportPublic => true, KeyOperation::ImportPublic { .. } => self.has_permission(&Permission::ManageKeys),
}
},
}
}
pub fn has_permission(&self, permission: &Permission) -> bool {
self.user_role.has_permission(permission) || self.user_permissions.contains(permission)
}
pub fn get_permissions(&self) -> HashSet<Permission> {
let mut permissions = self.user_role.permissions();
permissions.extend(self.user_permissions.clone());
permissions
}
pub fn can_manage_user_role(&self, target_role: &MemberRole) -> bool {
self.user_role.can_manage_role(target_role)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_role_permissions() {
let owner = MemberRole::Owner;
let member = MemberRole::Member;
assert!(owner.has_permission(&Permission::KickMember));
assert!(!member.has_permission(&Permission::KickMember));
assert!(member.has_permission(&Permission::SendMessage));
}
#[test]
fn test_role_hierarchy() {
let founder = MemberRole::Founder;
let admin = MemberRole::Admin;
let member = MemberRole::Member;
assert!(founder.can_manage_role(&admin));
assert!(admin.can_manage_role(&member));
assert!(!member.can_manage_role(&admin));
}
#[test]
fn test_ban_wildcard_matching() {
let ban = ChannelBan {
pattern: "*@evil.com".to_string(),
reason: Some("Spam domain".to_string()),
set_by: "admin".to_string(),
set_at: SystemTime::now(),
expires_at: None,
ban_type: BanType::Full,
};
assert!(ban.matches_pattern("user@evil.com"));
assert!(ban.matches_pattern("spammer@evil.com"));
assert!(!ban.matches_pattern("user@good.com"));
}
#[test]
fn test_admin_permissions() {
let admin = ChannelAdmin::new(
"admin_user".to_string(),
MemberRole::Admin,
HashSet::new(),
);
let settings = ChannelSettings::default();
let kick_op = AdminOperation::MemberOperation {
channel: "!test".to_string(),
target: "user".to_string(),
operation: MemberOperation::Kick { reason: None },
};
assert!(admin.can_perform(&kick_op, &settings));
}
}