- CI: workflows updates (cd, ci), remove playwright.yml - E2E: global-setup, auth/playlists/profile specs - Remove playwright-report and test-results artifacts from tracking - Backend: auth, handlers, services, workers, migrations - Frontend: components, features, vite config - Add e2e-results.json to gitignore - Docs: REMEDIATION_PROGRESS, audit archive - Rust: chat-server, stream-server updates
328 lines
No EOL
11 KiB
Rust
328 lines
No EOL
11 KiB
Rust
//! Module de gestion des messages avec filtrage de contenu et sécurité
|
|
//!
|
|
//! Ce module fournit une couche de haut niveau pour traiter les messages entrants,
|
|
//! appliquer les filtres de sécurité et déléguer aux modules métier appropriés.
|
|
|
|
use std::sync::Arc;
|
|
use tracing::{info, warn};
|
|
use uuid::Uuid;
|
|
use crate::error::{ChatError, Result};
|
|
use crate::hub::common::ChatHub;
|
|
use crate::permissions::Role;
|
|
use crate::security::{EnhancedSecurity, SecurityAction, ContentFilter};
|
|
use crate::security::permission::PermissionService;
|
|
|
|
/// Gestionnaire centralisé pour tous les types de messages
|
|
pub struct MessageHandler {
|
|
security: EnhancedSecurity,
|
|
content_filter: ContentFilter,
|
|
hub: Arc<ChatHub>,
|
|
permission_service: Arc<PermissionService>,
|
|
}
|
|
|
|
impl MessageHandler {
|
|
pub fn new(hub: Arc<ChatHub>, permission_service: Arc<PermissionService>) -> Result<Self> {
|
|
Ok(Self {
|
|
security: EnhancedSecurity::new()?,
|
|
content_filter: ContentFilter::new()?,
|
|
hub,
|
|
permission_service,
|
|
})
|
|
}
|
|
|
|
/// Gère les messages de salon avec permissions
|
|
pub async fn handle_room_message(
|
|
&mut self,
|
|
user_id: Uuid,
|
|
username: &str,
|
|
room: &str,
|
|
content: &str,
|
|
session_token: &str,
|
|
user_ip: &str,
|
|
parent_id: Option<Uuid>,
|
|
) -> Result<()> {
|
|
// Validation de sécurité
|
|
self.security.validate_request(
|
|
user_id,
|
|
user_ip,
|
|
session_token,
|
|
&SecurityAction::SendMessage,
|
|
Some(content)
|
|
).await?;
|
|
|
|
// Filtrage du contenu
|
|
let clean_room = self.content_filter.validate_content(room)?;
|
|
let clean_content = self.content_filter.validate_content(content)?;
|
|
|
|
info!(
|
|
user_id = %user_id,
|
|
username = %username,
|
|
room = %clean_room,
|
|
content_length = %clean_content.len(),
|
|
"📝 Message de salon filtré et validé"
|
|
);
|
|
|
|
// Délégation à la logique métier
|
|
let room_id = self.get_room_id_by_name(&clean_room).await?;
|
|
|
|
// Vérifier les permissions avant d'envoyer le message
|
|
self.permission_service
|
|
.can_send_message(user_id, room_id)
|
|
.await
|
|
.map_err(|e| {
|
|
warn!(
|
|
user_id = %user_id,
|
|
room_id = %room_id,
|
|
error = %e,
|
|
"Permission refusée pour l'envoi de message dans le salon"
|
|
);
|
|
e
|
|
})?;
|
|
|
|
crate::hub::channels::send_room_message(&self.hub, room_id, user_id, username, &clean_content, parent_id, None).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Gère les messages directs avec permissions
|
|
pub async fn handle_direct_message(
|
|
&mut self,
|
|
from_user: Uuid,
|
|
from_username: &str,
|
|
to_user: Uuid,
|
|
content: &str,
|
|
session_token: &str,
|
|
user_ip: &str,
|
|
parent_id: Option<Uuid>,
|
|
) -> Result<()> {
|
|
// Validation de sécurité
|
|
self.security.validate_request(
|
|
from_user,
|
|
user_ip,
|
|
session_token,
|
|
&SecurityAction::SendDM,
|
|
Some(content)
|
|
).await?;
|
|
|
|
// Filtrage du contenu
|
|
let clean_content = self.content_filter.validate_content(content)?;
|
|
|
|
info!(
|
|
from_user = %from_user,
|
|
from_username = %from_username,
|
|
to_user = %to_user,
|
|
content_length = %clean_content.len(),
|
|
"💬 Message direct filtré et validé"
|
|
);
|
|
|
|
// Délégation à la logique métier
|
|
let conversation_id = self.get_or_create_conversation(from_user, to_user).await?;
|
|
|
|
// Vérifier les permissions avant d'envoyer le message
|
|
self.permission_service
|
|
.can_send_message(from_user, conversation_id)
|
|
.await
|
|
.map_err(|e| {
|
|
warn!(
|
|
from_user = %from_user,
|
|
conversation_id = %conversation_id,
|
|
error = %e,
|
|
"Permission refusée pour l'envoi de message direct"
|
|
);
|
|
e
|
|
})?;
|
|
|
|
crate::hub::direct_messages::send_dm_message(&self.hub, conversation_id, from_user, from_username, &clean_content, parent_id, None).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Gère la jointure d'un salon avec permissions
|
|
pub async fn handle_join_room(
|
|
&mut self,
|
|
user_id: Uuid,
|
|
username: &str,
|
|
room: &str,
|
|
session_token: &str,
|
|
user_ip: &str,
|
|
) -> Result<()> {
|
|
// Validation de sécurité
|
|
self.security.validate_request(
|
|
user_id,
|
|
user_ip,
|
|
session_token,
|
|
&SecurityAction::JoinRoom,
|
|
None
|
|
).await?;
|
|
|
|
// Validation du nom de salon
|
|
let clean_room = self.content_filter.validate_content(room)?;
|
|
|
|
// Vérification que le salon existe ou peut être créé
|
|
// Pour l'instant, on suppose que le salon existe
|
|
let room_exists = true;
|
|
|
|
if !room_exists {
|
|
return Err(ChatError::not_found("Salon", &clean_room));
|
|
}
|
|
|
|
info!(
|
|
user_id = %user_id,
|
|
username = %username,
|
|
room = %clean_room,
|
|
"🚪 Jointure de salon validée"
|
|
);
|
|
|
|
// Délégation à la logique métier
|
|
let room_id = self.get_room_id_by_name(&clean_room).await?;
|
|
crate::hub::channels::join_room(&self.hub, room_id, user_id).await?;
|
|
|
|
// Envoi de confirmation
|
|
Ok(())
|
|
}
|
|
|
|
/// Gère la récupération d'historique avec permissions
|
|
pub async fn handle_room_history(
|
|
&mut self,
|
|
user_id: Uuid,
|
|
user_role: &Role,
|
|
room: &str,
|
|
limit: Option<i32>,
|
|
session_token: &str,
|
|
user_ip: &str,
|
|
) -> Result<Vec<crate::hub::channels::RoomMessage>> {
|
|
// Validation de sécurité pour la lecture
|
|
self.security.validate_request(
|
|
user_id,
|
|
user_ip,
|
|
session_token,
|
|
&SecurityAction::SendMessage, // Approximation
|
|
None
|
|
).await?;
|
|
|
|
// Validation du nom de salon
|
|
let clean_room = self.content_filter.validate_content(room)?;
|
|
let limit = limit.unwrap_or(50).min(100); // Limiter à 100 messages max
|
|
|
|
// Vérification des permissions de lecture
|
|
if !self.can_read_room_history(user_id, user_role, &clean_room).await? {
|
|
return Err(ChatError::unauthorized("Lecture de l'historique du salon"));
|
|
}
|
|
|
|
// Délégation à la logique métier
|
|
let room_id = self.get_room_id_by_name(&clean_room).await?;
|
|
let messages = crate::hub::channels::fetch_room_history(&self.hub, room_id, user_id, limit as i64, None).await?;
|
|
|
|
// Envoi de la réponse
|
|
info!(
|
|
user_id = %user_id,
|
|
room = %clean_room,
|
|
message_count = %messages.len(),
|
|
"📚 Historique de salon récupéré"
|
|
);
|
|
|
|
Ok(messages)
|
|
}
|
|
|
|
/// Gère la récupération d'historique DM avec permissions
|
|
pub async fn handle_dm_history(
|
|
&mut self,
|
|
user_id: Uuid,
|
|
with_user: Uuid,
|
|
limit: Option<i32>,
|
|
session_token: &str,
|
|
user_ip: &str,
|
|
) -> Result<Vec<crate::hub::direct_messages::DmMessage>> {
|
|
// Validation de sécurité
|
|
self.security.validate_request(
|
|
user_id,
|
|
user_ip,
|
|
session_token,
|
|
&SecurityAction::SendDM,
|
|
None
|
|
).await?;
|
|
|
|
let limit = limit.unwrap_or(50).min(100);
|
|
|
|
// Vérification que l'utilisateur peut lire cette conversation
|
|
if !self.can_read_dm_conversation(user_id, with_user).await? {
|
|
return Err(ChatError::unauthorized("Lecture de conversation privée"));
|
|
}
|
|
|
|
// Délégation à la logique métier
|
|
let conversation_id = self.get_or_create_conversation(user_id, with_user).await?;
|
|
let messages = crate::hub::direct_messages::fetch_history(&self.hub, conversation_id, user_id, limit as i64, None).await?;
|
|
|
|
// Envoi de la réponse
|
|
info!(
|
|
user_id = %user_id,
|
|
with_user = %with_user,
|
|
message_count = %messages.len(),
|
|
"💬 Historique DM récupéré"
|
|
);
|
|
|
|
Ok(messages)
|
|
}
|
|
|
|
/// Vérifie si un utilisateur peut lire l'historique d'un salon
|
|
async fn can_read_room_history(&self, user_id: Uuid, user_role: &Role, room: &str) -> Result<bool> {
|
|
// Logique simple : les admins et modérateurs peuvent tout lire
|
|
match user_role {
|
|
Role::Admin | Role::Moderator | Role::SuperAdmin => Ok(true),
|
|
Role::User => {
|
|
// Les utilisateurs normaux peuvent lire les salons dont ils sont membres
|
|
let room_id = self.get_room_id_by_name(room).await?;
|
|
self.permission_service
|
|
.can_read_conversation(user_id, room_id)
|
|
.await
|
|
.map(|_| true)
|
|
.or_else(|e| {
|
|
warn!(
|
|
user_id = %user_id,
|
|
room = %room,
|
|
error = %e,
|
|
"Permission refusée pour la lecture de l'historique"
|
|
);
|
|
Ok(false)
|
|
})
|
|
}
|
|
_ => Ok(false),
|
|
}
|
|
}
|
|
|
|
/// Vérifie si un utilisateur peut lire une conversation DM
|
|
async fn can_read_dm_conversation(&self, user_id: Uuid, with_user: Uuid) -> Result<bool> {
|
|
// Un utilisateur peut lire ses propres conversations
|
|
if user_id == with_user {
|
|
return Ok(true);
|
|
}
|
|
|
|
// Récupérer ou créer la conversation entre les deux utilisateurs
|
|
let conversation_id = self.get_or_create_conversation(user_id, with_user).await?;
|
|
|
|
// Vérifier les permissions
|
|
self.permission_service
|
|
.can_read_conversation(user_id, conversation_id)
|
|
.await
|
|
.map(|_| true)
|
|
.or_else(|e| {
|
|
warn!(
|
|
user_id = %user_id,
|
|
with_user = %with_user,
|
|
conversation_id = %conversation_id,
|
|
error = %e,
|
|
"Permission refusée pour la lecture de la conversation DM"
|
|
);
|
|
Ok(false)
|
|
})
|
|
}
|
|
|
|
/// Récupère ou crée une conversation entre deux utilisateurs
|
|
async fn get_or_create_conversation(&self, user1_id: Uuid, user2_id: Uuid) -> Result<Uuid> {
|
|
let conversation = crate::hub::direct_messages::get_or_create_dm_conversation(&self.hub, user1_id, user2_id).await?;
|
|
Ok(conversation.id)
|
|
}
|
|
|
|
/// Récupère l'ID d'un salon par son nom
|
|
async fn get_room_id_by_name(&self, room_name: &str) -> Result<Uuid> {
|
|
crate::hub::channels::get_room_id_by_name(&self.hub, room_name).await
|
|
}
|
|
}
|