diff --git a/src/database/permissions/server.rs b/src/database/permissions/server.rs index 611d770c..d7af8896 100644 --- a/src/database/permissions/server.rs +++ b/src/database/permissions/server.rs @@ -37,7 +37,7 @@ bitfield! { pub struct ServerPermissions(MSB0 [u32]); u32; pub get_view, _: 31; - pub get_manage_members, _: 30; + pub get_manage_roles, _: 30; pub get_manage_channels, _: 29; pub get_manage_server, _: 28; pub get_kick_members, _: 27; diff --git a/src/notifications/events.rs b/src/notifications/events.rs index 7d4c5677..4490d904 100644 --- a/src/notifications/events.rs +++ b/src/notifications/events.rs @@ -129,6 +129,15 @@ pub enum ClientboundNotification { id: String, user: String, }, + ServerRoleUpdate { + id: String, + role_id: String, + data: JsonValue + }, + ServerRoleDelete { + id: String, + role_id: String + }, UserUpdate { id: String, diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs index 8eadba74..29f75f9a 100644 --- a/src/routes/channels/mod.rs +++ b/src/routes/channels/mod.rs @@ -16,6 +16,8 @@ mod message_fetch; mod message_query; mod message_query_stale; mod message_send; +mod permissions_set; +mod permissions_set_default; pub fn routes() -> Vec { routes![ @@ -35,5 +37,7 @@ pub fn routes() -> Vec { group_add_member::req, group_remove_member::req, voice_join::req, + permissions_set::req, + permissions_set_default::req, ] } diff --git a/src/routes/channels/permissions_set.rs b/src/routes/channels/permissions_set.rs new file mode 100644 index 00000000..34984a99 --- /dev/null +++ b/src/routes/channels/permissions_set.rs @@ -0,0 +1,69 @@ +use mongodb::bson::doc; +use rocket_contrib::json::Json; +use serde::{Serialize, Deserialize}; +use validator::Contains; + +use crate::database::*; +use crate::database::permissions::channel::ChannelPermission; +use crate::notifications::events::ClientboundNotification; +use crate::util::result::{Error, Result}; + +#[derive(Serialize, Deserialize)] +pub struct Data { + permissions: u32 +} + +#[put("//permissions/", data = "")] +pub async fn req(user: User, target: Ref, role: String, data: Json) -> Result<()> { + let target = target.fetch_channel().await?; + + match target { + Channel::TextChannel { id, server, mut role_permissions, .. } + | Channel::VoiceChannel { id, server, mut role_permissions, .. } => { + let target = Ref::from_unchecked(server).fetch_server().await?; + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() + .await?; + + if !perm.get_manage_roles() { + return Err(Error::MissingPermission); + } + + if !target.roles.has_element(&role) { + return Err(Error::NotFound); + } + + let permissions: u32 = ChannelPermission::View as u32 | data.permissions; + + get_collection("channels") + .update_one( + doc! { "_id": &id }, + doc! { + "$set": { + "role_permissions.".to_owned() + &role: permissions as i32 + } + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "channel" + })?; + + role_permissions.insert(role, permissions as i32); + ClientboundNotification::ChannelUpdate { + id: id.clone(), + data: json!({ + "role_permissions": role_permissions + }), + clear: None + } + .publish(id); + + Ok(()) + } + _ => Err(Error::InvalidOperation) + } +} diff --git a/src/routes/channels/permissions_set_default.rs b/src/routes/channels/permissions_set_default.rs new file mode 100644 index 00000000..49c8e30f --- /dev/null +++ b/src/routes/channels/permissions_set_default.rs @@ -0,0 +1,97 @@ +use mongodb::bson::doc; +use rocket_contrib::json::Json; +use serde::{Serialize, Deserialize}; + +use crate::database::*; +use crate::database::permissions::channel::{ ChannelPermission, DEFAULT_PERMISSION_DM }; +use crate::notifications::events::ClientboundNotification; +use crate::util::result::{Error, Result}; + +#[derive(Serialize, Deserialize)] +pub struct Data { + permissions: u32 +} + +#[put("//permissions/default", data = "", rank = 1)] +pub async fn req(user: User, target: Ref, data: Json) -> Result<()> { + let target = target.fetch_channel().await?; + + match target { + Channel::Group { id, owner, .. } => { + if user.id == owner { + let permissions: u32 = ChannelPermission::View as u32 | (data.permissions & *DEFAULT_PERMISSION_DM); + + get_collection("channels") + .update_one( + doc! { "_id": &id }, + doc! { + "$set": { + "permissions": permissions as i32 + } + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "channel" + })?; + + ClientboundNotification::ChannelUpdate { + id: id.clone(), + data: json!({ + "permissions": permissions as i32 + }), + clear: None + } + .publish(id); + + Ok(()) + } else { + Err(Error::MissingPermission) + } + } + Channel::TextChannel { id, server, .. } + | Channel::VoiceChannel { id, server, .. } => { + let target = Ref::from_unchecked(server).fetch_server().await?; + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() + .await?; + + if !perm.get_manage_roles() { + return Err(Error::MissingPermission); + } + + let permissions: u32 = ChannelPermission::View as u32 | data.permissions; + + get_collection("channels") + .update_one( + doc! { "_id": &id }, + doc! { + "$set": { + "default_permissions": permissions as i32 + } + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "channel" + })?; + + ClientboundNotification::ChannelUpdate { + id: id.clone(), + data: json!({ + "default_permissions": permissions as i32 + }), + clear: None + } + .publish(id); + + Ok(()) + } + _ => Err(Error::InvalidOperation) + } +} diff --git a/src/routes/servers/ban_list.rs b/src/routes/servers/ban_list.rs index 62e8c82e..5127a7aa 100644 --- a/src/routes/servers/ban_list.rs +++ b/src/routes/servers/ban_list.rs @@ -14,8 +14,8 @@ pub async fn req(user: User, target: Ref) -> Result { .for_server() .await?; - if !perm.get_manage_members() { - Err(Error::MissingPermission)? + if !perm.get_ban_members() { + return Err(Error::MissingPermission); } let mut cursor = get_collection("server_bans") diff --git a/src/routes/servers/member_remove.rs b/src/routes/servers/member_remove.rs index 5e76b2b6..8fce3a14 100644 --- a/src/routes/servers/member_remove.rs +++ b/src/routes/servers/member_remove.rs @@ -12,7 +12,7 @@ pub async fn req(user: User, target: Ref, member: String) -> Result<()> { .for_server() .await?; - if !perm.get_manage_members() { + if !perm.get_kick_members() { return Err(Error::MissingPermission); } diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs index d9d700fc..2f03c357 100644 --- a/src/routes/servers/mod.rs +++ b/src/routes/servers/mod.rs @@ -18,6 +18,9 @@ mod ban_remove; mod invites_fetch; +mod roles_create; +mod roles_delete; + pub fn routes() -> Vec { routes![ server_create::req, @@ -32,6 +35,8 @@ pub fn routes() -> Vec { ban_create::req, ban_remove::req, ban_list::req, - invites_fetch::req + invites_fetch::req, + roles_create::req, + roles_delete::req ] } diff --git a/src/routes/servers/roles_create.rs b/src/routes/servers/roles_create.rs new file mode 100644 index 00000000..6453642a --- /dev/null +++ b/src/routes/servers/roles_create.rs @@ -0,0 +1,75 @@ +use crate::database::*; +use crate::notifications::events::ClientboundNotification; +use crate::util::result::{Error, Result}; + +use ulid::Ulid; +use mongodb::bson::doc; +use validator::Validate; +use serde::{Serialize, Deserialize}; +use rocket_contrib::json::{Json, JsonValue}; + +#[derive(Validate, Serialize, Deserialize)] +pub struct Data { + #[validate(length(min = 1, max = 32))] + name: String +} + +#[post("//roles", data = "")] +pub async fn req(user: User, target: Ref, data: Json) -> Result { + let data = data.into_inner(); + data.validate() + .map_err(|error| Error::FailedValidation { error })?; + + let target = target.fetch_server().await?; + + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() + .await?; + + if !perm.get_manage_roles() { + Err(Error::MissingPermission)? + } + + let id = Ulid::new().to_string(); + let perm_tuple = ( + *permissions::server::DEFAULT_PERMISSION as i32, + *permissions::channel::DEFAULT_PERMISSION_SERVER as i32 + ); + + get_collection("servers") + .update_one( + doc! { + "_id": &id + }, + doc! { + "$set": { + "roles.".to_owned() + &id: { + "name": &data.name, + "permissions": [ + &perm_tuple.0, + &perm_tuple.1 + ] + } + } + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "servers" + })?; + + ClientboundNotification::ServerRoleUpdate { + id: target.id.clone(), + role_id: id.clone(), + data: json!({ + "name": data.name, + "permissions": &perm_tuple + }) + } + .publish(target.id); + + Ok(json!({ "id": id, "permissions": perm_tuple })) +} diff --git a/src/routes/servers/roles_delete.rs b/src/routes/servers/roles_delete.rs new file mode 100644 index 00000000..baab78aa --- /dev/null +++ b/src/routes/servers/roles_delete.rs @@ -0,0 +1,48 @@ +use crate::database::*; +use crate::notifications::events::ClientboundNotification; +use crate::util::result::{Error, Result}; + +use mongodb::bson::doc; + +#[delete("//roles/")] +pub async fn req(user: User, target: Ref, role_id: String) -> Result<()> { + let target = target.fetch_server().await?; + + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() + .await?; + + if !perm.get_manage_roles() { + Err(Error::MissingPermission)? + } + + get_collection("servers") + .update_one( + doc! { + "_id": &role_id + }, + doc! { + "$unset": { + "roles.".to_owned() + &role_id: 1 + } + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "servers" + })?; + + // remove role from members + // remove role from channels + + ClientboundNotification::ServerRoleDelete { + id: target.id.clone(), + role_id + } + .publish(target.id); + + Ok(()) +} diff --git a/src/routes/servers/server_edit.rs b/src/routes/servers/server_edit.rs index de85a05f..c3753963 100644 --- a/src/routes/servers/server_edit.rs +++ b/src/routes/servers/server_edit.rs @@ -15,6 +15,8 @@ pub struct Data { description: Option, icon: Option, banner: Option, + categories: Option>, + system_messages: Option, remove: Option, } @@ -24,7 +26,7 @@ pub async fn req(user: User, target: Ref, data: Json) -> Result<()> { data.validate() .map_err(|error| Error::FailedValidation { error })?; - if data.name.is_none() && data.icon.is_none() && data.banner.is_none() && data.remove.is_none() + if data.name.is_none() && data.icon.is_none() && data.banner.is_none() && data.remove.is_none() && data.categories.is_none() && data.system_messages.is_none() { return Ok(()); } @@ -95,6 +97,14 @@ pub async fn req(user: User, target: Ref, data: Json) -> Result<()> { remove_banner = true; } + if let Some(categories) = &data.categories { + set.insert("categories", to_document(&categories).map_err(|_| Error::DatabaseError { operation: "to_document", with: "categories" })?); + } + + if let Some(system_messages) = &data.system_messages { + set.insert("system_messages", to_document(&system_messages).map_err(|_| Error::DatabaseError { operation: "to_document", with: "system_messages" })?); + } + let mut operations = doc! {}; if set.len() > 0 { operations.insert("$set", &set); diff --git a/src/util/result.rs b/src/util/result.rs index 218a5369..17d3dd4d 100644 --- a/src/util/result.rs +++ b/src/util/result.rs @@ -41,6 +41,7 @@ pub enum Error { // ? Server related errors. UnknownServer, + InvalidRole, Banned, // ? General errors. @@ -94,6 +95,7 @@ impl<'r> Responder<'r, 'static> for Error { Error::NotInGroup => Status::NotFound, Error::UnknownServer => Status::NotFound, + Error::InvalidRole => Status::NotFound, Error::Banned => Status::Forbidden, Error::FailedValidation { .. } => Status::UnprocessableEntity,