From 3b85dcce1406889321b7ae50699abfe0a1034250 Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Tue, 26 Jan 2021 22:05:32 +0000
Subject: [PATCH] Include group members in payload.

---
 src/database/entities/channel.rs        |  18 ++--
 src/database/entities/user.rs           |  16 ++++
 src/database/permissions/channel.rs     |  23 +++--
 src/database/permissions/mod.rs         |  13 ++-
 src/database/permissions/user.rs        |  55 +++++++-----
 src/notifications/payload.rs            | 112 ++++++++++++++----------
 src/routes/channels/delete_channel.rs   |   3 +-
 src/routes/channels/fetch_channel.rs    |   3 +-
 src/routes/channels/group_add_member.rs |   3 +-
 src/routes/channels/message_delete.rs   |   3 +-
 src/routes/channels/message_edit.rs     |   3 +-
 src/routes/channels/message_fetch.rs    |   3 +-
 src/routes/channels/message_query.rs    |   3 +-
 src/routes/channels/message_send.rs     |   3 +-
 src/routes/users/fetch_user.rs          |  25 +++---
 15 files changed, 178 insertions(+), 108 deletions(-)

diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs
index 359fe37..da8a61e 100644
--- a/src/database/entities/channel.rs
+++ b/src/database/entities/channel.rs
@@ -54,16 +54,18 @@ impl Channel {
 
     pub async fn get(id: &str) -> Result<Channel> {
         let doc = get_collection("channels")
-            .find_one(
-                doc! { "_id": id },
-                None
-            )
+            .find_one(doc! { "_id": id }, None)
             .await
-            .map_err(|_| Error::DatabaseError { operation: "find_one", with: "channel" })?
+            .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" })
+
+        from_document::<Channel>(doc).map_err(|_| Error::DatabaseError {
+            operation: "from_document",
+            with: "channel",
+        })
     }
 
     pub async fn publish(self) -> Result<()> {
diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs
index ba5af0f..778c024 100644
--- a/src/database/entities/user.rs
+++ b/src/database/entities/user.rs
@@ -1,5 +1,7 @@
 use serde::{Deserialize, Serialize};
 
+use crate::{database::permissions::user::UserPermissions, notifications::websocket::is_online};
+
 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
 pub enum RelationshipStatus {
     None,
@@ -32,3 +34,17 @@ pub struct User {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub online: Option<bool>,
 }
+
+impl User {
+    pub fn with(mut self, permissions: UserPermissions<[u32; 1]>) -> User {
+        if !permissions.get_view_all() {
+            self.relations = None;
+        }
+
+        if permissions.get_view_profile() {
+            self.online = Some(is_online(&self.id));
+        }
+
+        self
+    }
+}
diff --git a/src/database/permissions/channel.rs b/src/database/permissions/channel.rs
index e1df309..df69679 100644
--- a/src/database/permissions/channel.rs
+++ b/src/database/permissions/channel.rs
@@ -84,22 +84,31 @@ impl<'a> PermissionCalculator<'a> {
                 }
             }
             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) {
+                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() {
+                if recipients
+                    .iter()
+                    .find(|x| *x == &self.perspective.id)
+                    .is_some()
+                {
                     Ok(ChannelPermission::View + ChannelPermission::SendMessage)
                 } else {
                     Ok(0)
@@ -109,6 +118,6 @@ impl<'a> PermissionCalculator<'a> {
     }
 
     pub async fn for_channel(self) -> Result<ChannelPermissions<[u32; 1]>> {
-        Ok(ChannelPermissions([ self.calculate_channel().await? ]))
+        Ok(ChannelPermissions([self.calculate_channel().await?]))
     }
 }
diff --git a/src/database/permissions/mod.rs b/src/database/permissions/mod.rs
index 86344e4..c29427e 100644
--- a/src/database/permissions/mod.rs
+++ b/src/database/permissions/mod.rs
@@ -10,6 +10,8 @@ pub struct PermissionCalculator<'a> {
 
     user: Option<&'a User>,
     channel: Option<&'a Channel>,
+
+    has_mutual_conncetion: bool,
 }
 
 impl<'a> PermissionCalculator<'a> {
@@ -18,7 +20,9 @@ impl<'a> PermissionCalculator<'a> {
             perspective,
 
             user: None,
-            channel: None
+            channel: None,
+
+            has_mutual_conncetion: false,
         }
     }
 
@@ -35,4 +39,11 @@ impl<'a> PermissionCalculator<'a> {
             ..self
         }
     }
+
+    pub fn with_mutual_connection(self) -> PermissionCalculator<'a> {
+        PermissionCalculator {
+            has_mutual_conncetion: true,
+            ..self
+        }
+    }
 }
