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