From f42480886beded6e00ac969980dcee4bf827dc0a Mon Sep 17 00:00:00 2001 From: Paul Makles <paulmakles@gmail.com> Date: Tue, 26 Jan 2021 21:12:23 +0000 Subject: [PATCH] Re-structure Permissions; add perm to view users from mutual groups. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/database/entities/channel.rs | 16 ++++- src/database/entities/message.rs | 2 +- src/database/permissions/channel.rs | 78 +++++++++++++++++---- src/database/permissions/mod.rs | 34 +++++++++ src/database/permissions/user.rs | 93 ++++++++++++++++--------- src/routes/channels/delete_channel.rs | 4 +- src/routes/channels/fetch_channel.rs | 4 +- src/routes/channels/group_add_member.rs | 5 +- src/routes/channels/message_delete.rs | 4 +- src/routes/channels/message_edit.rs | 5 +- src/routes/channels/message_fetch.rs | 4 +- src/routes/channels/message_query.rs | 4 +- src/routes/channels/message_send.rs | 4 +- src/routes/users/fetch_user.rs | 4 +- src/util/result.rs | 3 + 17 files changed, 210 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9a49dd..d00b6a7 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 bd44ca9..08a2990 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 3ee927a..359fe37 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 8f8fbe7..8594f40 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 7c216b0..e1df309 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 0aea919..86344e4 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 951afe5..2b58c3b 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 0b8e30f..7e01b5d 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 4d95960..8016cfa 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 cbb9756..0373a5a 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 67b88dc..7587584 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 5f170cd..e714d41 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 7b4ff93..f497747 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 0bb1284..cff9529 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 71a0f93..986b490 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 8c4b91c..f9b7a1b 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 7aa3e03..4a54803 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, -- GitLab