diff --git a/src/database/permissions/user.rs b/src/database/permissions/user.rs
index 2b58c3b..1941453 100644
--- a/src/database/permissions/user.rs
+++ b/src/database/permissions/user.rs
@@ -3,9 +3,9 @@ use crate::util::result::{Error, Result};
 
 use super::PermissionCalculator;
 
-use std::ops;
 use mongodb::bson::doc;
 use num_enum::TryFromPrimitive;
+use std::ops;
 
 #[derive(Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone)]
 #[repr(u32)]
@@ -13,7 +13,9 @@ pub enum UserPermission {
     Access = 1,
     ViewProfile = 2,
     SendMessage = 4,
-    Invite = 8
+    Invite = 8,
+
+    ViewAll = 2147483648,
 }
 
 bitfield! {
@@ -23,9 +25,13 @@ bitfield! {
     pub get_view_profile, _: 30;
     pub get_send_message, _: 29;
     pub get_invite, _: 28;
+
+    pub get_view_all, _: 0;
 }
 
 impl_op_ex!(+ |a: &UserPermission, b: &UserPermission| -> u32 { *a as u32 | *b as u32 });
+impl_op_ex!(-|a: &UserPermission, b: &UserPermission| -> u32 { *a as u32 & !(*b as u32) });
+impl_op_ex!(-|a: &u32, b: &UserPermission| -> u32 { *a & !(*b as u32) });
 impl_op_ex_commutative!(+ |a: &u32, b: &UserPermission| -> u32 { *a | *b as u32 });
 
 pub fn get_relationship(a: &User, b: &str) -> RelationshipStatus {
@@ -44,11 +50,13 @@ pub fn get_relationship(a: &User, b: &str) -> RelationshipStatus {
 
 impl<'a> PermissionCalculator<'a> {
     pub async fn calculate_user(self, target: &str) -> Result<u32> {
+        if &self.perspective.id == target {
+            return Ok(u32::MAX);
+        }
+
         let mut permissions: u32 = 0;
         match get_relationship(&self.perspective, &target) {
-            RelationshipStatus::Friend => {
-                return Ok(u32::MAX)
-            }
+            RelationshipStatus::Friend => return Ok(u32::MAX - UserPermission::ViewAll),
             RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => {
                 return Ok(UserPermission::Access as u32)
             }
@@ -62,20 +70,25 @@ impl<'a> PermissionCalculator<'a> {
             _ => {}
         }
 
-        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() {
+        if self.has_mutual_conncetion
+            || 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);
         }
 
@@ -83,11 +96,11 @@ impl<'a> PermissionCalculator<'a> {
     }
 
     pub async fn for_user(self, target: &str) -> Result<UserPermissions<[u32; 1]>> {
-        Ok(UserPermissions([ self.calculate_user(&target).await? ]))
+        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? ]))
+        Ok(UserPermissions([self.calculate_user(&id).await?]))
     }
 }
diff --git a/src/notifications/payload.rs b/src/notifications/payload.rs
index add0f82..c81b958 100644
--- a/src/notifications/payload.rs
+++ b/src/notifications/payload.rs
@@ -1,4 +1,6 @@
-use crate::notifications::events::ClientboundNotification;
+use std::collections::HashSet;
+
+use crate::{database::*, notifications::events::ClientboundNotification};
 use crate::{
     database::{entities::User, get_collection},
     util::result::{Error, Result},
@@ -9,55 +11,16 @@ use mongodb::{
     options::FindOptions,
 };
 
-use super::websocket::is_online;
-
 pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
     let mut users = vec![];
+    let mut user_ids: HashSet<String> = HashSet::new();
 
     if let Some(relationships) = &user.relations {
-        let user_ids: Vec<String> = relationships
-            .iter()
-            .map(|relationship| relationship.id.clone())
-            .collect();
-
-        let mut cursor = get_collection("users")
-            .find(
-                doc! {
-                    "_id": {
-                        "$in": user_ids
-                    }
-                },
-                FindOptions::builder()
-                    .projection(doc! { "_id": 1, "username": 1 })
-                    .build(),
-            )
-            .await
-            .map_err(|_| Error::DatabaseError {
-                operation: "find",
-                with: "users",
-            })?;
-
-        while let Some(result) = cursor.next().await {
-            if let Ok(doc) = result {
-                let mut user: User = from_document(doc).map_err(|_| Error::DatabaseError {
-                    operation: "from_document",
-                    with: "user",
-                })?;
-
-                user.relationship = Some(
-                    relationships
-                        .iter()
-                        .find(|x| user.id == x.id)
-                        .ok_or_else(|| Error::InternalError)?
-                        .status
-                        .clone(),
-                );
-
-                user.online = Some(is_online(&user.id));
-
-                users.push(user);
-            }
-        }
+        user_ids.extend(
+            relationships
+                .iter()
+                .map(|relationship| relationship.id.clone()),
+        );
     }
 
     let mut cursor = get_collection("channels")
@@ -89,10 +52,63 @@ pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
     let mut channels = vec![];
     while let Some(result) = cursor.next().await {
         if let Ok(doc) = result {
-            channels.push(from_document(doc).map_err(|_| Error::DatabaseError {
+            let channel = from_document(doc).map_err(|_| Error::DatabaseError {
                 operation: "from_document",
                 with: "channel",
-            })?);
+            })?;
+
+            if let Channel::Group { recipients, .. } = &channel {
+                user_ids.extend(recipients.iter().cloned());
+            }
+
+            channels.push(channel);
+        }
+    }
+
+    if user_ids.len() > 0 {
+        let mut cursor = get_collection("users")
+            .find(
+                doc! {
+                    "_id": {
+                        "$in": user_ids.into_iter().collect::<Vec<String>>()
+                    }
+                },
+                FindOptions::builder()
+                    .projection(doc! { "_id": 1, "username": 1 })
+                    .build(),
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "find",
+                with: "users",
+            })?;
+
+        while let Some(result) = cursor.next().await {
+            if let Ok(doc) = result {
+                let mut other: User = from_document(doc).map_err(|_| Error::DatabaseError {
+                    operation: "from_document",
+                    with: "user",
+                })?;
+
+                if let Some(relationships) = &user.relations {
+                    other.relationship = Some(
+                        if let Some(relationship) = relationships.iter().find(|x| other.id == x.id)
+                        {
+                            relationship.status.clone()
+                        } else {
+                            RelationshipStatus::None
+                        },
+                    );
+                }
+
+                let permissions = PermissionCalculator::new(&user)
+                    .with_mutual_connection()
+                    .with_user(&other)
+                    .for_user_given()
+                    .await?;
+
+                users.push(other.with(permissions));
+            }
         }
     }
 
diff --git a/src/routes/channels/delete_channel.rs b/src/routes/channels/delete_channel.rs
index 7e01b5d..9fd0881 100644
--- a/src/routes/channels/delete_channel.rs
+++ b/src/routes/channels/delete_channel.rs
@@ -9,7 +9,8 @@ pub async fn req(user: User, target: Ref) -> Result<()> {
 
     let perm = permissions::PermissionCalculator::new(&user)
         .with_channel(&target)
-        .for_channel().await?;
+        .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 8016cfa..d5e6867 100644
--- a/src/routes/channels/fetch_channel.rs
+++ b/src/routes/channels/fetch_channel.rs
@@ -9,7 +9,8 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
 
     let perm = permissions::PermissionCalculator::new(&user)
         .with_channel(&target)
-        .for_channel().await?;
+        .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 0373a5a..4ff36fd 100644
--- a/src/routes/channels/group_add_member.rs
+++ b/src/routes/channels/group_add_member.rs
@@ -13,7 +13,8 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
     let channel = target.fetch_channel().await?;
     let perm = permissions::PermissionCalculator::new(&user)
         .with_channel(&channel)
-        .for_channel().await?;
+        .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 7587584..85bdfed 100644
--- a/src/routes/channels/message_delete.rs
+++ b/src/routes/channels/message_delete.rs
@@ -9,7 +9,8 @@ pub async fn req(user: User, target: Ref, msg: Ref) -> Result<()> {
 
     let perm = permissions::PermissionCalculator::new(&user)
         .with_channel(&channel)
-        .for_channel().await?;
+        .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 e714d41..dc559c2 100644
--- a/src/routes/channels/message_edit.rs
+++ b/src/routes/channels/message_edit.rs
@@ -21,7 +21,8 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<
     let channel = target.fetch_channel().await?;
     let perm = permissions::PermissionCalculator::new(&user)
         .with_channel(&channel)
-        .for_channel().await?;
+        .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 f497747..162e48d 100644
--- a/src/routes/channels/message_fetch.rs
+++ b/src/routes/channels/message_fetch.rs
@@ -9,7 +9,8 @@ pub async fn req(user: User, target: Ref, msg: Ref) -> Result<JsonValue> {
 
     let perm = permissions::PermissionCalculator::new(&user)
         .with_channel(&channel)
-        .for_channel().await?;
+        .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 cff9529..c30967f 100644
--- a/src/routes/channels/message_query.rs
+++ b/src/routes/channels/message_query.rs
@@ -31,7 +31,8 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json
 
     let perm = permissions::PermissionCalculator::new(&user)
         .with_channel(&target)
-        .for_channel().await?;
+        .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 986b490..1a7df55 100644
--- a/src/routes/channels/message_send.rs
+++ b/src/routes/channels/message_send.rs
@@ -26,7 +26,8 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
 
     let perm = permissions::PermissionCalculator::new(&user)
         .with_channel(&target)
-        .for_channel().await?;
+        .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 f9b7a1b..f2731f8 100644
--- a/src/routes/users/fetch_user.rs
+++ b/src/routes/users/fetch_user.rs
@@ -1,5 +1,5 @@
+use crate::database::*;
 use crate::util::result::{Error, Result};
-use crate::{database::*, notifications::websocket::is_online};
 
 use rocket_contrib::json::JsonValue;
 
@@ -7,19 +7,16 @@ use rocket_contrib::json::JsonValue;
 pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
     let mut target = target.fetch_user().await?;
 
-    if user.id != target.id {
-        // Check whether we are allowed to fetch this user.
-        let perm = permissions::PermissionCalculator::new(&user)
-            .with_user(&target)
-            .for_user_given().await?;
-        if !perm.get_access() {
-            Err(Error::LabelMe)?
-        }
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_user(&target)
+        .for_user_given()
+        .await?;
 
-        // Only return user relationships if the target is the caller.
-        target.relations = None;
+    if !perm.get_access() {
+        Err(Error::LabelMe)?
+    }
 
-        // Add relevant relationship
+    if user.id != target.id {
         if let Some(relationships) = &user.relations {
             target.relationship = relationships
                 .iter()
@@ -33,7 +30,5 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
         target.relationship = Some(RelationshipStatus::User);
     }
 
-    target.online = Some(is_online(&target.id));
-
-    Ok(json!(target))
+    Ok(json!(target.with(perm)))
 }
-- 
GitLab