use tonic::{Request, Response, Status}; use uuid::Uuid; use crate::models::channels::ChannelPermissionOverwrite; use crate::models::common::{OverwriteTarget, Role}; use crate::pb::im::permission_service_server::PermissionService; use crate::pb::im::{ CheckPermissionRequest, CheckPermissionResponse, DeletePermissionOverwriteRequest, DeletePermissionOverwriteResponse, EnsureReadableRequest, EnsureReadableResponse, GetPermissionOverwritesRequest, GetPermissionOverwritesResponse, GetPermissionsRequest, GetPermissionsResponse, PermissionOverwrite, ResolveChannelRequest, ResolveChannelResponse, SetPermissionOverwriteRequest, SetPermissionOverwriteResponse, }; use crate::service::util::role_level; use crate::service::AppService; pub struct PermissionGrpcService { service: AppService, } impl PermissionGrpcService { pub fn new(service: AppService) -> Self { Self { service } } fn parse_uuid(s: &str, field: &str) -> Result { Uuid::parse_str(s).map_err(|e| Status::invalid_argument(format!("{field}: {e}"))) } fn im_permission_to_str(v: i32) -> &'static str { match v { 1 => "READ_CHANNEL", 2 => "SEND_MESSAGE", 3 => "MANAGE_THREADS", 4 => "MANAGE_REACTIONS", 5 => "MANAGE_PINS", 6 => "INVITE_MEMBERS", 7 => "KICK_MEMBERS", 8 => "MANAGE_CHANNEL", 9 => "MANAGE_ROLES", 10 => "MANAGE_WEBHOOKS", 11 => "MANAGE_EMOJIS", 12 => "VIEW_AUDIT_LOG", 13 => "MANAGE_INTEGRATIONS", 14 => "SEND_TTS", 15 => "USE_SLASH_COMMANDS", 16 => "ATTACH_FILES", 17 => "MENTION_EVERYONE", 18 => "MANAGE_MESSAGES", 19 => "ADMIN", _ => "UNSPECIFIED", } } fn str_to_im_permission(s: &str) -> i32 { match s { "READ_CHANNEL" => 1, "SEND_MESSAGE" => 2, "MANAGE_THREADS" => 3, "MANAGE_REACTIONS" => 4, "MANAGE_PINS" => 5, "INVITE_MEMBERS" => 6, "KICK_MEMBERS" => 7, "MANAGE_CHANNEL" => 8, "MANAGE_ROLES" => 9, "MANAGE_WEBHOOKS" => 10, "MANAGE_EMOJIS" => 11, "VIEW_AUDIT_LOG" => 12, "MANAGE_INTEGRATIONS" => 13, "SEND_TTS" => 14, "USE_SLASH_COMMANDS" => 15, "ATTACH_FILES" => 16, "MENTION_EVERYONE" => 17, "MANAGE_MESSAGES" => 18, "ADMIN" => 19, _ => 0, } } fn permission_requires_role(p: i32) -> Role { match p { 1 => Role::Viewer, 2 => Role::Member, 3 => Role::Member, 4 => Role::Member, 5 => Role::Moderator, 6 => Role::Moderator, 7 => Role::Moderator, 8 => Role::Admin, 9 => Role::Admin, 10 => Role::Admin, 11 => Role::Admin, 12 => Role::Moderator, 13 => Role::Admin, 14 => Role::Member, 15 => Role::Member, 16 => Role::Member, 17 => Role::Moderator, 18 => Role::Moderator, 19 => Role::Admin, _ => Role::Owner, } } fn role_to_permissions(role: Role) -> Vec { let level = role_level(role); let mut perms = Vec::new(); if level >= role_level(Role::Viewer) { perms.push(1); } if level >= role_level(Role::Member) { perms.extend_from_slice(&[2, 3, 4, 14, 15, 16]); } if level >= role_level(Role::Moderator) { perms.extend_from_slice(&[5, 6, 7, 12, 17, 18]); } if level >= role_level(Role::Admin) { perms.extend_from_slice(&[8, 9, 10, 11, 13, 19]); } perms.sort(); perms.dedup(); perms } fn overwrite_to_proto(o: ChannelPermissionOverwrite) -> PermissionOverwrite { PermissionOverwrite { id: o.id.to_string(), channel_id: o.channel_id.to_string(), target_type: o.target_type.as_str().to_string(), target_id: o.target_id.to_string(), allow: o .allow .iter() .map(|p| Self::str_to_im_permission(p)) .collect(), deny: o .deny .iter() .map(|p| Self::str_to_im_permission(p)) .collect(), created_at: o.created_at.to_rfc3339(), updated_at: o.updated_at.to_rfc3339(), } } } #[tonic::async_trait] impl PermissionService for PermissionGrpcService { async fn check_permission( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?; let user_uid = Self::parse_uuid(&req.user_id, "user_id")?; let channel = self .service .im .resolve_channel(channel_id) .await .map_err(|e| Status::not_found(e.to_string()))?; let role = self .service .im .channel_member_role(channel.id, user_uid) .await .map_err(|e| Status::internal(e.to_string()))?; let required_role = Self::permission_requires_role(req.permission); let allowed = role_level(role) >= role_level(required_role); Ok(Response::new(CheckPermissionResponse { allowed, role: role.as_str().to_string(), })) } async fn get_permissions( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?; let user_uid = Self::parse_uuid(&req.user_id, "user_id")?; let channel = self .service .im .resolve_channel(channel_id) .await .map_err(|e| Status::not_found(e.to_string()))?; let role = self .service .im .channel_member_role(channel.id, user_uid) .await .map_err(|e| Status::internal(e.to_string()))?; let permissions = Self::role_to_permissions(role); Ok(Response::new(GetPermissionsResponse { permissions, role: role.as_str().to_string(), })) } async fn set_permission_overwrite( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?; let target_id = Self::parse_uuid(&req.target_id, "target_id")?; let target_type: OverwriteTarget = req.target_type.parse().unwrap_or(OverwriteTarget::Unknown); let allow: Vec = req .allow .into_iter() .map(|v| Self::im_permission_to_str(v).to_string()) .collect(); let deny: Vec = req .deny .into_iter() .map(|v| Self::im_permission_to_str(v).to_string()) .collect(); let now = chrono::Utc::now(); let id = Uuid::new_v4(); let overwrite = sqlx::query_as::<_, ChannelPermissionOverwrite>( "INSERT INTO channel_permission_overwrite \ (id, channel_id, target_type, target_id, allow, deny, created_by, created_at, updated_at) \ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) \ ON CONFLICT (channel_id, target_type, target_id) \ DO UPDATE SET allow = $5, deny = $6, updated_at = $9 \ RETURNING id, channel_id, target_type, target_id, allow, deny, created_by, created_at, updated_at", ) .bind(id) .bind(channel_id) .bind(target_type) .bind(target_id) .bind(&allow) .bind(&deny) .bind(Uuid::nil()) .bind(now) .bind(now) .fetch_one(self.service.ctx.db.writer()) .await .map_err(|e| Status::internal(e.to_string()))?; Ok(Response::new(SetPermissionOverwriteResponse { overwrite: Some(Self::overwrite_to_proto(overwrite)), })) } async fn get_permission_overwrites( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?; let overwrites = sqlx::query_as::<_, ChannelPermissionOverwrite>( "SELECT id, channel_id, target_type, target_id, allow, deny, created_by, created_at, updated_at \ FROM channel_permission_overwrite WHERE channel_id = $1", ) .bind(channel_id) .fetch_all(self.service.ctx.db.reader()) .await .map_err(|e| Status::internal(e.to_string()))?; let proto_overwrites: Vec<_> = overwrites .into_iter() .map(Self::overwrite_to_proto) .collect(); Ok(Response::new(GetPermissionOverwritesResponse { overwrites: proto_overwrites, })) } async fn delete_permission_overwrite( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?; let target_id = Self::parse_uuid(&req.target_id, "target_id")?; let target_type: OverwriteTarget = req.target_type.parse().unwrap_or(OverwriteTarget::Unknown); sqlx::query( "DELETE FROM channel_permission_overwrite \ WHERE channel_id = $1 AND target_type = $2 AND target_id = $3", ) .bind(channel_id) .bind(target_type) .bind(target_id) .execute(self.service.ctx.db.writer()) .await .map_err(|e| Status::internal(e.to_string()))?; Ok(Response::new(DeletePermissionOverwriteResponse {})) } async fn resolve_channel( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?; let channel = self .service .im .resolve_channel(channel_id) .await .map_err(|e| Status::not_found(e.to_string()))?; Ok(Response::new(ResolveChannelResponse { channel_id: channel.id.to_string(), workspace_id: channel.workspace_id.to_string(), name: channel.name, visibility: channel.visibility.as_str().to_string(), channel_type: channel.channel_type.as_str().to_string(), read_only: channel.read_only, archived: channel.archived, created_by: Some(channel.created_by.to_string()), })) } async fn ensure_readable( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); let channel_id = Self::parse_uuid(&req.channel_id, "channel_id")?; let user_uid = Self::parse_uuid(&req.user_id, "user_id")?; let channel = self .service .im .resolve_channel(channel_id) .await .map_err(|e| Status::not_found(e.to_string()))?; let allowed = self .service .im .ensure_channel_readable(user_uid, &channel) .await .is_ok(); Ok(Response::new(EnsureReadableResponse { allowed })) } }