diff --git a/src/api/v1/channels/uuid/mod.rs b/src/api/v1/channels/uuid/mod.rs index 1cb20c7..bece6ed 100644 --- a/src/api/v1/channels/uuid/mod.rs +++ b/src/api/v1/channels/uuid/mod.rs @@ -4,11 +4,7 @@ pub mod messages; pub mod socket; use crate::{ - Data, - api::v1::auth::check_access_token, - error::Error, - objects::{Channel, Member}, - utils::{get_auth_header, global_checks}, + api::v1::auth::check_access_token, error::Error, objects::{Channel, Member, Permissions}, utils::{get_auth_header, global_checks}, Data }; use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web}; use serde::Deserialize; @@ -59,7 +55,9 @@ pub async fn delete( let channel = Channel::fetch_one(&data, channel_uuid).await?; - Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?; + let member = Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?; + + member.check_permission(&data, Permissions::DeleteChannel).await?; channel.delete(&data).await?; @@ -125,7 +123,9 @@ pub async fn patch( let mut channel = Channel::fetch_one(&data, channel_uuid).await?; - Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?; + let member = Member::check_membership(&mut conn, uuid, channel.guild_uuid).await?; + + member.check_permission(&data, Permissions::ManageChannel).await?; if let Some(new_name) = &new_info.name { channel.set_name(&data, new_name.to_string()).await?; diff --git a/src/api/v1/guilds/uuid/channels.rs b/src/api/v1/guilds/uuid/channels.rs index 083553a..db895e4 100644 --- a/src/api/v1/guilds/uuid/channels.rs +++ b/src/api/v1/guilds/uuid/channels.rs @@ -1,9 +1,5 @@ use crate::{ - Data, - api::v1::auth::check_access_token, - error::Error, - objects::{Channel, Member}, - utils::{get_auth_header, global_checks, order_by_is_above}, + api::v1::auth::check_access_token, error::Error, objects::{Channel, Member, Permissions}, utils::{get_auth_header, global_checks, order_by_is_above}, Data }; use ::uuid::Uuid; use actix_web::{HttpRequest, HttpResponse, get, post, web}; @@ -74,9 +70,9 @@ pub async fn create( global_checks(&data, uuid).await?; - Member::check_membership(&mut conn, uuid, guild_uuid).await?; + let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?; - // FIXME: Logic to check permissions, should probably be done in utils.rs + member.check_permission(&data, Permissions::CreateChannel).await?; let channel = Channel::new( data.clone(), diff --git a/src/api/v1/guilds/uuid/icon.rs b/src/api/v1/guilds/uuid/icon.rs index 5025416..4061585 100644 --- a/src/api/v1/guilds/uuid/icon.rs +++ b/src/api/v1/guilds/uuid/icon.rs @@ -5,11 +5,7 @@ use futures_util::StreamExt as _; use uuid::Uuid; use crate::{ - Data, - api::v1::auth::check_access_token, - error::Error, - objects::{Guild, Member}, - utils::{get_auth_header, global_checks}, + api::v1::auth::check_access_token, error::Error, objects::{Guild, Member, Permissions}, utils::{get_auth_header, global_checks}, Data }; /// `PUT /api/v1/guilds/{uuid}/icon` Icon upload @@ -36,7 +32,9 @@ pub async fn upload( global_checks(&data, uuid).await?; - Member::check_membership(&mut conn, uuid, guild_uuid).await?; + let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?; + + member.check_permission(&data, Permissions::ManageServer).await?; let mut guild = Guild::fetch_one(&mut conn, guild_uuid).await?; diff --git a/src/api/v1/guilds/uuid/invites/mod.rs b/src/api/v1/guilds/uuid/invites/mod.rs index bb8269c..eb8d2ce 100644 --- a/src/api/v1/guilds/uuid/invites/mod.rs +++ b/src/api/v1/guilds/uuid/invites/mod.rs @@ -3,11 +3,7 @@ use serde::Deserialize; use uuid::Uuid; use crate::{ - Data, - api::v1::auth::check_access_token, - error::Error, - objects::{Guild, Member}, - utils::{get_auth_header, global_checks}, + api::v1::auth::check_access_token, error::Error, objects::{Guild, Member, Permissions}, utils::{get_auth_header, global_checks}, Data }; #[derive(Deserialize)] @@ -61,7 +57,9 @@ pub async fn create( global_checks(&data, uuid).await?; - Member::check_membership(&mut conn, uuid, guild_uuid).await?; + let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?; + + member.check_permission(&data, Permissions::CreateInvite).await?; let guild = Guild::fetch_one(&mut conn, guild_uuid).await?; diff --git a/src/api/v1/guilds/uuid/roles/mod.rs b/src/api/v1/guilds/uuid/roles/mod.rs index 717b30b..c33f144 100644 --- a/src/api/v1/guilds/uuid/roles/mod.rs +++ b/src/api/v1/guilds/uuid/roles/mod.rs @@ -3,11 +3,7 @@ use actix_web::{HttpRequest, HttpResponse, get, post, web}; use serde::Deserialize; use crate::{ - Data, - api::v1::auth::check_access_token, - error::Error, - objects::{Member, Role}, - utils::{get_auth_header, global_checks, order_by_is_above}, + api::v1::auth::check_access_token, error::Error, objects::{Member, Permissions, Role}, utils::{get_auth_header, global_checks, order_by_is_above}, Data }; pub mod uuid; @@ -70,9 +66,9 @@ pub async fn create( global_checks(&data, uuid).await?; - Member::check_membership(&mut conn, uuid, guild_uuid).await?; + let member = Member::check_membership(&mut conn, uuid, guild_uuid).await?; - // FIXME: Logic to check permissions, should probably be done in utils.rs + member.check_permission(&data, Permissions::CreateRole).await?; let role = Role::new(&mut conn, guild_uuid, role_info.name.clone()).await?; diff --git a/src/objects/member.rs b/src/objects/member.rs index 67312e4..20bc848 100644 --- a/src/objects/member.rs +++ b/src/objects/member.rs @@ -5,7 +5,7 @@ use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{Conn, Data, error::Error, schema::guild_members}; +use crate::{error::Error, objects::{Permissions, Role}, schema::guild_members, Conn, Data}; use super::{User, load_or_empty}; @@ -21,7 +21,7 @@ pub struct MemberBuilder { } impl MemberBuilder { - async fn build(&self, data: &Data) -> Result { + pub async fn build(&self, data: &Data) -> Result { let user = User::fetch_one(data, self.user_uuid).await?; Ok(Member { @@ -33,6 +33,18 @@ impl MemberBuilder { user, }) } + + pub async fn check_permission(&self, data: &Data, permission: Permissions) -> Result<(), Error> { + if !self.is_owner { + let roles = Role::fetch_from_member(&data, self.uuid).await?; + let allowed = roles.iter().any(|r| r.permissions & permission as i64 != 0); + if !allowed { + return Err(Error::Forbidden("Not allowed".to_string())) + } + } + + Ok(()) + } } #[derive(Serialize, Deserialize)] @@ -61,16 +73,16 @@ impl Member { conn: &mut Conn, user_uuid: Uuid, guild_uuid: Uuid, - ) -> Result<(), Error> { + ) -> Result { use guild_members::dsl; - dsl::guild_members + let member_builder = dsl::guild_members .filter(dsl::user_uuid.eq(user_uuid)) .filter(dsl::guild_uuid.eq(guild_uuid)) .select(MemberBuilder::as_select()) .get_result(conn) .await?; - Ok(()) + Ok(member_builder) } pub async fn fetch_one(data: &Data, user_uuid: Uuid, guild_uuid: Uuid) -> Result { diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 7b45957..30a0a64 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -27,6 +27,7 @@ pub use member::Member; pub use message::Message; pub use password_reset_token::PasswordResetToken; pub use role::Role; +pub use role::Permissions; pub use user::User; use crate::error::Error; @@ -111,44 +112,6 @@ impl MailClient { } } -#[derive(Clone, Copy)] -pub enum Permissions { - SendMessage = 1, - CreateChannel = 2, - DeleteChannel = 4, - ManageChannel = 8, - CreateRole = 16, - DeleteRole = 32, - ManageRole = 64, - CreateInvite = 128, - ManageInvite = 256, - ManageServer = 512, - ManageMember = 1024, -} - -impl Permissions { - pub fn fetch_permissions(permissions: i64) -> Vec { - let all_perms = vec![ - Self::SendMessage, - Self::CreateChannel, - Self::DeleteChannel, - Self::ManageChannel, - Self::CreateRole, - Self::DeleteRole, - Self::ManageRole, - Self::CreateInvite, - Self::ManageInvite, - Self::ManageServer, - Self::ManageMember, - ]; - - all_perms - .into_iter() - .filter(|p| permissions & (*p as i64) != 0) - .collect() - } -} - #[derive(Deserialize)] pub struct StartAmountQuery { pub start: Option, diff --git a/src/objects/role.rs b/src/objects/role.rs index f67dc6d..a78798a 100644 --- a/src/objects/role.rs +++ b/src/objects/role.rs @@ -3,14 +3,14 @@ use diesel::{ update, }; use diesel_async::RunQueryDsl; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{Conn, error::Error, schema::roles, utils::order_by_is_above}; +use crate::{error::Error, schema::{role_members, roles}, utils::order_by_is_above, Conn, Data}; use super::{HasIsAbove, HasUuid, load_or_empty}; -#[derive(Serialize, Clone, Queryable, Selectable, Insertable)] +#[derive(Deserialize, Serialize, Clone, Queryable, Selectable, Insertable)] #[diesel(table_name = roles)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Role { @@ -19,7 +19,28 @@ pub struct Role { name: String, color: i32, is_above: Option, - permissions: i64, + pub permissions: i64, +} + +#[derive(Serialize, Clone, Queryable, Selectable, Insertable)] +#[diesel(table_name = role_members)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct RoleMember { + role_uuid: Uuid, + member_uuid: Uuid, +} + +impl RoleMember { + async fn fetch_role(&self, conn: &mut Conn) -> Result { + use roles::dsl; + let role: Role = dsl::roles + .filter(dsl::uuid.eq(self.role_uuid)) + .select(Role::as_select()) + .get_result(conn) + .await?; + + Ok(role) + } } impl HasUuid for Role { @@ -48,6 +69,33 @@ impl Role { Ok(roles) } + pub async fn fetch_from_member(data: &Data, member_uuid: Uuid) -> Result, Error> { + if let Ok(roles) = data.get_cache_key(format!("{}_roles", member_uuid)).await { + return Ok(serde_json::from_str(&roles)?) + } + + let mut conn = data.pool.get().await?; + + use role_members::dsl; + let role_memberships: Vec = load_or_empty( + dsl::role_members + .filter(dsl::member_uuid.eq(member_uuid)) + .select(RoleMember::as_select()) + .load(&mut conn) + .await, + )?; + + let mut roles = vec![]; + + for membership in role_memberships { + roles.push(membership.fetch_role(&mut conn).await?); + } + + data.set_cache_key(format!("{}_roles", member_uuid), roles.clone(), 300).await?; + + Ok(roles) + } + pub async fn fetch_one(conn: &mut Conn, role_uuid: Uuid) -> Result { use roles::dsl; let role: Role = dsl::roles @@ -59,6 +107,10 @@ impl Role { Ok(role) } + pub async fn fetch_permissions(&self) -> Vec { + Permissions::fetch_permissions(self.permissions.clone()) + } + pub async fn new(conn: &mut Conn, guild_uuid: Uuid, name: String) -> Result { let role_uuid = Uuid::now_v7(); @@ -94,3 +146,41 @@ impl Role { Ok(new_role) } } + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Permissions { + SendMessage = 1, + CreateChannel = 2, + DeleteChannel = 4, + ManageChannel = 8, + CreateRole = 16, + DeleteRole = 32, + ManageRole = 64, + CreateInvite = 128, + ManageInvite = 256, + ManageServer = 512, + ManageMember = 1024, +} + +impl Permissions { + pub fn fetch_permissions(permissions: i64) -> Vec { + let all_perms = vec![ + Self::SendMessage, + Self::CreateChannel, + Self::DeleteChannel, + Self::ManageChannel, + Self::CreateRole, + Self::DeleteRole, + Self::ManageRole, + Self::CreateInvite, + Self::ManageInvite, + Self::ManageServer, + Self::ManageMember, + ]; + + all_perms + .into_iter() + .filter(|p| permissions & (*p as i64) != 0) + .collect() + } +}