439 lines
16 KiB
Rust
439 lines
16 KiB
Rust
use crate::models::computed_permission::PermissionScopeType;
|
|
use crate::models::{
|
|
channel, channel_role_permission, channel_user_permission, computed_permission, role_user,
|
|
server_role_permission, server_user, server_user_permission,
|
|
};
|
|
use crate::permissions::{ChannelPermission, ServerPermission};
|
|
use crate::services::ServicesContext;
|
|
use sea_orm::{ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QuerySelect, Set};
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PermissionService {
|
|
service_context: Arc<ServicesContext>,
|
|
}
|
|
|
|
impl PermissionService {
|
|
pub fn new(service_context: Arc<ServicesContext>) -> Self {
|
|
Self { service_context }
|
|
}
|
|
|
|
/// Recalcule de manière globale et optimisée `computed_permission` pour TOUS les utilisateurs
|
|
/// et TOUTES les ressources d'un serveur donné.
|
|
pub async fn sync_server(&self, server_id: Uuid) -> Result<(), anyhow::Error> {
|
|
let db = &self.service_context.repositories.server.context.db;
|
|
Self::sync_server_with_db(db, server_id).await
|
|
}
|
|
|
|
/// Recalcule TOUTES les entrées `computed_permission` d'un utilisateur sur un serveur.
|
|
pub async fn sync_user(&self, user_id: Uuid, server_id: Uuid) -> Result<(), anyhow::Error> {
|
|
let db = &self.service_context.repositories.server.context.db;
|
|
Self::sync_user_with_db(db, user_id, server_id).await
|
|
}
|
|
|
|
/// Recalcule le `computed_permission` pour UN utilisateur et UN canal spécifique.
|
|
pub async fn sync_channel_user_permission(
|
|
&self,
|
|
channel_id: Uuid,
|
|
user_id: Uuid,
|
|
) -> Result<(), anyhow::Error> {
|
|
let db = &self.service_context.repositories.server.context.db;
|
|
Self::sync_channel_user_permission_with_db(db, channel_id, user_id).await
|
|
}
|
|
}
|
|
|
|
impl PermissionService {
|
|
pub async fn sync_server_with_db<C>(db: &C, server_id: Uuid) -> Result<(), anyhow::Error>
|
|
where
|
|
C: ConnectionTrait,
|
|
{
|
|
// 1. Charger tous les canaux du serveur
|
|
let channels = channel::Entity::find()
|
|
.filter(channel::Column::ServerId.eq(server_id))
|
|
.all(db)
|
|
.await?;
|
|
|
|
let channel_ids: Vec<Uuid> = channels.iter().map(|c| c.id).collect();
|
|
|
|
// 2. Charger tous les utilisateurs du serveur
|
|
let user_ids: Vec<Uuid> = server_user::Entity::find()
|
|
.filter(server_user::Column::ServerId.eq(server_id))
|
|
.select_only()
|
|
.column(server_user::Column::UserId)
|
|
.into_tuple::<Uuid>()
|
|
.all(db)
|
|
.await?;
|
|
|
|
if user_ids.is_empty() {
|
|
// Aucun membre, nettoyer simplement le cache de ce serveur
|
|
computed_permission::Entity::delete_many()
|
|
.filter(computed_permission::Column::ServerId.eq(server_id))
|
|
.exec(db)
|
|
.await?;
|
|
return Ok(());
|
|
}
|
|
|
|
// 3. Charger en lot toutes les affectations de rôles des membres
|
|
let user_roles_models = role_user::Entity::find()
|
|
.filter(role_user::Column::UserId.is_in(user_ids.clone()))
|
|
.all(db)
|
|
.await?;
|
|
|
|
let mut roles_by_user: HashMap<Uuid, Vec<Uuid>> = HashMap::new();
|
|
for ur in user_roles_models {
|
|
roles_by_user
|
|
.entry(ur.user_id)
|
|
.or_default()
|
|
.push(ur.role_id);
|
|
}
|
|
|
|
// 4. Charger toutes les permissions serveur (Rôles & Utilisateurs)
|
|
let server_role_perms = server_role_permission::Entity::find()
|
|
.filter(server_role_permission::Column::ServerId.eq(server_id))
|
|
.all(db)
|
|
.await?;
|
|
|
|
let mut server_perm_by_role: HashMap<Uuid, ServerPermission> = HashMap::new();
|
|
for srp in server_role_perms {
|
|
server_perm_by_role.insert(
|
|
srp.role_id,
|
|
ServerPermission::from_bits_retain(srp.permissions as u64),
|
|
);
|
|
}
|
|
|
|
let server_user_perms = server_user_permission::Entity::find()
|
|
.filter(server_user_permission::Column::ServerId.eq(server_id))
|
|
.filter(server_user_permission::Column::UserId.is_in(user_ids.clone()))
|
|
.all(db)
|
|
.await?;
|
|
|
|
let mut server_perm_by_user: HashMap<Uuid, ServerPermission> = HashMap::new();
|
|
for sup in server_user_perms {
|
|
server_perm_by_user.insert(
|
|
sup.user_id,
|
|
ServerPermission::from_bits_retain(sup.permissions as u64),
|
|
);
|
|
}
|
|
|
|
// 5. Charger toutes les permissions de canaux (Rôles & Utilisateurs)
|
|
let channel_role_perms = if channel_ids.is_empty() {
|
|
Vec::new()
|
|
} else {
|
|
channel_role_permission::Entity::find()
|
|
.filter(channel_role_permission::Column::ChannelId.is_in(channel_ids.clone()))
|
|
.all(db)
|
|
.await?
|
|
};
|
|
|
|
let mut channel_role_perm_map: HashMap<(Uuid, Uuid), ChannelPermission> = HashMap::new();
|
|
for crp in channel_role_perms {
|
|
channel_role_perm_map.insert(
|
|
(crp.channel_id, crp.role_id),
|
|
ChannelPermission::from_bits_retain(crp.permissions as u64),
|
|
);
|
|
}
|
|
|
|
let channel_user_perms = if channel_ids.is_empty() {
|
|
Vec::new()
|
|
} else {
|
|
channel_user_permission::Entity::find()
|
|
.filter(channel_user_permission::Column::ChannelId.is_in(channel_ids.clone()))
|
|
.filter(channel_user_permission::Column::UserId.is_in(user_ids.clone()))
|
|
.all(db)
|
|
.await?
|
|
};
|
|
|
|
let mut channel_user_perm_map: HashMap<(Uuid, Uuid), ChannelPermission> = HashMap::new();
|
|
for cup in channel_user_perms {
|
|
channel_user_perm_map.insert(
|
|
(cup.channel_id, cup.user_id),
|
|
ChannelPermission::from_bits_retain(cup.permissions as u64),
|
|
);
|
|
}
|
|
|
|
// 6. Calcul en RAM pour l'ensemble des paires (Utilisateur x Ressource)
|
|
let mut to_insert: Vec<computed_permission::ActiveModel> = Vec::new();
|
|
|
|
for user_id in user_ids {
|
|
let user_roles = roles_by_user.get(&user_id);
|
|
|
|
// A. Permission Serveur
|
|
let mut final_server_perm = ServerPermission::empty();
|
|
if let Some(roles) = user_roles {
|
|
for role_id in roles {
|
|
if let Some(p) = server_perm_by_role.get(role_id) {
|
|
final_server_perm |= *p;
|
|
}
|
|
}
|
|
}
|
|
if let Some(p) = server_perm_by_user.get(&user_id) {
|
|
final_server_perm |= *p;
|
|
}
|
|
|
|
to_insert.push(computed_permission::ActiveModel {
|
|
user_id: Set(user_id),
|
|
server_id: Set(server_id),
|
|
scope_type: Set(PermissionScopeType::Server),
|
|
resource_id: Set(server_id),
|
|
permissions: Set(final_server_perm.bits() as i64),
|
|
..Default::default()
|
|
});
|
|
|
|
// B. Permissions par Canal
|
|
for channel in &channels {
|
|
let mut final_channel_perm = ChannelPermission::empty();
|
|
|
|
if let Some(roles) = user_roles {
|
|
for role_id in roles {
|
|
if let Some(p) = channel_role_perm_map.get(&(channel.id, *role_id)) {
|
|
final_channel_perm |= *p;
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(p) = channel_user_perm_map.get(&(channel.id, user_id)) {
|
|
final_channel_perm |= *p;
|
|
}
|
|
|
|
to_insert.push(computed_permission::ActiveModel {
|
|
user_id: Set(user_id),
|
|
server_id: Set(server_id),
|
|
scope_type: Set(PermissionScopeType::Channel),
|
|
resource_id: Set(channel.id),
|
|
permissions: Set(final_channel_perm.bits() as i64),
|
|
..Default::default()
|
|
});
|
|
}
|
|
}
|
|
|
|
// 7. Remplacement atomique complet pour le serveur en BDD
|
|
computed_permission::Entity::delete_many()
|
|
.filter(computed_permission::Column::ServerId.eq(server_id))
|
|
.exec(db)
|
|
.await?;
|
|
|
|
if !to_insert.is_empty() {
|
|
// Insertion par lots (chunking de 1000 pour éviter les limites de paramètres SQL)
|
|
for chunk in to_insert.chunks(1000) {
|
|
computed_permission::Entity::insert_many(chunk.to_vec())
|
|
.exec(db)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn sync_user_with_db<C>(
|
|
db: &C,
|
|
user_id: Uuid,
|
|
server_id: Uuid,
|
|
) -> Result<(), anyhow::Error>
|
|
where
|
|
C: ConnectionTrait,
|
|
{
|
|
// 1. Rôles de l'utilisateur
|
|
let role_ids = role_user::Entity::find()
|
|
.filter(role_user::Column::UserId.eq(user_id))
|
|
.select_only()
|
|
.column(role_user::Column::RoleId)
|
|
.into_tuple::<Uuid>()
|
|
.all(db)
|
|
.await?;
|
|
|
|
// 2. Permissions serveur des rôles
|
|
let mut server_permissions = ServerPermission::empty();
|
|
if !role_ids.is_empty() {
|
|
let role_permissions = server_role_permission::Entity::find()
|
|
.filter(server_role_permission::Column::ServerId.eq(server_id))
|
|
.filter(server_role_permission::Column::RoleId.is_in(role_ids.clone()))
|
|
.all(db)
|
|
.await?;
|
|
|
|
for permission in role_permissions {
|
|
server_permissions |=
|
|
ServerPermission::from_bits_retain(permission.permissions as u64);
|
|
}
|
|
}
|
|
|
|
// 3. Permissions serveur directes
|
|
if let Some(permission) = server_user_permission::Entity::find()
|
|
.filter(server_user_permission::Column::ServerId.eq(server_id))
|
|
.filter(server_user_permission::Column::UserId.eq(user_id))
|
|
.one(db)
|
|
.await?
|
|
{
|
|
server_permissions |= ServerPermission::from_bits_retain(permission.permissions as u64);
|
|
}
|
|
|
|
// 4. Canaux du serveur
|
|
let channels = channel::Entity::find()
|
|
.filter(channel::Column::ServerId.eq(server_id))
|
|
.all(db)
|
|
.await?;
|
|
|
|
let channel_ids: Vec<Uuid> = channels.iter().map(|c| c.id).collect();
|
|
|
|
// 5. Permissions de rôles pour les canaux
|
|
let role_channel_permissions = if role_ids.is_empty() || channel_ids.is_empty() {
|
|
Vec::new()
|
|
} else {
|
|
channel_role_permission::Entity::find()
|
|
.filter(channel_role_permission::Column::ChannelId.is_in(channel_ids.clone()))
|
|
.filter(channel_role_permission::Column::RoleId.is_in(role_ids))
|
|
.all(db)
|
|
.await?
|
|
};
|
|
|
|
let mut permissions_by_channel: HashMap<Uuid, ChannelPermission> = HashMap::new();
|
|
for permission in role_channel_permissions {
|
|
permissions_by_channel
|
|
.entry(permission.channel_id)
|
|
.or_default()
|
|
.insert(ChannelPermission::from_bits_retain(
|
|
permission.permissions as u64,
|
|
));
|
|
}
|
|
|
|
// 6. Permissions directes utilisateur pour les canaux
|
|
let user_channel_permissions = if channel_ids.is_empty() {
|
|
Vec::new()
|
|
} else {
|
|
channel_user_permission::Entity::find()
|
|
.filter(channel_user_permission::Column::UserId.eq(user_id))
|
|
.filter(channel_user_permission::Column::ChannelId.is_in(channel_ids))
|
|
.all(db)
|
|
.await?
|
|
};
|
|
|
|
for permission in user_channel_permissions {
|
|
permissions_by_channel
|
|
.entry(permission.channel_id)
|
|
.or_default()
|
|
.insert(ChannelPermission::from_bits_retain(
|
|
permission.permissions as u64,
|
|
));
|
|
}
|
|
|
|
// 7. Modèles à insérer
|
|
let mut computed_permissions = Vec::with_capacity(channels.len().saturating_add(1));
|
|
|
|
computed_permissions.push(computed_permission::ActiveModel {
|
|
user_id: Set(user_id),
|
|
server_id: Set(server_id),
|
|
scope_type: Set(PermissionScopeType::Server),
|
|
resource_id: Set(server_id),
|
|
permissions: Set(server_permissions.bits() as i64),
|
|
..Default::default()
|
|
});
|
|
|
|
for channel in channels {
|
|
let channel_permissions = permissions_by_channel
|
|
.remove(&channel.id)
|
|
.unwrap_or_else(ChannelPermission::empty);
|
|
|
|
computed_permissions.push(computed_permission::ActiveModel {
|
|
user_id: Set(user_id),
|
|
server_id: Set(server_id),
|
|
scope_type: Set(PermissionScopeType::Channel),
|
|
resource_id: Set(channel.id),
|
|
permissions: Set(channel_permissions.bits() as i64),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
// 8. Remplacement atomique BDD
|
|
computed_permission::Entity::delete_many()
|
|
.filter(computed_permission::Column::UserId.eq(user_id))
|
|
.filter(computed_permission::Column::ServerId.eq(server_id))
|
|
.exec(db)
|
|
.await?;
|
|
|
|
if !computed_permissions.is_empty() {
|
|
computed_permission::Entity::insert_many(computed_permissions)
|
|
.exec(db)
|
|
.await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn sync_channel_user_permission_with_db<C>(
|
|
db: &C,
|
|
channel_id: Uuid,
|
|
user_id: Uuid,
|
|
) -> Result<(), anyhow::Error>
|
|
where
|
|
C: ConnectionTrait,
|
|
{
|
|
// 1. Récupérer le canal pour connaître son server_id
|
|
let channel = channel::Entity::find_by_id(channel_id)
|
|
.one(db)
|
|
.await?
|
|
.ok_or_else(|| anyhow::anyhow!("Canal non trouvé"))?;
|
|
|
|
let server_id = match channel.server_id {
|
|
Some(sid) => sid,
|
|
None => return Ok(()), // Canal DM ou sans serveur
|
|
};
|
|
|
|
// 2. Rôles de l'utilisateur sur le serveur
|
|
let role_ids = role_user::Entity::find()
|
|
.filter(role_user::Column::UserId.eq(user_id))
|
|
.select_only()
|
|
.column(role_user::Column::RoleId)
|
|
.into_tuple::<Uuid>()
|
|
.all(db)
|
|
.await?;
|
|
|
|
// 3. Permissions cumulées des rôles sur ce canal
|
|
let mut computed = ChannelPermission::empty();
|
|
|
|
if !role_ids.is_empty() {
|
|
let role_perms = channel_role_permission::Entity::find()
|
|
.filter(channel_role_permission::Column::ChannelId.eq(channel_id))
|
|
.filter(channel_role_permission::Column::RoleId.is_in(role_ids))
|
|
.all(db)
|
|
.await?;
|
|
|
|
for p in role_perms {
|
|
computed |= ChannelPermission::from_bits_retain(p.permissions as u64);
|
|
}
|
|
}
|
|
|
|
// 4. Override direct utilisateur sur le canal
|
|
if let Some(user_perm) = channel_user_permission::Entity::find()
|
|
.filter(channel_user_permission::Column::ChannelId.eq(channel_id))
|
|
.filter(channel_user_permission::Column::UserId.eq(user_id))
|
|
.one(db)
|
|
.await?
|
|
{
|
|
computed |= ChannelPermission::from_bits_retain(user_perm.permissions as u64);
|
|
}
|
|
|
|
// 5. Suppression & insertion atomique de l'entrée computed_permission
|
|
computed_permission::Entity::delete_many()
|
|
.filter(computed_permission::Column::UserId.eq(user_id))
|
|
.filter(computed_permission::Column::ResourceId.eq(channel_id))
|
|
.exec(db)
|
|
.await?;
|
|
|
|
let active_model = computed_permission::ActiveModel {
|
|
user_id: Set(user_id),
|
|
server_id: Set(server_id),
|
|
scope_type: Set(PermissionScopeType::Channel),
|
|
resource_id: Set(channel_id),
|
|
permissions: Set(computed.bits() as i64),
|
|
..Default::default()
|
|
};
|
|
|
|
computed_permission::Entity::insert(active_model)
|
|
.exec(db)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|