From eb382fa1ecbbf1fde06fcd7c95b45d56ddd5eba5 Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Tue, 29 Dec 2020 13:02:04 +0000
Subject: [PATCH] New perm concept, reference, and adding routes.

---
 Cargo.lock                       |  7 +++++
 Cargo.toml                       |  1 +
 src/database/entities/channel.rs | 21 ++++++++++++-
 src/database/entities/guild.rs   |  6 ++--
 src/database/entities/message.rs |  8 ++---
 src/database/entities/user.rs    | 35 ++-------------------
 src/database/guards/reference.rs | 52 ++++++++++++++++++++++++++++++++
 src/database/mod.rs              |  1 +
 src/database/permissions/mod.rs  | 32 ++++++++++++++++++++
 src/main.rs                      |  4 +++
 src/routes/users/fetch_dms.rs    | 42 ++++++++++++++++++++++++++
 src/routes/users/fetch_user.rs   | 25 ++++++++++++---
 src/routes/users/mod.rs          |  4 ++-
 src/util/result.rs               |  4 +++
 14 files changed, 196 insertions(+), 46 deletions(-)
 create mode 100644 src/database/permissions/mod.rs
 create mode 100644 src/routes/users/fetch_dms.rs

diff --git a/Cargo.lock b/Cargo.lock
index e046456..fc83ca8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1203,6 +1203,12 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f7280c75fb2e2fc47080ec80ccc481376923acb04501957fc38f935c3de5088"
 
+[[package]]
+name = "impl_ops"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f97a5f38dd3ccfbe7aa80f4a0c00930f21b922c74195be0201c51028f22dcf"
+
 [[package]]
 name = "indexmap"
 version = "1.6.1"
@@ -2346,6 +2352,7 @@ dependencies = [
  "env_logger",
  "futures",
  "hive_pubsub",
+ "impl_ops",
  "lazy_static",
  "lettre",
  "log",
diff --git a/Cargo.toml b/Cargo.toml
index 7402835..6c14426 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@ edition = "2018"
 [dependencies]
 futures = "0.3.8"
 many-to-many = "0.1.2"
+impl_ops = "0.1.1"
 ctrlc = { version = "3.0", features = ["termination"] }
 async-tungstenite = { version = "0.10.0", features = ["async-std-runtime"] }
 rauth = { git = "https://gitlab.insrt.uk/insert/rauth" }
diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs
index 413b7f1..1f66d51 100644
--- a/src/database/entities/channel.rs
+++ b/src/database/entities/channel.rs
@@ -1,6 +1,6 @@
 use serde::{Deserialize, Serialize};
 
-#[derive(Serialize, Deserialize, Debug, Clone)]
+/*#[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct LastMessage {
     id: String,
     user_id: String,
@@ -28,4 +28,23 @@ pub struct Channel {
     pub name: Option<String>,
     // GUILD + GDM: channel description
     pub description: Option<String>,
+}*/
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(tag = "type")]
+pub enum Channel {
+    DirectMessage {
+        #[serde(rename = "_id")]
+        id: String,
+        active: bool,
+        recipients: Vec<String>,
+    },
+    Group {
+        #[serde(rename = "_id")]
+        id: String,
+        name: String,
+        owner: String,
+        description: String,
+        recipients: Vec<String>,
+    }
 }
diff --git a/src/database/entities/guild.rs b/src/database/entities/guild.rs
index ee5a54e..4b69b43 100644
--- a/src/database/entities/guild.rs
+++ b/src/database/entities/guild.rs
@@ -1,6 +1,6 @@
-use serde::{Deserialize, Serialize};
+// use serde::{Deserialize, Serialize};
 
-#[derive(Serialize, Deserialize, Debug, Clone)]
+/*#[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct MemberCompositeKey {
     pub guild: String,
     pub user: String,
@@ -40,4 +40,4 @@ pub struct Guild {
     pub bans: Vec<Ban>,
 
     pub default_permissions: u32,
-}
+}*/
diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs
index 9f8c233..59f51cb 100644
--- a/src/database/entities/message.rs
+++ b/src/database/entities/message.rs
@@ -1,7 +1,7 @@
-use mongodb::bson::DateTime;
-use serde::{Deserialize, Serialize};
+// use mongodb::bson::DateTime;
+// use serde::{Deserialize, Serialize};
 
