diff --git a/Cargo.lock b/Cargo.lock index e9a49ddf9ae0fcca27743e660521e5c9057cd551..d00b6a733371b98e5257110af5aace21fbb9a2c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2351,7 +2351,7 @@ dependencies = [ [[package]] name = "revolt" -version = "0.3.1" +version = "0.3.2" dependencies = [ "async-std", "async-tungstenite", diff --git a/Cargo.toml b/Cargo.toml index bd44ca9c69a4bc8993794428f80cc6990779865f..08a29907898cf30bff2b338f0ca7a8cc990edbf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt" -version = "0.3.1" +version = "0.3.2" authors = ["Paul Makles <paulmakles@gmail.com>"] edition = "2018" diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs index 3ee927a122ba196b9573c3dfef407234d5c37429..359fe379027f7fa13e2cd5e51d4e4ba12d6821b5 100644 --- a/src/database/entities/channel.rs +++ b/src/database/entities/channel.rs @@ -1,7 +1,7 @@ use crate::database::*; use crate::notifications::events::ClientboundNotification; use crate::util::result::{Error, Result}; -use mongodb::bson::{doc, to_document}; +use mongodb::bson::{doc, from_document, to_document}; use rocket_contrib::json::JsonValue; use serde::{Deserialize, Serialize}; @@ -52,6 +52,20 @@ impl Channel { } } + pub async fn get(id: &str) -> Result<Channel> { + let doc = get_collection("channels") + .find_one( + doc! { "_id": id }, + None + ) + .await + .map_err(|_| Error::DatabaseError { operation: "find_one", with: "channel" })? + .ok_or_else(|| Error::UnknownChannel)?; + + from_document::<Channel>(doc) + .map_err(|_| Error::DatabaseError { operation: "from_document", with: "channel" }) + } + pub async fn publish(self) -> Result<()> { get_collection("channels") .insert_one( diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs index 8f8fbe75dfa5c939ccb6de4d62bf3d9dc7de07b5..8594f40052d37b8d1742dc1ea3a99240be18a09d 100644 --- a/src/database/entities/message.rs +++ b/src/database/entities/message.rs @@ -44,7 +44,7 @@ impl Message { with: "message", })?; - // ! temp code + // ! FIXME: temp code let channels = get_collection("channels"); match channel { Channel::DirectMessage { id, .. } => { diff --git a/src/database/permissions/channel.rs b/src/database/permissions/channel.rs index 7c216b0fb1fa22b84a3dad9d8994734c34cc2bde..e1df309d8c4f837ada3da33abcf081897806a6a3 100644 --- a/src/database/permissions/channel.rs +++ b/src/database/permissions/channel.rs @@ -1,4 +1,8 @@ use crate::database::*; +use crate::util::result::Result; + +use super::PermissionCalculator; + use num_enum::TryFromPrimitive; use std::ops; @@ -21,40 +25,90 @@ bitfield! { impl_op_ex!(+ |a: &ChannelPermission, b: &ChannelPermission| -> u32 { *a as u32 | *b as u32 }); impl_op_ex_commutative!(+ |a: &u32, b: &ChannelPermission| -> u32 { *a | *b as u32 }); -pub async fn calculate(user: &User, target: &Channel) -> ChannelPermissions<[u32; 1]> { +/*pub async fn calculate(user: &User, target: &Channel) -> Result<u32> { match target { Channel::SavedMessages { user: owner, .. } => { if &user.id == owner { - ChannelPermissions([ChannelPermission::View + Ok(ChannelPermission::View + ChannelPermission::SendMessage - + ChannelPermission::ManageMessages]) + + ChannelPermission::ManageMessages) } else { - ChannelPermissions([0]) + Ok(0) } } Channel::DirectMessage { recipients, .. } => { if recipients.iter().find(|x| *x == &user.id).is_some() { if let Some(recipient) = recipients.iter().find(|x| *x != &user.id) { - let perms = super::user::calculate(&user, recipient).await; + let perms = super::user::get(&user, recipient).await?; if perms.get_send_message() { - return ChannelPermissions([ - ChannelPermission::View + ChannelPermission::SendMessage - ]); + return Ok(ChannelPermission::View + ChannelPermission::SendMessage); } - return ChannelPermissions([ChannelPermission::View as u32]); + return Ok(ChannelPermission::View as u32); } } - ChannelPermissions([0]) + Ok(0) } Channel::Group { recipients, .. } => { if recipients.iter().find(|x| *x == &user.id).is_some() { - ChannelPermissions([ChannelPermission::View + ChannelPermission::SendMessage]) + Ok(ChannelPermission::View + ChannelPermission::SendMessage) } else { - ChannelPermissions([0]) + Ok(0) } } } } + +pub async fn get(user: &User, target: &Channel) -> Result<ChannelPermissions<[u32; 1]>> { + Ok(ChannelPermissions([calculate(&user, &target).await?])) +}*/ + +impl<'a> PermissionCalculator<'a> { + pub async fn calculate_channel(self) -> Result<u32> { + let channel = if let Some(channel) = self.channel { + channel + } else { + unreachable!() + }; + + match channel { + Channel::SavedMessages { user: owner, .. } => { + if &self.perspective.id == owner { + Ok(ChannelPermission::View + + ChannelPermission::SendMessage + + ChannelPermission::ManageMessages) + } else { + Ok(0) + } + } + Channel::DirectMessage { recipients, .. } => { + if recipients.iter().find(|x| *x == &self.perspective.id).is_some() { + if let Some(recipient) = recipients.iter().find(|x| *x != &self.perspective.id) { + let perms = self.for_user(recipient).await?; + + if perms.get_send_message() { + return Ok(ChannelPermission::View + ChannelPermission::SendMessage); + } + + return Ok(ChannelPermission::View as u32); + } + } + + Ok(0) + } + Channel::Group { recipients, .. } => { + if recipients.iter().find(|x| *x == &self.perspective.id).is_some() { + Ok(ChannelPermission::View + ChannelPermission::SendMessage) + } else { + Ok(0) + } + } + } + } + + pub async fn for_channel(self) -> Result<ChannelPermissions<[u32; 1]>> { + Ok(ChannelPermissions([ self.calculate_channel().await? ])) + } +} diff --git a/src/database/permissions/mod.rs b/src/database/permissions/mod.rs index 0aea9198e39f5bae4b519d96801b938badd37eba..86344e47f4f94a0c9cd6bdc20fde92b77219cc73 100644 --- a/src/database/permissions/mod.rs +++ b/src/database/permissions/mod.rs @@ -1,4 +1,38 @@ +pub use crate::database::*; + pub mod channel; pub mod user; pub use user::get_relationship; + +pub struct PermissionCalculator<'a> { + perspective: &'a User, + + user: Option<&'a User>, + channel: Option<&'a Channel>, +} + +impl<'a> PermissionCalculator<'a> { + pub fn new(perspective: &'a User) -> PermissionCalculator { + PermissionCalculator { + perspective, + + user: None, + channel: None + } + } + + pub fn with_user(self, user: &'a User) -> PermissionCalculator { + PermissionCalculator { + user: Some(&user), + ..self + } + } + + pub fn with_channel(self, channel: &'a Channel) -> PermissionCalculator { + PermissionCalculator { + channel: Some(&channel), + ..self + } + } +} diff --git a/src/database/permissions/user.rs b/src/database/permissions/user.rs index 951afe5598ac0fe41afbc30512928030d4f5f224..2b58c3be558f50892c2bc62ab7f808f63535822a 100644 --- a/src/database/permissions/user.rs +++ b/src/database/permissions/user.rs @@ -1,52 +1,33 @@ use crate::database::*; -use num_enum::TryFromPrimitive; +use crate::util::result::{Error, Result}; + +use super::PermissionCalculator; + use std::ops; +use mongodb::bson::doc; +use num_enum::TryFromPrimitive; #[derive(Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone)] #[repr(u32)] pub enum UserPermission { Access = 1, - SendMessage = 2, - Invite = 4, + ViewProfile = 2, + SendMessage = 4, + Invite = 8 } bitfield! { pub struct UserPermissions(MSB0 [u32]); u32; pub get_access, _: 31; - pub get_send_message, _: 30; - pub get_invite, _: 29; + pub get_view_profile, _: 30; + pub get_send_message, _: 29; + pub get_invite, _: 28; } impl_op_ex!(+ |a: &UserPermission, b: &UserPermission| -> u32 { *a as u32 | *b as u32 }); impl_op_ex_commutative!(+ |a: &u32, b: &UserPermission| -> u32 { *a | *b as u32 }); -pub async fn calculate(user: &User, target: &str) -> UserPermissions<[u32; 1]> { - // if friends; Access + Message + Invite - // if mutually know each other: - // and has DMs from users enabled -> Access + Message - // otherwise -> Access - // otherwise; None - - let mut permissions: u32 = 0; - match get_relationship(&user, &target) { - RelationshipStatus::Friend => { - return UserPermissions([UserPermission::Access - + UserPermission::SendMessage - + UserPermission::Invite]) - } - RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => { - return UserPermissions([UserPermission::Access as u32]) - } - RelationshipStatus::Incoming | RelationshipStatus::Outgoing => { - permissions = UserPermission::Access as u32; - } - _ => {} - } - - UserPermissions([permissions]) -} - pub fn get_relationship(a: &User, b: &str) -> RelationshipStatus { if a.id == b { return RelationshipStatus::Friend; @@ -60,3 +41,53 @@ pub fn get_relationship(a: &User, b: &str) -> RelationshipStatus { RelationshipStatus::None } + +impl<'a> PermissionCalculator<'a> { + pub async fn calculate_user(self, target: &str) -> Result<u32> { + let mut permissions: u32 = 0; + match get_relationship(&self.perspective, &target) { + RelationshipStatus::Friend => { + return Ok(u32::MAX) + } + RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => { + return Ok(UserPermission::Access as u32) + } + RelationshipStatus::Incoming | RelationshipStatus::Outgoing => { + permissions = UserPermission::Access as u32; + // ! INFO: if we add boolean switch for permission to + // ! message people who have mutual, we need to get + // ! rid of this return statement. + return Ok(permissions); + } + _ => {} + } + + if get_collection("channels") + .find_one( + doc! { + "type": "Group", + "$and": { + "recipients": &self.perspective.id, + "recipients": target + } + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { operation: "find", with: "channels" })? + .is_some() { + return Ok(UserPermission::Access as u32); + } + + Ok(permissions) + } + + pub async fn for_user(self, target: &str) -> Result<UserPermissions<[u32; 1]>> { + Ok(UserPermissions([ self.calculate_user(&target).await? ])) + } + + pub async fn for_user_given(self) -> Result<UserPermissions<[u32; 1]>> { + let id = &self.user.unwrap().id; + Ok(UserPermissions([ self.calculate_user(&id).await? ])) + } +} diff --git a/src/routes/channels/delete_channel.rs b/src/routes/channels/delete_channel.rs index 0b8e30fd91a0e800ded9859aa66de93d5dd5ec28..7e01b5dc61a2c785446bbe62cb46e9e64a479451 100644 --- a/src/routes/channels/delete_channel.rs +++ b/src/routes/channels/delete_channel.rs @@ -7,7 +7,9 @@ use mongodb::bson::doc; pub async fn req(user: User, target: Ref) -> Result<()> { let target = target.fetch_channel().await?; - let perm = permissions::channel::calculate(&user, &target).await; + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&target) + .for_channel().await?; if !perm.get_view() { Err(Error::LabelMe)? } diff --git a/src/routes/channels/fetch_channel.rs b/src/routes/channels/fetch_channel.rs index 4d9596018998d9dd2b0f9f2d772f6b382f6bd1cb..8016cfad964a36ccf82b68a2f108dbc472fcc27d 100644 --- a/src/routes/channels/fetch_channel.rs +++ b/src/routes/channels/fetch_channel.rs @@ -7,7 +7,9 @@ use rocket_contrib::json::JsonValue; pub async fn req(user: User, target: Ref) -> Result<JsonValue> { let target = target.fetch_channel().await?; - let perm = permissions::channel::calculate(&user, &target).await; + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&target) + .for_channel().await?; if !perm.get_view() { Err(Error::LabelMe)? } diff --git a/src/routes/channels/group_add_member.rs b/src/routes/channels/group_add_member.rs index cbb97564a4294d0838bd7b814b46d62cd4fc74e0..0373a5a59b9556aa2b88dd95cb5fe7c063ba0605 100644 --- a/src/routes/channels/group_add_member.rs +++ b/src/routes/channels/group_add_member.rs @@ -11,8 +11,9 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { } let channel = target.fetch_channel().await?; - - let perm = permissions::channel::calculate(&user, &channel).await; + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&channel) + .for_channel().await?; if !perm.get_view() { Err(Error::LabelMe)? } diff --git a/src/routes/channels/message_delete.rs b/src/routes/channels/message_delete.rs index 67b88dc82da8cd4d5dbaa780f2c386b6abf04606..75875849d1a553bc80f0cd47868639050f2055ec 100644 --- a/src/routes/channels/message_delete.rs +++ b/src/routes/channels/message_delete.rs @@ -7,7 +7,9 @@ use mongodb::bson::doc; pub async fn req(user: User, target: Ref, msg: Ref) -> Result<()> { let channel = target.fetch_channel().await?; - let perm = permissions::channel::calculate(&user, &channel).await; + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&channel) + .for_channel().await?; if !perm.get_view() { Err(Error::LabelMe)? } diff --git a/src/routes/channels/message_edit.rs b/src/routes/channels/message_edit.rs index 5f170cd5d471f262f7b0987b6a1e72d3580d0633..e714d4122afb02a3fb5b4da6f6426b442bed3658 100644 --- a/src/routes/channels/message_edit.rs +++ b/src/routes/channels/message_edit.rs @@ -19,8 +19,9 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result< .map_err(|error| Error::FailedValidation { error })?; let channel = target.fetch_channel().await?; - - let perm = permissions::channel::calculate(&user, &channel).await; + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&channel) + .for_channel().await?; if !perm.get_view() { Err(Error::LabelMe)? } diff --git a/src/routes/channels/message_fetch.rs b/src/routes/channels/message_fetch.rs index 7b4ff9337b0fb1b44da143216244c07c0254270a..f497747c9077ca2a5735e505fd22adef09a81230 100644 --- a/src/routes/channels/message_fetch.rs +++ b/src/routes/channels/message_fetch.rs @@ -7,7 +7,9 @@ use rocket_contrib::json::JsonValue; pub async fn req(user: User, target: Ref, msg: Ref) -> Result<JsonValue> { let channel = target.fetch_channel().await?; - let perm = permissions::channel::calculate(&user, &channel).await; + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&channel) + .for_channel().await?; if !perm.get_view() { Err(Error::LabelMe)? } diff --git a/src/routes/channels/message_query.rs b/src/routes/channels/message_query.rs index 0bb128489000c8241f0113b652f4ea60736908d6..cff9529c5337fd25747cebe80658d524611f5bfb 100644 --- a/src/routes/channels/message_query.rs +++ b/src/routes/channels/message_query.rs @@ -29,7 +29,9 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json let target = target.fetch_channel().await?; - let perm = permissions::channel::calculate(&user, &target).await; + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&target) + .for_channel().await?; if !perm.get_view() { Err(Error::LabelMe)? } diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs index 71a0f938864790f53fe7a4de99ea7d5d1b605166..986b4908f1a80d96b8f415175f213e0640116647 100644 --- a/src/routes/channels/message_send.rs +++ b/src/routes/channels/message_send.rs @@ -24,7 +24,9 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal let target = target.fetch_channel().await?; - let perm = permissions::channel::calculate(&user, &target).await; + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&target) + .for_channel().await?; if !perm.get_send_message() { Err(Error::LabelMe)? } diff --git a/src/routes/users/fetch_user.rs b/src/routes/users/fetch_user.rs index 8c4b91c6a88fc7909b575d49af7c6d648cbbd967..f9b7a1bf416c4b3b60cda20c0ae2761ee1d11a56 100644 --- a/src/routes/users/fetch_user.rs +++ b/src/routes/users/fetch_user.rs @@ -9,7 +9,9 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { if user.id != target.id { // Check whether we are allowed to fetch this user. - let perm = permissions::user::calculate(&user, &target.id).await; + let perm = permissions::PermissionCalculator::new(&user) + .with_user(&target) + .for_user_given().await?; if !perm.get_access() { Err(Error::LabelMe)? } diff --git a/src/util/result.rs b/src/util/result.rs index 7aa3e033b05772c75f532ab0ad260fa500751be1..4a548032b98e18d88791d1c62c2053b36754cdc6 100644 --- a/src/util/result.rs +++ b/src/util/result.rs @@ -34,6 +34,8 @@ pub enum Error { NotFriends, // ? Channel related errors. + #[snafu(display("This channel does not exist!"))] + UnknownChannel, #[snafu(display("Cannot edit someone else's message."))] CannotEditMessage, #[snafu(display("Cannot remove yourself from a group, use delete channel instead."))] @@ -81,6 +83,7 @@ impl<'r> Responder<'r, 'static> for Error { Error::BlockedByOther => Status::Forbidden, Error::NotFriends => Status::Forbidden, + Error::UnknownChannel => Status::NotFound, Error::CannotEditMessage => Status::Forbidden, Error::CannotRemoveYourself => Status::BadRequest, Error::GroupTooLarge { .. } => Status::Forbidden,