Files
ox_speak_server_v2/src/utils/permissions.rs
2026-03-16 00:56:28 +01:00

214 lines
7.3 KiB
Rust

/// # 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<u64>,
/// Permissions de TOUS les groupes de l'utilisateur (None = aucun groupe)
pub groups: Option<u64>,
/// Permissions de l'utilisateur au niveau serveur (None = non défini)
pub server_user: Option<u64>,
/// Permissions de TOUS les groupes au niveau serveur (None = non défini)
pub server_groups: Option<u64>,
/// Permissions de l'utilisateur sur ce canal (None = non défini)
pub channel_user: Option<u64>,
/// Permissions de TOUS les groupes sur ce canal (None = non défini)
pub channel_groups: Option<u64>,
}
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
}
}