Skip to content
Snippets Groups Projects
Verified Commit 82f6e621 authored by insert's avatar insert
Browse files

Implement channel permissions.

parent 7af9a77d
Branches
Tags
No related merge requests found
......@@ -8,7 +8,7 @@ pub fn find_member_permissions<C: Into<Option<String>>>(
id: String,
guild: String,
channel: C,
) -> u8 {
) -> u32 {
let col = get_collection("guilds");
match col.find_one(
......@@ -31,10 +31,10 @@ pub fn find_member_permissions<C: Into<Option<String>>>(
Ok(result) => {
if let Some(doc) = result {
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 {
0
}
......
......@@ -27,6 +27,7 @@ pub fn get_collection(collection: &str) -> Collection {
pub mod channel;
pub mod guild;
pub mod message;
pub mod mutual;
pub mod permissions;
pub mod user;
......
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
}
}
use super::mutual::has_mutual_connection;
use crate::database::user::UserRelationship;
use crate::guards::auth::UserRef;
use crate::guards::channel::ChannelRef;
use crate::guards::guild::GuildRef;
use bson::{bson, doc};
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 [u8]);
pub struct MemberPermissions(MSB0 [u32]);
u8;
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;
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;
}
use super::get_collection;
use crate::guards::channel::ChannelRef;
use crate::guards::guild::GuildRef;
pub fn get_relationship_internal(
user_id: &str,
target_id: &str,
relationships: &Option<Vec<UserRelationship>>,
) -> Relationship {
if user_id == target_id {
return Relationship::SELF;
}
use bson::{bson, doc};
use mongodb::options::FindOneOptions;
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 user_id: String,
pub user: UserRef,
pub channel: Option<ChannelRef>,
pub guild: Option<GuildRef>,
}
impl PermissionCalculator {
pub fn new(user_id: String) -> PermissionCalculator {
pub fn new(user: UserRef) -> PermissionCalculator {
PermissionCalculator {
user_id,
user,
channel: 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 {
Some(value)
} else if let Some(channel) = &self.channel {
......@@ -70,14 +138,14 @@ impl PermissionCalculator {
doc! {
"members": {
"$elemMatch": {
"id": &self.user_id,
"id": &self.user.id,
}
}
},
doc! { }
doc! {},
) {
if guild.owner == self.user_id {
return u8::MAX;
if guild.owner == self.user.id {
return u32::MAX;
}
permissions = guild.default_permissions;
......@@ -87,29 +155,42 @@ impl PermissionCalculator {
if let Some(channel) = &self.channel {
match channel.channel_type {
0 => {
// ? check user is part of the channel
if let Some(arr) = &channel.recipients {
let mut other_user = "";
for item in arr {
if item == &self.user_id {
if item == &self.user.id {
permissions = 49;
break;
} 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;
}
}
},
1 => {
unreachable!()
},
}
1 => unreachable!(),
2 => {
// nothing implemented yet
},
}
_ => {}
}
}
permissions as u8
permissions as u32
}
pub fn as_permission(self) -> MemberPermissions<[u8; 1]> {
MemberPermissions([ self.calculate() ])
pub fn as_permission(self) -> MemberPermissions<[u32; 1]> {
MemberPermissions([self.calculate()])
}
}
use bson::{bson, doc, from_bson, Document};
use mongodb::options::FindOneOptions;
use rocket::http::{RawStr, Status};
use rocket::request::{self, FromParam, FromRequest, Request};
use rocket::Outcome;
use bson::{bson, doc, from_bson, Document};
use serde::{Deserialize, Serialize};
use mongodb::options::FindOneOptions;
use crate::database;
use database::user::{User, UserRelationship};
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserRef {
pub id: String,
pub username: String,
......@@ -29,11 +29,13 @@ impl UserRef {
let user = database::get_collection("users")
.find_one(
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("Missing user document.");
if let Ok(arr) = user.get_array("relations") {
let mut relationships = vec![];
for item in arr {
......@@ -77,13 +79,15 @@ impl<'a, 'r> FromRequest<'a, 'r> for UserRef {
.unwrap();
if let Some(user) = result {
Outcome::Success(
UserRef {
id: user.get_str("_id").unwrap().to_string(),
username: user.get_str("username").unwrap().to_string(),
email_verified: user.get_document("email_verification").unwrap().get_bool("verified").unwrap(),
}
)
Outcome::Success(UserRef {
id: user.get_str("_id").unwrap().to_string(),
username: user.get_str("username").unwrap().to_string(),
email_verified: user
.get_document("email_verification")
.unwrap()
.get_bool("verified")
.unwrap(),
})
} else {
Outcome::Failure((Status::Forbidden, AuthError::Invalid))
}
......@@ -124,26 +128,28 @@ impl<'r> FromParam<'r> for UserRef {
fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
let col = database::get_db().collection("users");
let result = database::get_collection("users")
.find_one(
doc! { "_id": param.to_string() },
FindOneOptions::builder()
.projection(doc! {
"_id": 1,
"username": 1,
"email_verification.verified": 1,
})
.build(),
)
.unwrap();
.find_one(
doc! { "_id": param.to_string() },
FindOneOptions::builder()
.projection(doc! {
"_id": 1,
"username": 1,
"email_verification.verified": 1,
})
.build(),
)
.unwrap();
if let Some(user) = result {
Ok(
UserRef {
id: user.get_str("_id").unwrap().to_string(),
username: user.get_str("username").unwrap().to_string(),
email_verified: user.get_document("email_verification").unwrap().get_bool("verified").unwrap(),
}
)
Ok(UserRef {
id: user.get_str("_id").unwrap().to_string(),
username: user.get_str("username").unwrap().to_string(),
email_verified: user
.get_document("email_verification")
.unwrap()
.get_bool("verified")
.unwrap(),
})
} else {
Err(param)
}
......
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." }))
} else {
let col = database::get_collection("messages");
if message.author != user.id {
return Some(Response::Unauthorized(
json!({ "error": "You did not send this message." }),
));
}
let time = if let Some(edited) = message.edited {
edited.0
} else {
Ulid::from_string(&message.id).unwrap().datetime()
};
let col = database::get_collection("messages");
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(),
);*/
Response::Result(super::Status::Ok)
}
Err(_) => {
Response::InternalServerError(json!({ "error": "Failed to update message." }))
}
"$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(),
);*/
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." }),
)),
}
}
......@@ -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])
......
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." }),
)
}
}
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment