Implement channel permissions.

This commit is contained in:
Paul Makles
2020-04-09 11:38:32 +01:00
parent 7af9a77df4
commit 82f6e6215f
8 changed files with 360 additions and 240 deletions

View File

@@ -8,7 +8,7 @@ pub fn find_member_permissions<C: Into<Option<String>>>(
id: String, id: String,
guild: String, guild: String,
channel: C, channel: C,
) -> u8 { ) -> u32 {
let col = get_collection("guilds"); let col = get_collection("guilds");
match col.find_one( match col.find_one(
@@ -31,10 +31,10 @@ pub fn find_member_permissions<C: Into<Option<String>>>(
Ok(result) => { Ok(result) => {
if let Some(doc) = result { if let Some(doc) = result {
if doc.get_str("owner").unwrap() == id { if doc.get_str("owner").unwrap() == id {
return u8::MAX; return u32::MAX;
} }
doc.get_i32("default_permissions").unwrap() as u8 doc.get_i32("default_permissions").unwrap() as u32
} else { } else {
0 0
} }

View File

@@ -27,6 +27,7 @@ pub fn get_collection(collection: &str) -> Collection {
pub mod channel; pub mod channel;
pub mod guild; pub mod guild;
pub mod message; pub mod message;
pub mod mutual;
pub mod permissions; pub mod permissions;
pub mod user; pub mod user;

35
src/database/mutual.rs Normal file
View File

@@ -0,0 +1,35 @@
use super::get_collection;
use bson::{bson, doc};
use mongodb::options::FindOneOptions;
/*pub struct MutualGuild {
}
pub fn find_mutual_guilds(user_id: String, target_id: String) -> Vec<> {
}*/
pub fn has_mutual_connection(user_id: String, target_id: String) -> bool {
let col = get_collection("guilds");
if let Ok(result) = col.find_one(
doc! {
"$and": [
{ "members": { "$elemMatch": { "id": user_id } } },
{ "members": { "$elemMatch": { "id": target_id } } },
]
},
FindOneOptions::builder()
.projection(doc! { "_id": 1 })
.build(),
) {
if result.is_some() {
true
} else {
false
}
} else {
false
}
}

View File

@@ -1,31 +1,99 @@
bitfield! { use super::mutual::has_mutual_connection;
pub struct MemberPermissions(MSB0 [u8]); use crate::database::user::UserRelationship;
u8; use crate::guards::auth::UserRef;
pub get_access, set_access: 7;
pub get_create_invite, set_create_invite: 6;
pub get_kick_members, set_kick_members: 5;
pub get_ban_members, set_ban_members: 4;
pub get_read_messages, set_read_messages: 3;
pub get_send_messages, set_send_messages: 2;
}
use super::get_collection;
use crate::guards::channel::ChannelRef; use crate::guards::channel::ChannelRef;
use crate::guards::guild::GuildRef; use crate::guards::guild::GuildRef;
use bson::{bson, doc}; use bson::{bson, doc};
use mongodb::options::FindOneOptions; use num_enum::TryFromPrimitive;
#[derive(Debug, PartialEq, Eq, TryFromPrimitive)]
#[repr(u8)]
pub enum Relationship {
FRIEND = 0,
OUTGOING = 1,
INCOMING = 2,
BLOCKED = 3,
BLOCKEDOTHER = 4,
NONE = 5,
SELF = 6,
}
#[derive(Debug, PartialEq, Eq, TryFromPrimitive)]
#[repr(u32)]
pub enum Permission {
ACCESS = 1,
CREATE_INVITE = 2,
KICK_MEMBERS = 4,
BAN_MEMBERS = 8,
READ_MESSAGES = 16,
SEND_MESSAGES = 32,
MANAGE_MESSAGES = 64,
MANAGE_CHANNELS = 128,
MANAGE_SERVER = 256,
MANAGE_ROLES = 512,
}
bitfield! {
pub struct MemberPermissions(MSB0 [u32]);
u8;
pub get_access, set_access: 31;
pub get_create_invite, set_create_invite: 30;
pub get_kick_members, set_kick_members: 29;
pub get_ban_members, set_ban_members: 28;
pub get_read_messages, set_read_messages: 27;
pub get_send_messages, set_send_messages: 26;
pub get_manage_messages, set_manage_messages: 25;
pub get_manage_channels, set_manage_channels: 24;
pub get_manage_server, set_manage_server: 23;
pub get_manage_roles, set_manage_roles: 22;
}
pub fn get_relationship_internal(
user_id: &str,
target_id: &str,
relationships: &Option<Vec<UserRelationship>>,
) -> Relationship {
if user_id == target_id {
return Relationship::SELF;
}
if let Some(arr) = &relationships {
for entry in arr {
if entry.id == target_id {
match entry.status {
0 => return Relationship::FRIEND,
1 => return Relationship::OUTGOING,
2 => return Relationship::INCOMING,
3 => return Relationship::BLOCKED,
4 => return Relationship::BLOCKEDOTHER,
_ => return Relationship::NONE,
}
}
}
}
Relationship::NONE
}
pub fn get_relationship(a: &UserRef, b: &UserRef) -> Relationship {
if a.id == b.id {
return Relationship::SELF;
}
get_relationship_internal(&a.id, &b.id, &a.fetch_relationships())
}
pub struct PermissionCalculator { pub struct PermissionCalculator {
pub user_id: String, pub user: UserRef,
pub channel: Option<ChannelRef>, pub channel: Option<ChannelRef>,
pub guild: Option<GuildRef>, pub guild: Option<GuildRef>,
} }
impl PermissionCalculator { impl PermissionCalculator {
pub fn new(user_id: String) -> PermissionCalculator { pub fn new(user: UserRef) -> PermissionCalculator {
PermissionCalculator { PermissionCalculator {
user_id, user,
channel: None, channel: None,
guild: None, guild: None,
} }
@@ -45,7 +113,7 @@ impl PermissionCalculator {
} }
} }
pub fn calculate(self) -> u8 { pub fn calculate(self) -> u32 {
let guild = if let Some(value) = self.guild { let guild = if let Some(value) = self.guild {
Some(value) Some(value)
} else if let Some(channel) = &self.channel { } else if let Some(channel) = &self.channel {
@@ -70,14 +138,14 @@ impl PermissionCalculator {
doc! { doc! {
"members": { "members": {
"$elemMatch": { "$elemMatch": {
"id": &self.user_id, "id": &self.user.id,
} }
} }
}, },
doc! { } doc! {},
) { ) {
if guild.owner == self.user_id { if guild.owner == self.user.id {
return u8::MAX; return u32::MAX;
} }
permissions = guild.default_permissions; permissions = guild.default_permissions;
@@ -87,29 +155,42 @@ impl PermissionCalculator {
if let Some(channel) = &self.channel { if let Some(channel) = &self.channel {
match channel.channel_type { match channel.channel_type {
0 => { 0 => {
// ? check user is part of the channel
if let Some(arr) = &channel.recipients { if let Some(arr) = &channel.recipients {
let mut other_user = "";
for item in arr { for item in arr {
if item == &self.user_id { if item == &self.user.id {
permissions = 49;
} else {
other_user = item;
}
}
let relationships = self.user.fetch_relationships();
let relationship =
get_relationship_internal(&self.user.id, &other_user, &relationships);
if relationship == Relationship::BLOCKED
|| relationship == Relationship::BLOCKEDOTHER
{
permissions = 1;
} else if has_mutual_connection(self.user.id, other_user.to_string()) {
permissions = 49; permissions = 49;
break;
} }
} }
} }
}, 1 => unreachable!(),
1 => {
unreachable!()
},
2 => { 2 => {
// nothing implemented yet // nothing implemented yet
}, }
_ => {} _ => {}
} }
} }
permissions as u8 permissions as u32
} }
pub fn as_permission(self) -> MemberPermissions<[u8; 1]> { pub fn as_permission(self) -> MemberPermissions<[u32; 1]> {
MemberPermissions([self.calculate()]) MemberPermissions([self.calculate()])
} }
} }

View File

@@ -1,14 +1,14 @@
use bson::{bson, doc, from_bson, Document};
use mongodb::options::FindOneOptions;
use rocket::http::{RawStr, Status}; use rocket::http::{RawStr, Status};
use rocket::request::{self, FromParam, FromRequest, Request}; use rocket::request::{self, FromParam, FromRequest, Request};
use rocket::Outcome; use rocket::Outcome;
use bson::{bson, doc, from_bson, Document};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use mongodb::options::FindOneOptions;
use crate::database; use crate::database;
use database::user::{User, UserRelationship}; use database::user::{User, UserRelationship};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserRef { pub struct UserRef {
pub id: String, pub id: String,
pub username: String, pub username: String,
@@ -29,7 +29,9 @@ impl UserRef {
let user = database::get_collection("users") let user = database::get_collection("users")
.find_one( .find_one(
doc! { "_id": &self.id }, doc! { "_id": &self.id },
FindOneOptions::builder().projection(doc! { "relations": 1 }).build(), FindOneOptions::builder()
.projection(doc! { "relations": 1 })
.build(),
) )
.expect("Failed to fetch user relationships from database.") .expect("Failed to fetch user relationships from database.")
.expect("Missing user document."); .expect("Missing user document.");
@@ -77,13 +79,15 @@ impl<'a, 'r> FromRequest<'a, 'r> for UserRef {
.unwrap(); .unwrap();
if let Some(user) = result { if let Some(user) = result {
Outcome::Success( Outcome::Success(UserRef {
UserRef {
id: user.get_str("_id").unwrap().to_string(), id: user.get_str("_id").unwrap().to_string(),
username: user.get_str("username").unwrap().to_string(), username: user.get_str("username").unwrap().to_string(),
email_verified: user.get_document("email_verification").unwrap().get_bool("verified").unwrap(), email_verified: user
} .get_document("email_verification")
) .unwrap()
.get_bool("verified")
.unwrap(),
})
} else { } else {
Outcome::Failure((Status::Forbidden, AuthError::Invalid)) Outcome::Failure((Status::Forbidden, AuthError::Invalid))
} }
@@ -137,13 +141,15 @@ impl<'r> FromParam<'r> for UserRef {
.unwrap(); .unwrap();
if let Some(user) = result { if let Some(user) = result {
Ok( Ok(UserRef {
UserRef {
id: user.get_str("_id").unwrap().to_string(), id: user.get_str("_id").unwrap().to_string(),
username: user.get_str("username").unwrap().to_string(), username: user.get_str("username").unwrap().to_string(),
email_verified: user.get_document("email_verification").unwrap().get_bool("verified").unwrap(), email_verified: user
} .get_document("email_verification")
) .unwrap()
.get_bool("verified")
.unwrap(),
})
} else { } else {
Err(param) Err(param)
} }

View File

@@ -1,8 +1,7 @@
use super::Response; use super::Response;
use crate::database::{self, channel::Channel, message::Message, user::User, PermissionCalculator}; use crate::database::{self, message::Message, Permission, PermissionCalculator};
use crate::guards::channel::ChannelRef;
use crate::guards::auth::UserRef; use crate::guards::auth::UserRef;
use crate::websocket; use crate::guards::channel::ChannelRef;
use bson::{bson, doc, from_bson, Bson::UtcDatetime}; use bson::{bson, doc, from_bson, Bson::UtcDatetime};
use chrono::prelude::*; use chrono::prelude::*;
@@ -20,9 +19,8 @@ pub enum ChannelType {
} }
macro_rules! with_permissions { macro_rules! with_permissions {
($user: expr, $target: expr) => { ($user: expr, $target: expr) => {{
{ let permissions = PermissionCalculator::new($user.clone())
let permissions = PermissionCalculator::new($user.id.clone())
.channel($target.clone()) .channel($target.clone())
.as_permission(); .as_permission();
@@ -31,8 +29,7 @@ macro_rules! with_permissions {
} }
permissions permissions
} }};
};
} }
/// fetch channel information /// fetch channel information
@@ -41,34 +38,28 @@ pub fn channel(user: UserRef, target: ChannelRef) -> Option<Response> {
with_permissions!(user, target); with_permissions!(user, target);
match target.channel_type { match target.channel_type {
0..=1 => Some(Response::Success( 0..=1 => Some(Response::Success(json!({
json!({
"id": target.id, "id": target.id,
"type": target.channel_type, "type": target.channel_type,
"recipients": target.recipients, "recipients": target.recipients,
}) }))),
)),
2 => { 2 => {
if let Some(info) = target.fetch_data( if let Some(info) = target.fetch_data(doc! {
doc! {
"name": 1, "name": 1,
"description": 1, "description": 1,
} }) {
) { Some(Response::Success(json!({
Some(Response::Success(
json!({
"id": target.id, "id": target.id,
"type": target.channel_type, "type": target.channel_type,
"guild": target.guild, "guild": target.guild,
"name": info.get_str("name").unwrap(), "name": info.get_str("name").unwrap(),
"description": info.get_str("description").unwrap_or(""), "description": info.get_str("description").unwrap_or(""),
}) })))
))
} else { } else {
None None
} }
}, }
_ => unreachable!() _ => unreachable!(),
} }
} }
@@ -77,38 +68,49 @@ pub fn channel(user: UserRef, target: ChannelRef) -> Option<Response> {
/// or close DM conversation /// or close DM conversation
#[delete("/<target>")] #[delete("/<target>")]
pub fn delete(user: UserRef, target: ChannelRef) -> Option<Response> { pub fn delete(user: UserRef, target: ChannelRef) -> Option<Response> {
with_permissions!(user, target); let permissions = with_permissions!(user, target);
if !permissions.get_manage_channels() {
return Some(Response::LackingPermission(Permission::MANAGE_CHANNELS));
}
let col = database::get_collection("channels"); let col = database::get_collection("channels");
Some(match target.channel_type { match target.channel_type {
0 => { 0 => {
col.update_one( if col.update_one(
doc! { "_id": target.id }, doc! { "_id": target.id },
doc! { "$set": { "active": false } }, doc! { "$set": { "active": false } },
None, None,
) ).is_ok() {
.expect("Failed to update channel."); Some(Response::Result(super::Status::Ok))
} else {
Response::Result(super::Status::Ok) Some(Response::InternalServerError(json!({ "error": "Failed to close channel." })))
}
} }
1 => { 1 => {
// ? TODO: group dm // ? TODO: group dm
Response::Result(super::Status::Ok) Some(Response::Result(super::Status::Ok))
} }
2 => { 2 => {
// ? TODO: guild // ? TODO: guild
Response::Result(super::Status::Ok) Some(Response::Result(super::Status::Ok))
}
_ => Some(Response::InternalServerError(
json!({ "error": "Unknown error has occurred." }),
)),
} }
_ => Response::InternalServerError(json!({ "error": "Unknown error has occurred." })),
})
} }
/// fetch channel messages /// fetch channel messages
#[get("/<target>/messages")] #[get("/<target>/messages")]
pub fn messages(user: UserRef, target: ChannelRef) -> Option<Response> { pub fn messages(user: UserRef, target: ChannelRef) -> Option<Response> {
with_permissions!(user, target); let permissions = with_permissions!(user, target);
if !permissions.get_read_messages() {
return Some(Response::LackingPermission(Permission::READ_MESSAGES));
}
let col = database::get_collection("messages"); let col = database::get_collection("messages");
let result = col.find(doc! { "channel": target.id }, None).unwrap(); let result = col.find(doc! { "channel": target.id }, None).unwrap();
@@ -136,8 +138,16 @@ pub struct SendMessage {
/// send a message to a channel /// send a message to a channel
#[post("/<target>/messages", data = "<message>")] #[post("/<target>/messages", data = "<message>")]
pub fn send_message(user: UserRef, target: ChannelRef, message: Json<SendMessage>) -> Option<Response> { pub fn send_message(
with_permissions!(user, target); user: UserRef,
target: ChannelRef,
message: Json<SendMessage>,
) -> Option<Response> {
let permissions = with_permissions!(user, target);
if !permissions.get_send_messages() {
return Some(Response::LackingPermission(Permission::SEND_MESSAGES));
}
let content: String = message.content.chars().take(2000).collect(); let content: String = message.content.chars().take(2000).collect();
let nonce: String = message.nonce.chars().take(32).collect(); let nonce: String = message.nonce.chars().take(32).collect();
@@ -201,7 +211,11 @@ pub fn send_message(user: UserRef, target: ChannelRef, message: Json<SendMessage
/// get a message /// get a message
#[get("/<target>/messages/<message>")] #[get("/<target>/messages/<message>")]
pub fn get_message(user: UserRef, target: ChannelRef, message: Message) -> Option<Response> { pub fn get_message(user: UserRef, target: ChannelRef, message: Message) -> Option<Response> {
with_permissions!(user, target); let permissions = with_permissions!(user, target);
if !permissions.get_read_messages() {
return Some(Response::LackingPermission(Permission::READ_MESSAGES));
}
let prev = let prev =
// ! CHECK IF USER HAS PERMISSION TO VIEW EDITS OF MESSAGES // ! CHECK IF USER HAS PERMISSION TO VIEW EDITS OF MESSAGES
@@ -243,11 +257,13 @@ pub fn edit_message(
) -> Option<Response> { ) -> Option<Response> {
with_permissions!(user, target); with_permissions!(user, target);
Some(if message.author != user.id { if message.author != user.id {
Response::Unauthorized(json!({ "error": "You did not send this message." })) return Some(Response::Unauthorized(
} else { json!({ "error": "You did not send this message." }),
let col = database::get_collection("messages"); ));
}
let col = database::get_collection("messages");
let time = if let Some(edited) = message.edited { let time = if let Some(edited) = message.edited {
edited.0 edited.0
} else { } else {
@@ -286,23 +302,25 @@ pub fn edit_message(
.to_string(), .to_string(),
);*/ );*/
Response::Result(super::Status::Ok) Some(Response::Result(super::Status::Ok))
} }
Err(_) => { Err(_) => Some(Response::InternalServerError(
Response::InternalServerError(json!({ "error": "Failed to update message." })) json!({ "error": "Failed to update message." }),
)),
} }
} }
})
}
/// delete a message /// delete a message
#[delete("/<target>/messages/<message>")] #[delete("/<target>/messages/<message>")]
pub fn delete_message(user: UserRef, target: ChannelRef, message: Message) -> Option<Response> { pub fn delete_message(user: UserRef, target: ChannelRef, message: Message) -> Option<Response> {
with_permissions!(user, target); let permissions = with_permissions!(user, target);
if !permissions.get_manage_messages() {
if message.author != user.id {
return Some(Response::LackingPermission(Permission::MANAGE_MESSAGES));
}
}
Some(if message.author != user.id {
Response::Unauthorized(json!({ "error": "You did not send this message." }))
} else {
let col = database::get_collection("messages"); let col = database::get_collection("messages");
match col.delete_one(doc! { "_id": message.id.clone() }, None) { match col.delete_one(doc! { "_id": message.id.clone() }, None) {
@@ -319,11 +337,10 @@ pub fn delete_message(user: UserRef, target: ChannelRef, message: Message) -> Op
.to_string(), .to_string(),
);*/ );*/
Response::Result(super::Status::Ok) Some(Response::Result(super::Status::Ok))
} }
Err(_) => { Err(_) => Some(Response::InternalServerError(
Response::InternalServerError(json!({ "error": "Failed to delete message." })) json!({ "error": "Failed to delete message." }),
)),
} }
} }
})
}

View File

@@ -3,6 +3,8 @@ pub use rocket::response::Redirect;
use rocket::Rocket; use rocket::Rocket;
use rocket_contrib::json::JsonValue; use rocket_contrib::json::JsonValue;
use crate::database::Permission;
pub mod account; pub mod account;
pub mod channel; pub mod channel;
pub mod guild; pub mod guild;
@@ -21,6 +23,8 @@ pub enum Response {
BadRequest(JsonValue), BadRequest(JsonValue),
#[response(status = 401)] #[response(status = 401)]
Unauthorized(JsonValue), Unauthorized(JsonValue),
#[response(status = 401)]
LackingPermission(Permission),
#[response(status = 404)] #[response(status = 404)]
NotFound(JsonValue), NotFound(JsonValue),
#[response(status = 406)] #[response(status = 406)]
@@ -37,6 +41,22 @@ pub enum Response {
InternalServerError(JsonValue), InternalServerError(JsonValue),
} }
use rocket::http::ContentType;
use rocket::request::Request;
use std::io::Cursor;
impl<'a> rocket::response::Responder<'a> for Permission {
fn respond_to(self, _: &Request) -> rocket::response::Result<'a> {
rocket::response::Response::build()
.header(ContentType::JSON)
.sized_body(Cursor::new(format!(
"{{\"error\":\"Lacking {:?} permission.\"}}",
self
)))
.ok()
}
}
pub fn mount(rocket: Rocket) -> Rocket { pub fn mount(rocket: Rocket) -> Rocket {
rocket rocket
.mount("/api", routes![root::root]) .mount("/api", routes![root::root])

View File

@@ -1,7 +1,8 @@
use super::Response; use super::Response;
use crate::database::{self, channel::Channel, user::UserRelationship}; use crate::database::{self, channel::Channel};
use crate::routes::channel; use crate::database::{get_relationship, get_relationship_internal, Relationship};
use crate::guards::auth::UserRef; use crate::guards::auth::UserRef;
use crate::routes::channel;
use bson::{bson, doc, from_bson}; use bson::{bson, doc, from_bson};
use mongodb::options::FindOptions; use mongodb::options::FindOptions;
@@ -9,61 +10,20 @@ use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ulid::Ulid; use ulid::Ulid;
enum Relationship {
FRIEND = 0,
OUTGOING = 1,
INCOMING = 2,
BLOCKED = 3,
BLOCKEDOTHER = 4,
NONE = 5,
SELF = 6,
}
fn get_relationship_internal(user_id: &str, target_id: &str, relationships: &Option<Vec<UserRelationship>>) -> Relationship {
if user_id == target_id {
return Relationship::SELF;
}
if let Some(arr) = &relationships {
for entry in arr {
if entry.id == target_id {
match entry.status {
0 => return Relationship::FRIEND,
1 => return Relationship::OUTGOING,
2 => return Relationship::INCOMING,
3 => return Relationship::BLOCKED,
4 => return Relationship::BLOCKEDOTHER,
_ => return Relationship::NONE,
}
}
}
}
Relationship::NONE
}
fn get_relationship(a: &UserRef, b: &UserRef) -> Relationship {
if a.id == b.id {
return Relationship::SELF;
}
get_relationship_internal(&a.id, &b.id, &a.fetch_relationships())
}
/// retrieve your user information /// retrieve your user information
#[get("/@me")] #[get("/@me")]
pub fn me(user: UserRef) -> Response { pub fn me(user: UserRef) -> Response {
if let Some(info) = user.fetch_data(doc! { "email": 1 }) { if let Some(info) = user.fetch_data(doc! { "email": 1 }) {
Response::Success( Response::Success(json!({
json!({
"id": user.id, "id": user.id,
"username": user.username, "username": user.username,
"email": info.get_str("email").unwrap(), "email": info.get_str("email").unwrap(),
"verified": user.email_verified, "verified": user.email_verified,
}) }))
)
} else { } else {
Response::InternalServerError(json!({ "error": "Failed to fetch information from database." })) Response::InternalServerError(
json!({ "error": "Failed to fetch information from database." }),
)
} }
} }