Init
This commit is contained in:
@@ -1,6 +1,3 @@
|
||||
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
|
||||
@@ -70,67 +67,113 @@ impl Permission {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
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 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();
|
||||
|
||||
impl PermissionStack {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
global,
|
||||
channel_overrides,
|
||||
server: None,
|
||||
groups: None,
|
||||
server_user: None,
|
||||
server_groups: None,
|
||||
channel_user: None,
|
||||
channel_groups: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si une permission spécifique est accordée, optionnellement pour un canal précis.
|
||||
/// 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.
|
||||
///
|
||||
/// 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 :
|
||||
/// ### Exemple :
|
||||
/// ```rust
|
||||
/// if ctx.has(Permission::SendMessage, Some(channel_id)) {
|
||||
/// // Autorisé !
|
||||
/// if stack.has(Permission::SendMessage) {
|
||||
/// println!("L'utilisateur peut envoyer des messages !");
|
||||
/// }
|
||||
/// ```
|
||||
#[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
|
||||
};
|
||||
|
||||
pub fn has(&self, permission: Permission) -> bool {
|
||||
let mask = self.resolve();
|
||||
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))
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,45 +182,32 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_permission_context() {
|
||||
let channel_id = Uuid::new_v4();
|
||||
let other_channel_id = Uuid::new_v4();
|
||||
fn test_permission_stack() {
|
||||
let mut stack = PermissionStack::new();
|
||||
|
||||
// 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
|
||||
];
|
||||
// 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));
|
||||
|
||||
// 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,
|
||||
)];
|
||||
// 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));
|
||||
|
||||
let ctx = PermissionContext::new(&role_masks, channel_permissions);
|
||||
// 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));
|
||||
|
||||
// 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));
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user