This commit is contained in:
2026-03-09 01:30:12 +01:00
parent 74f4effd49
commit 50e1d4c25f
32 changed files with 1499 additions and 79 deletions

View File

@@ -1,4 +1,5 @@
pub mod auth;
pub mod password;
pub mod permissions;
pub mod ssh_auth;
pub mod toolbox;

183
src/utils/permissions.rs Normal file
View File

@@ -0,0 +1,183 @@
use std::collections::HashMap;
use uuid::Uuid;
/// # 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
}
}
/// Contexte de permissions calculé pour un utilisateur donné.
///
/// Ce contexte regroupe les permissions globales (issues de la fusion de tous les groupes
/// de l'utilisateur) et les surcharges spécifiques par canal (overrides).
#[derive(Debug, Clone)]
pub struct PermissionContext {
/// Union des masques de tous les groupes auxquels appartient l'utilisateur.
pub global: u64,
/// Masques spécifiques par canal (si présents, ils remplacent totalement le masque global
/// pour ce canal spécifique).
pub channel_overrides: HashMap<Uuid, u64>,
}
impl PermissionContext {
/// Crée un nouveau contexte à partir des masques bruts de la base de données.
///
/// - `group_masks` : Liste des permissions de chaque groupe de l'utilisateur.
/// - `channel_permissions` : Liste de (ID Canal, Masque) pour les permissions spécifiques.
pub fn new(group_masks: &[i64], channel_permissions: Vec<(Uuid, i64)>) -> Self {
// On combine tous les groupes avec l'opérateur OR (|)
let global = group_masks.iter().fold(0u64, |acc, &m| acc | m as u64);
let channel_overrides = channel_permissions
.into_iter()
.map(|(id, m)| (id, m as u64))
.collect();
Self {
global,
channel_overrides,
}
}
/// Vérifie si une permission spécifique est accordée, optionnellement pour un canal précis.
///
/// Si `channel_id` est fourni et qu'une surcharge existe pour ce canal, la surcharge
/// est utilisée. Sinon, on utilise les permissions globales du serveur.
///
/// ### Exemple d'utilisation :
/// ```rust
/// if ctx.has(Permission::SendMessage, Some(channel_id)) {
/// // Autorisé !
/// }
/// ```
#[inline]
pub fn has(&self, permission: Permission, channel_id: Option<Uuid>) -> bool {
let mask = if let Some(cid) = channel_id {
self.channel_overrides
.get(&cid)
.copied()
.unwrap_or(self.global)
} else {
self.global
};
mask & (permission as u64) != 0
}
/// Méthode utilitaire pour vérifier rapidement l'accès en lecture à un canal.
pub fn can_see_channel(&self, channel_id: Uuid) -> bool {
self.has(Permission::ReadChannel, Some(channel_id))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_permission_context() {
let channel_id = Uuid::new_v4();
let other_channel_id = Uuid::new_v4();
// On imagine un utilisateur avec 2 groupes
let role_masks = vec![
Permission::ReadChannel as i64 | Permission::SendMessage as i64, // Groupe 1 : lecture + envoi
Permission::VoiceSpeak as i64, // Groupe 2 : vocal
];
// Et une permission spécifique sur un canal (Lecture + Envoi + Gestion)
let channel_permissions = vec![(
channel_id,
Permission::ReadChannel as i64
| Permission::SendMessage as i64
| Permission::ManageChannel as i64,
)];
let ctx = PermissionContext::new(&role_masks, channel_permissions);
// Test des permissions globales
assert!(ctx.has(Permission::ReadChannel, None));
assert!(ctx.has(Permission::SendMessage, None));
assert!(ctx.has(Permission::VoiceSpeak, None));
assert!(!ctx.has(Permission::ManageChannel, None));
// Test des surcharges par canal (L'override remplace le masque global)
assert!(ctx.has(Permission::ReadChannel, Some(channel_id)));
assert!(ctx.has(Permission::SendMessage, Some(channel_id)));
assert!(ctx.has(Permission::ManageChannel, Some(channel_id)));
assert!(!ctx.has(Permission::VoiceSpeak, Some(channel_id))); // Ici on perd le vocal car l'override remplace tout
// Test d'un autre canal (pas de surcharge, on retombe sur le global)
assert!(ctx.has(Permission::ReadChannel, Some(other_channel_id)));
assert!(ctx.has(Permission::VoiceSpeak, Some(other_channel_id)));
assert!(!ctx.has(Permission::ManageChannel, Some(other_channel_id)));
// Test des fonctions sucre
assert!(ctx.can_see_channel(channel_id));
assert!(ctx.can_see_channel(other_channel_id));
}
}