diff --git a/src/database/permissions/server.rs b/src/database/permissions/server.rs index 611d770c91b62fdcb7168cfbac97848764da124f..d7af889682d7455d2044f8b667f7f030cc2b37be 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 7d4c5677203fa5a74535a4335b88ef0f45c05e1a..4490d9047ff6bcabcd0f8ed14716a9c80ddb0230 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 8eadba743bc58ce650da8fd848bb9a977db08a2c..29f75f9aed47dd8f744f2878c1909dcdc38814cf 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<Route> { routes![ @@ -35,5 +37,7 @@ pub fn routes() -> Vec<Route> { 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 0000000000000000000000000000000000000000..34984a99259c60e0ffc116c4b85a1cf0e40ed91e --- /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("/<target>/permissions/<role>", data = "<data>")] +pub async fn req(user: User, target: Ref, role: String, data: Json<Data>) -> 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 0000000000000000000000000000000000000000..49c8e30f198b5b3bdb2e4f91e9ca05001f230bf8 --- /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("/<target>/permissions/default", data = "<data>", rank = 1)] +pub async fn req(user: User, target: Ref, data: Json<Data>) -> 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 62e8c82ed36c30e69c14736fa726c18737a2506f..5127a7aa4d164a2eec7a3ffb7bdc6a8f146a4d63 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<JsonValue> { .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 5e76b2b6f9b14ea993f1dcc202e3f5623c3f1676..8fce3a141d3958657bb56b53632ce807b93a6238 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 d9d700fc3e9195fcbddf226a63913547ad0b4d34..2f03c3578825a0eb9ae90d190f69b34ccf744172 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<Route> { routes![ server_create::req, @@ -32,6 +35,8 @@ pub fn routes() -> Vec<Route> { 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 0000000000000000000000000000000000000000..6453642a5a0d0a905ca826e7c6f9b28953ae5c45 --- /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("/<target>/roles", data = "<data>")] +pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<JsonValue> { + 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 0000000000000000000000000000000000000000..baab78aaaf4f864f9a34ff7d50053e5d1ca07b4f --- /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("/<target>/roles/<role_id>")] +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 de85a05fafede1b6e23d67c2298d4da16430975b..c3753963b1e9527eb4d83f2fa2dfe4586971cd21 100644 --- a/src/routes/servers/server_edit.rs +++ b/src/routes/servers/server_edit.rs @@ -15,6 +15,8 @@ pub struct Data { description: Option<String>, icon: Option<String>, banner: Option<String>, + categories: Option<Vec<Category>>, + system_messages: Option<SystemMessageChannels>, remove: Option<RemoveServerField>, } @@ -24,7 +26,7 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> 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<Data>) -> 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 218a5369fda0655fc1f37af79637bbc92d6d2df1..17d3dd4d19be315d1accff8a19c5d42a1d495dac 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,