use std::collections::HashMap; use std::collections::HashSet; use models::dir::dir_overwrite_group; use models::dir::dir_overwrite_users; use models::dir::dirs; use models::space::space_member_groups; use models::space::spaces; use sea_orm::{ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter}; use uuid::Uuid; use crate::errors::SpaceError; pub const PERM_DIR_VISIBLE: i64 = 1 << 0; fn node_visible( caller: i64, my_groups: &HashSet, group_ows: &[dir_overwrite_group::Model], user_ows: &[dir_overwrite_users::Model], ) -> bool { if group_ows.is_empty() && user_ows.is_empty() { return true; } if let Some(uo) = user_ows.iter().find(|u| u.user_id == caller) { if (uo.deny_mask & PERM_DIR_VISIBLE) != 0 { return false; } if (uo.allow_mask & PERM_DIR_VISIBLE) != 0 { return true; } } let matching: Vec<&dir_overwrite_group::Model> = group_ows .iter() .filter(|g| my_groups.contains(&g.group_id)) .collect(); if matching .iter() .any(|g| (g.deny_mask & PERM_DIR_VISIBLE) != 0) { return false; } matching .iter() .any(|g| (g.allow_mask & PERM_DIR_VISIBLE) != 0) } pub async fn is_dir_visible( db: &impl ConnectionTrait, space: &spaces::Model, dir_id: Uuid, caller: i64, ) -> Result { if space.owner_id == caller { return Ok(true); } let target = dirs::Entity::find_by_id(dir_id).one(db).await?; let Some(target) = target else { return Ok(false); }; let my_groups: HashSet = space_member_groups::Entity::find() .filter(space_member_groups::Column::SpaceId.eq(space.id)) .filter(space_member_groups::Column::UserId.eq(caller)) .all(db) .await? .into_iter() .map(|mg| mg.group_id) .collect(); let nodes = dirs::Entity::find() .filter(dirs::Column::SpaceId.eq(space.id)) .all(db) .await?; let group_ows = dir_overwrite_group::Entity::find() .filter(dir_overwrite_group::Column::DirId.is_in(nodes.iter().map(|n| n.id))) .all(db) .await?; let user_ows = dir_overwrite_users::Entity::find() .filter(dir_overwrite_users::Column::DirId.is_in(nodes.iter().map(|n| n.id))) .all(db) .await?; let mut gmap: HashMap> = HashMap::new(); for g in group_ows { gmap.entry(g.dir_id).or_default().push(g); } let mut umap: HashMap> = HashMap::new(); for u in user_ows { umap.entry(u.dir_id).or_default().push(u); } let mut parent_map: HashMap> = HashMap::new(); for n in &nodes { parent_map.insert(n.id, n.parent_dir_id); } let direct = |id: Uuid| -> bool { node_visible( caller, &my_groups, gmap.get(&id).map(Vec::as_slice).unwrap_or(&[]), umap.get(&id).map(Vec::as_slice).unwrap_or(&[]), ) }; let mut current: Option = Some(target.id); let mut visited = HashSet::new(); while let Some(id) = current { if !visited.insert(id) { return Ok(false); } if !direct(id) { return Ok(false); } current = parent_map.get(&id).copied().flatten(); } Ok(true) }