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

@@ -1,8 +1,7 @@
use super::Response;
use crate::database::{self, channel::Channel, message::Message, user::User, PermissionCalculator};
use crate::guards::channel::ChannelRef;
use crate::database::{self, message::Message, Permission, PermissionCalculator};
use crate::guards::auth::UserRef;
use crate::websocket;
use crate::guards::channel::ChannelRef;
use bson::{bson, doc, from_bson, Bson::UtcDatetime};
use chrono::prelude::*;
@@ -20,19 +19,17 @@ pub enum ChannelType {
}
macro_rules! with_permissions {
($user: expr, $target: expr) => {
{
let permissions = PermissionCalculator::new($user.id.clone())
.channel($target.clone())
.as_permission();
if !permissions.get_access() {
return None;
}
($user: expr, $target: expr) => {{
let permissions = PermissionCalculator::new($user.clone())
.channel($target.clone())
.as_permission();
permissions
if !permissions.get_access() {
return None;
}
};
permissions
}};
}
/// fetch channel information
@@ -41,34 +38,28 @@ pub fn channel(user: UserRef, target: ChannelRef) -> Option<Response> {
with_permissions!(user, target);
match target.channel_type {
0..=1 => Some(Response::Success(
json!({
"id": target.id,
"type": target.channel_type,
"recipients": target.recipients,
})
)),
0..=1 => Some(Response::Success(json!({
"id": target.id,
"type": target.channel_type,
"recipients": target.recipients,
}))),
2 => {
if let Some(info) = target.fetch_data(
doc! {
"name": 1,
"description": 1,
}
) {
Some(Response::Success(
json!({
"id": target.id,
"type": target.channel_type,
"guild": target.guild,
"name": info.get_str("name").unwrap(),
"description": info.get_str("description").unwrap_or(""),
})
))
if let Some(info) = target.fetch_data(doc! {
"name": 1,
"description": 1,
}) {
Some(Response::Success(json!({
"id": target.id,
"type": target.channel_type,
"guild": target.guild,
"name": info.get_str("name").unwrap(),
"description": info.get_str("description").unwrap_or(""),
})))
} else {
None
}
},
_ => unreachable!()
}
_ => unreachable!(),
}
}
@@ -77,38 +68,49 @@ pub fn channel(user: UserRef, target: ChannelRef) -> Option<Response> {
/// or close DM conversation
#[delete("/<target>")]
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");
Some(match target.channel_type {
match target.channel_type {
0 => {
col.update_one(
if col.update_one(
doc! { "_id": target.id },
doc! { "$set": { "active": false } },
None,
)
.expect("Failed to update channel.");
Response::Result(super::Status::Ok)
).is_ok() {
Some(Response::Result(super::Status::Ok))
} else {
Some(Response::InternalServerError(json!({ "error": "Failed to close channel." })))
}
}
1 => {
// ? TODO: group dm
Response::Result(super::Status::Ok)
Some(Response::Result(super::Status::Ok))
}
2 => {
// ? TODO: guild
Response::Result(super::Status::Ok)
Some(Response::Result(super::Status::Ok))
}
_ => Response::InternalServerError(json!({ "error": "Unknown error has occurred." })),
})
_ => Some(Response::InternalServerError(
json!({ "error": "Unknown error has occurred." }),
)),
}
}
/// fetch channel messages
#[get("/<target>/messages")]
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 result = col.find(doc! { "channel": target.id }, None).unwrap();
@@ -136,8 +138,16 @@ pub struct SendMessage {
/// send a message to a channel
#[post("/<target>/messages", data = "<message>")]
pub fn send_message(user: UserRef, target: ChannelRef, message: Json<SendMessage>) -> Option<Response> {
with_permissions!(user, target);
pub fn send_message(
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 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("/<target>/messages/<message>")]
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 =
// ! CHECK IF USER HAS PERMISSION TO VIEW EDITS OF MESSAGES
@@ -243,87 +257,90 @@ pub fn edit_message(
) -> Option<Response> {
with_permissions!(user, target);
Some(if message.author != user.id {
Response::Unauthorized(json!({ "error": "You did not send this message." }))
if message.author != user.id {
return Some(Response::Unauthorized(
json!({ "error": "You did not send this message." }),
));
}
let col = database::get_collection("messages");
let time = if let Some(edited) = message.edited {
edited.0
} else {
let col = database::get_collection("messages");
Ulid::from_string(&message.id).unwrap().datetime()
};
let time = if let Some(edited) = message.edited {
edited.0
} else {
Ulid::from_string(&message.id).unwrap().datetime()
};
let edited = Utc::now();
match col.update_one(
doc! { "_id": message.id.clone() },
doc! {
"$set": {
"content": edit.content.clone(),
"edited": UtcDatetime(edited.clone())
},
"$push": {
"previous_content": {
"content": message.content,
"time": time,
}
},
let edited = Utc::now();
match col.update_one(
doc! { "_id": message.id.clone() },
doc! {
"$set": {
"content": edit.content.clone(),
"edited": UtcDatetime(edited.clone())
},
None,
) {
Ok(_) => {
/*websocket::queue_message(
get_recipients(&target),
json!({
"type": "message_update",
"data": {
"id": message.id,
"channel": target.id,
"content": edit.content.clone(),
"edited": edited.timestamp()
},
})
.to_string(),
);*/
"$push": {
"previous_content": {
"content": message.content,
"time": time,
}
},
},
None,
) {
Ok(_) => {
/*websocket::queue_message(
get_recipients(&target),
json!({
"type": "message_update",
"data": {
"id": message.id,
"channel": target.id,
"content": edit.content.clone(),
"edited": edited.timestamp()
},
})
.to_string(),
);*/
Response::Result(super::Status::Ok)
}
Err(_) => {
Response::InternalServerError(json!({ "error": "Failed to update message." }))
}
Some(Response::Result(super::Status::Ok))
}
})
Err(_) => Some(Response::InternalServerError(
json!({ "error": "Failed to update message." }),
)),
}
}
/// delete a message
#[delete("/<target>/messages/<message>")]
pub fn delete_message(user: UserRef, target: ChannelRef, message: Message) -> Option<Response> {
with_permissions!(user, target);
let permissions = with_permissions!(user, target);
Some(if message.author != user.id {
Response::Unauthorized(json!({ "error": "You did not send this message." }))
} else {
let col = database::get_collection("messages");
match col.delete_one(doc! { "_id": message.id.clone() }, None) {
Ok(_) => {
/*websocket::queue_message(
get_recipients(&target),
json!({
"type": "message_delete",
"data": {
"id": message.id,
"channel": target.id
},
})
.to_string(),
);*/
Response::Result(super::Status::Ok)
}
Err(_) => {
Response::InternalServerError(json!({ "error": "Failed to delete message." }))
}
if !permissions.get_manage_messages() {
if message.author != user.id {
return Some(Response::LackingPermission(Permission::MANAGE_MESSAGES));
}
})
}
let col = database::get_collection("messages");
match col.delete_one(doc! { "_id": message.id.clone() }, None) {
Ok(_) => {
/*websocket::queue_message(
get_recipients(&target),
json!({
"type": "message_delete",
"data": {
"id": message.id,
"channel": target.id
},
})
.to_string(),
);*/
Some(Response::Result(super::Status::Ok))
}
Err(_) => Some(Response::InternalServerError(
json!({ "error": "Failed to delete message." }),
)),
}
}

View File

@@ -3,6 +3,8 @@ pub use rocket::response::Redirect;
use rocket::Rocket;
use rocket_contrib::json::JsonValue;
use crate::database::Permission;
pub mod account;
pub mod channel;
pub mod guild;
@@ -21,6 +23,8 @@ pub enum Response {
BadRequest(JsonValue),
#[response(status = 401)]
Unauthorized(JsonValue),
#[response(status = 401)]
LackingPermission(Permission),
#[response(status = 404)]
NotFound(JsonValue),
#[response(status = 406)]
@@ -37,6 +41,22 @@ pub enum Response {
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 {
rocket
.mount("/api", routes![root::root])

View File

@@ -1,7 +1,8 @@
use super::Response;
use crate::database::{self, channel::Channel, user::UserRelationship};
use crate::routes::channel;
use crate::database::{self, channel::Channel};
use crate::database::{get_relationship, get_relationship_internal, Relationship};
use crate::guards::auth::UserRef;
use crate::routes::channel;
use bson::{bson, doc, from_bson};
use mongodb::options::FindOptions;
@@ -9,61 +10,20 @@ use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
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
#[get("/@me")]
pub fn me(user: UserRef) -> Response {
if let Some(info) = user.fetch_data(doc! { "email": 1 }) {
Response::Success(
json!({
"id": user.id,
"username": user.username,
"email": info.get_str("email").unwrap(),
"verified": user.email_verified,
})
)
Response::Success(json!({
"id": user.id,
"username": user.username,
"email": info.get_str("email").unwrap(),
"verified": user.email_verified,
}))
} else {
Response::InternalServerError(json!({ "error": "Failed to fetch information from database." }))
Response::InternalServerError(
json!({ "error": "Failed to fetch information from database." }),
)
}
}