ef0b83a
use std::collections::HashSet;
use export::AppState;
use models::space::spaces;
use crate::authority;
use crate::errors::SpaceError;
use crate::services::service::SpaceService;
pub const VIEW_SPACE: &str = "view_space";
pub const READ: &str = "read";
pub const MESSAGE: &str = "message";
pub const MANAGE_MEMBERS: &str = "manage_members";
pub const MANAGE_ROLES: &str = "manage_roles";
pub const MANAGE_INVITES: &str = "manage_invites";
pub const MANAGE_BANS: &str = "manage_bans";
pub const MANAGE_CHANNELS: &str = "manage_channels";
pub const MANAGE_OVERWRITES: &str = "manage_overwrites";
pub const ALL_PERMISSIONS: [&str; 9] = [
VIEW_SPACE,
READ,
MESSAGE,
MANAGE_MEMBERS,
MANAGE_ROLES,
MANAGE_INVITES,
MANAGE_BANS,
MANAGE_CHANNELS,
MANAGE_OVERWRITES,
];
#[derive(Debug, Clone, Default)]
pub struct Permissions {
granted: HashSet<String>,
}
impl Permissions {
pub fn owner() -> Self {
Self {
granted: ALL_PERMISSIONS.into_iter().map(String::from).collect(),
}
}
pub fn from_vec(values: Vec<String>) -> Self {
Self {
granted: values.into_iter().collect(),
}
}
pub fn has(&self, action: &str) -> bool {
self.granted.contains(action)
}
pub fn into_vec(self) -> Vec<String> {
let mut v: Vec<String> = self.granted.into_iter().collect();
v.sort();
v.dedup();
v
}
}
pub async fn assert_not_banned(
state: &AppState,
space_id: uuid::Uuid,
user_id: i64,
) -> Result<(), SpaceError> {
if let Ok(Some(member)) = authority::member(state, user_id, space_id, user_id).await
&& member.status() == models::space::MemberStatus::Banned
{
return Err(SpaceError::Forbidden);
}
Ok(())
}
/// KenOAuth is the only source of truth for organization permission decisions.
/// There is deliberately no local owner shortcut and no version-keyed cache:
/// a stale local row must never be able to grant access.
pub async fn effective_permissions(
state: &AppState,
space: &spaces::Model,
user_id: i64,
) -> Result<Permissions, SpaceError> {
let names: Vec<String> = ALL_PERMISSIONS
.iter()
.map(|action| format!("space.{action}"))
.collect();
let allowed = authority::permissions(state, space.id, user_id, &names).await?;
let granted: Vec<String> = ALL_PERMISSIONS
.iter()
.zip(allowed)
.filter_map(|(action, allowed)| allowed.then(|| (*action).to_string()))
.collect();
if granted.is_empty() {
return Err(SpaceError::Forbidden);
}
Ok(Permissions::from_vec(granted))
}
pub async fn require_perm(
service: &SpaceService,
space: &spaces::Model,
caller: i64,
action: &str,
) -> Result<(), SpaceError> {
let perms = effective_permissions(&service.state, space, caller).await?;
if !perms.has(action) {
return Err(SpaceError::Forbidden);
}
Ok(())
}