-#[derive(Serialize, Deserialize, Debug)]
+/*#[derive(Serialize, Deserialize, Debug)]
 pub struct PreviousEntry {
     pub content: String,
     pub time: DateTime,
@@ -19,4 +19,4 @@ pub struct Message {
     pub edited: Option<DateTime>,
 
     pub previous_content: Vec<PreviousEntry>,
-}
+}*/
diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs
index d3cdf87..722c1bf 100644
--- a/src/database/entities/user.rs
+++ b/src/database/entities/user.rs
@@ -5,17 +5,18 @@ use serde::{Deserialize, Serialize};
 use crate::database::get_collection;
 use rocket::request::{self, FromRequest, Outcome, Request};
 
-#[derive(Serialize, Deserialize, Debug, Clone)]
+#[derive(Serialize, Deserialize, Debug)]
 pub struct Relationship {
     pub id: String,
     pub status: u8,
 }
 
-#[derive(Serialize, Deserialize, Debug, Clone)]
+#[derive(Serialize, Deserialize, Debug)]
 pub struct User {
     #[serde(rename = "_id")]
     pub id: String,
     pub username: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub relations: Option<Vec<Relationship>>,
 }
 
@@ -43,35 +44,5 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
         } else {
             Outcome::Failure((Status::InternalServerError, rauth::util::Error::DatabaseError))
         }
-
-        /*Outcome::Success(
-            User {
-                id: "gaming".to_string(),
-                username: None,
-                relations: None
-            }
-        )*/
-
-        /*match (
-            request.managed_state::<Auth>(),
-            header_user_id,
-            header_session_token,
-        ) {
-            (Some(auth), Some(user_id), Some(session_token)) => {
-                let session = Session {
-                    id: None,
-                    user_id,
-                    session_token,
-                };
-
-                if let Ok(session) = auth.verify_session(session).await {
-                    Outcome::Success(session)
-                } else {
-                    Outcome::Failure((Status::Forbidden, Error::InvalidSession))
-                }
-            }
-            (None, _, _) => Outcome::Failure((Status::InternalServerError, Error::InternalError)),
-            (_, _, _) => Outcome::Failure((Status::Forbidden, Error::MissingHeaders)),
-        }*/
     }
 }
diff --git a/src/database/guards/reference.rs b/src/database/guards/reference.rs
index e69de29..1f9a36e 100644
--- a/src/database/guards/reference.rs
+++ b/src/database/guards/reference.rs
@@ -0,0 +1,52 @@
+use mongodb::bson::{doc, from_bson, Bson};
+use crate::util::result::{Error, Result};
+use serde::{Deserialize, Serialize};
+use crate::database::get_collection;
+use crate::database::entities::*;
+use rocket::request::FromParam;
+use rocket::http::RawStr;
+use validator::Validate;
+
+#[derive(Validate, Serialize, Deserialize)]
+pub struct Ref {
+    #[validate(length(min = 26, max = 26))]
+    id: String,
+}
+
+impl Ref {
+    pub fn from(id: String) -> Result<Ref> {
+        Ok(Ref { id })
+    }
+
+    pub async fn fetch_user(self) -> Result<User> {
+        let doc = get_collection("users")
+            .find_one(
+                doc! {
+                    "_id": self.id
+                },
+                None
+            )
+            .await
+            .map_err(|_| Error::DatabaseError { operation: "find_one", with: "user" })?
+            .ok_or_else(|| Error::UnknownUser)?;
+        
+        Ok(
+            from_bson(Bson::Document(doc))
+                .map_err(|_| Error::DatabaseError { operation: "from_bson", with: "user" })?
+        )
+    }
+}
+
+impl<'r> FromParam<'r> for Ref {
+    type Error = &'r RawStr;
+
+    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
+        if let Ok(result) = Ref::from(param.to_string()) {
+            if result.validate().is_ok() {
+                return Ok(result);
+            }
+        }
+
+        Err(param)
+    }
+}
diff --git a/src/database/mod.rs b/src/database/mod.rs
index b287c2c..48d81d9 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -26,6 +26,7 @@ pub fn get_collection(collection: &str) -> Collection {
     get_db().collection(collection)
 }
 
+pub mod permissions;
 pub mod migrations;
 pub mod entities;
 pub mod guards;
diff --git a/src/database/permissions/mod.rs b/src/database/permissions/mod.rs
new file mode 100644
index 0000000..3b0f438
--- /dev/null
+++ b/src/database/permissions/mod.rs
@@ -0,0 +1,32 @@
+use crate::database::entities::User;
+use num_enum::TryFromPrimitive;
+use std::ops;
+
+#[derive(Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone)]
+#[repr(u32)]
+pub enum UserPermission {
+    Access = 1,
+    SendMessage = 2,
+    Invite = 4
+}
+
+bitfield! {
+    pub struct UserPermissions(MSB0 [u32]);
+    u32;
+    pub get_access, _: 31;
+    pub get_send_message, _: 30;
+    pub get_invite, _: 29;
+}
+
+impl_op_ex!(+ |a: &UserPermission, b: &UserPermission| -> u32 { *a + *b });
+impl_op_ex!(+ |a: &u32, b: &UserPermission| -> u32 { *a + *b });
+
+pub async fn temp_calc_perm(_user: &User, _target: &User) -> 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
+
+    UserPermissions([UserPermission::Access + UserPermission::SendMessage + UserPermission::Invite])
+}
diff --git a/src/main.rs b/src/main.rs
index 7d9e043..46ed96f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,10 @@ extern crate rocket;
 extern crate rocket_contrib;
 #[macro_use]
 extern crate lazy_static;
