/// # Système de Permissions par Bitmask /// /// Un bitmask (masque de bits) est une manière compacte et efficace de stocker plusieurs /// valeurs booléennes dans un seul nombre entier. /// /// ## Comment ça fonctionne ? /// Chaque permission est associée à une puissance de 2 (1, 2, 4, 8, 16, etc.), ce qui /// correspond à un bit unique dans la représentation binaire du nombre. /// /// - `ReadChannel` (1 << 0) = `00000001` (1 en décimal) /// - `JoinChannel` (1 << 1) = `00000010` (2 en décimal) /// - `SendMessage` (1 << 2) = `00000100` (4 en décimal) /// /// ## Opérations courantes : /// /// ### 1. Combiner des permissions (OU binaire `|`) /// Pour donner à la fois `ReadChannel` et `SendMessage` : /// `let mask = Permission::ReadChannel as u64 | Permission::SendMessage as u64;` /// Résultat : `00000101` (5 en décimal) /// /// ### 2. Vérifier une permission (ET binaire `&`) /// Pour savoir si un utilisateur a `SendMessage` dans son `mask` : /// `(mask & Permission::SendMessage as u64) != 0` /// /// ### 3. Retirer une permission (NON `!`, ET `&`) /// `mask &= !(Permission::SendMessage as u64);` /// #[derive(Debug, Clone, Copy)] #[repr(u64)] pub enum Permission { /// Pouvoir voir le canal dans la liste et lire les messages ReadChannel = 1 << 0, /// Pouvoir rejoindre le canal vocal JoinChannel = 1 << 1, /// Pouvoir envoyer des messages SendMessage = 1 << 2, /// Pouvoir supprimer ses propres messages DeleteMessage = 1 << 3, /// Pouvoir supprimer les messages des autres (Modérateur) DeleteOthersMessage = 1 << 4, /// Pouvoir modifier les paramètres du canal ManageChannel = 1 << 5, /// Pouvoir gérer les groupes/permissions du serveur ManageRoles = 1 << 6, /// Pouvoir expulser des membres du serveur KickMember = 1 << 7, /// Pouvoir parler en vocal VoiceSpeak = 1 << 8, /// Pouvoir rendre muet les autres utilisateurs en vocal VoiceMuteOthers = 1 << 9, /// Pouvoir modifier les paramètres globaux du serveur ManageServer = 1 << 10, } impl Permission { /// Retourne un bitmask contenant toutes les permissions "standard" /// (Tout sauf les permissions de gestion "Manage..."). pub fn default_permissions() -> u64 { Permission::ReadChannel as u64 | Permission::JoinChannel as u64 | Permission::SendMessage as u64 | Permission::DeleteMessage as u64 | Permission::DeleteOthersMessage as u64 | Permission::KickMember as u64 | Permission::VoiceSpeak as u64 | Permission::VoiceMuteOthers as u64 } } #[derive(Debug, Clone)] pub struct PermissionStack { /// Permissions au niveau du serveur (None = non configuré) pub server: Option, /// Permissions de TOUS les groupes de l'utilisateur (None = aucun groupe) pub groups: Option, /// Permissions de l'utilisateur au niveau serveur (None = non défini) pub server_user: Option, /// Permissions de TOUS les groupes au niveau serveur (None = non défini) pub server_groups: Option, /// Permissions de l'utilisateur sur ce canal (None = non défini) pub channel_user: Option, /// Permissions de TOUS les groupes sur ce canal (None = non défini) pub channel_groups: Option, } impl PermissionStack { pub fn new() -> Self { Self { server: None, groups: None, server_user: None, server_groups: None, channel_user: None, channel_groups: None, } } /// Résout les permissions finales en mode Union /// On accumule TOUTES les sources définies avec OR pub fn resolve(&self) -> u64 { let mut result = 0u64; if let Some(perms) = self.server { result |= perms; } if let Some(perms) = self.groups { result |= perms; } if let Some(perms) = self.server_user { result |= perms; } if let Some(perms) = self.server_groups { result |= perms; } if let Some(perms) = self.channel_user { result |= perms; } if let Some(perms) = self.channel_groups { result |= perms; } result } /// Vérifie si une permission spécifique est accordée. /// /// ### Exemple : /// ```rust /// if stack.has(Permission::SendMessage) { /// println!("L'utilisateur peut envoyer des messages !"); /// } /// ``` pub fn has(&self, permission: Permission) -> bool { let mask = self.resolve(); mask & (permission as u64) != 0 } /// Vérifie si TOUTES les permissions listées sont accordées (Strict). /// /// Utile pour des actions nécessitant plusieurs droits combinés. /// /// ### Exemple : /// ```rust /// // L'utilisateur doit pouvoir voir ET parler pour rejoindre un vocal /// let perms = [Permission::ReadChannel, Permission::VoiceSpeak]; /// if stack.has_all(&perms) { /// join_voice_channel(); /// } /// ``` pub fn has_all(&self, permissions: &[Permission]) -> bool { let required = permissions.iter().fold(0u64, |acc, &p| acc | p as u64); let mask = self.resolve(); (mask & required) == required } /// Vérifie si AU MOINS UNE des permissions listées est accordée. /// /// Utile pour les rôles de modération ou les accès "ou" (OR). /// /// ### Exemple : /// ```rust /// // L'utilisateur peut supprimer le message s'il est modérateur OU admin /// let moderator_perms = [Permission::DeleteOthersMessage, Permission::ManageChannel]; /// if stack.has_any(&moderator_perms) { /// delete_message(); /// } /// ``` pub fn has_any(&self, permissions: &[Permission]) -> bool { let required = permissions.iter().fold(0u64, |acc, &p| acc | p as u64); let mask = self.resolve(); (mask & required) != 0 } } #[cfg(test)] mod tests { use super::*; #[test] fn test_permission_stack() { let mut stack = PermissionStack::new(); // 1. Test des permissions par défaut du serveur stack.server = Some(Permission::ReadChannel as u64 | Permission::SendMessage as u64); assert!(stack.has(Permission::ReadChannel)); assert!(stack.has(Permission::SendMessage)); assert!(!stack.has(Permission::VoiceSpeak)); // 2. Test de l'union avec les groupes stack.groups = Some(Permission::VoiceSpeak as u64); assert!(stack.has(Permission::ReadChannel)); assert!(stack.has(Permission::SendMessage)); assert!(stack.has(Permission::VoiceSpeak)); // 3. Test du bypass administrateur (Saturation du masque) stack.server_user = Some(u64::MAX); assert!(stack.has(Permission::ManageServer)); assert!(stack.has(Permission::ManageRoles)); assert!(stack.has(Permission::KickMember)); // 4. Test spécifique au canal (Sans bypass admin) stack.server_user = None; stack.channel_user = Some(Permission::ManageChannel as u64); assert!(stack.has(Permission::ReadChannel)); // Vient du serveur assert!(stack.has(Permission::ManageChannel)); // Vient du channel_user assert!(!stack.has(Permission::ManageRoles)); // Pas défini } }