+#[macro_use]
+extern crate impl_ops;
+#[macro_use]
+extern crate bitfield;
 extern crate ctrlc;
 
 pub mod notifications;
diff --git a/src/routes/users/fetch_dms.rs b/src/routes/users/fetch_dms.rs
new file mode 100644
index 0000000..beeb5e7
--- /dev/null
+++ b/src/routes/users/fetch_dms.rs
@@ -0,0 +1,42 @@
+use crate::database::entities::{Channel, User};
+use mongodb::bson::{Bson, doc, from_bson};
+use crate::util::result::{Error, Result};
+use crate::database::get_collection;
+use rocket_contrib::json::JsonValue;
+use futures::StreamExt;
+
+#[get("/dms")]
+pub async fn req(user: User) -> Result<JsonValue> {
+    let mut cursor = get_collection("channels")
+        .find(
+            doc! {
+                "$or": [
+                    {
+                        "type": "DirectMessage",
+                        "active": true
+                    },
+                    {
+                        "type": "Group"
+                    }
+                ],
+                "recipients": user.id
+            },
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError { operation: "find", with: "channels" })?;
+    
+    let mut channels: Vec<Channel> = vec![];
+    while let Some(result) = cursor.next().await {
+        if let Ok(doc) = result {
+            channels.push(
+                from_bson(Bson::Document(doc))
+                    .map_err(|_| Error::DatabaseError { operation: "from_bson", with: "channel" })?
+            );
+        }
+    }
+
+    Ok(json!(
+        channels
+    ))
+}
diff --git a/src/routes/users/fetch_user.rs b/src/routes/users/fetch_user.rs
index 009bace..ec7a23f 100644
--- a/src/routes/users/fetch_user.rs
+++ b/src/routes/users/fetch_user.rs
@@ -1,7 +1,22 @@
-use crate::util::result::Result;
+use crate::database::guards::reference::Ref;
+use crate::util::result::{Error, Result};
+use crate::database::entities::User;
+use rocket_contrib::json::JsonValue;
 
-#[get("/<id>")]
-pub async fn req(id: String) -> Result<String> {
-    println!("{}", id);
-    Ok("LETS FUCKING GOOOO".to_string())
+#[get("/<target>")]
+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 = crate::database::permissions::temp_calc_perm(&user, &target).await;
+        if !perm.get_access() {
+            Err(Error::LabelMe)?
+        }
+        
+        // Only return user relationships if the target is the caller.
+        target.relations = None;
+    }
+
+    Ok(json!(target))
 }
diff --git a/src/routes/users/mod.rs b/src/routes/users/mod.rs
index 4251314..7b92a3f 100644
--- a/src/routes/users/mod.rs
+++ b/src/routes/users/mod.rs
@@ -1,9 +1,11 @@
 use rocket::Route;
 
 mod fetch_user;
+mod fetch_dms;
 
 pub fn routes() -> Vec<Route> {
     routes! [
-        fetch_user::req
+        fetch_user::req,
+        fetch_dms::req
     ]
 }
diff --git a/src/util/result.rs b/src/util/result.rs
index 4866756..173ad27 100644
--- a/src/util/result.rs
+++ b/src/util/result.rs
@@ -23,6 +23,9 @@ pub enum Error {
     #[snafu(display("Username has already been taken."))]
     #[serde(rename = "username_taken")]
     UsernameTaken,
+    #[snafu(display("This user does not exist!"))]
+    #[serde(rename = "unknown_user")]
+    UnknownUser,
 
     // ? General errors.
     #[snafu(display("Failed to validate fields."))]
@@ -74,6 +77,7 @@ impl<'r> Responder<'r, 'static> for Error {
             Error::DatabaseError { .. } => Status::InternalServerError,
             Error::FailedValidation { .. } => Status::UnprocessableEntity,
             Error::LabelMe => Status::InternalServerError,
+            Error::UnknownUser => Status::NotFound,
             Error::UsernameTaken => Status::Conflict,
         };
 
-- 
GitLab