forked from jmug/stoatchat
chore(monorepo): delta, january, quark
This commit is contained in:
93
crates/quark/src/permissions/defn/mod.rs
Normal file
93
crates/quark/src/permissions/defn/mod.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
mod permission;
|
||||
mod user;
|
||||
|
||||
use bson::Bson;
|
||||
pub use permission::*;
|
||||
pub use user::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Holds a permission value to manipulate.
|
||||
#[derive(Debug)]
|
||||
pub struct PermissionValue(u64);
|
||||
|
||||
/// Representation of a single permission override
|
||||
#[derive(Deserialize, JsonSchema, Debug, Clone, Copy)]
|
||||
pub struct Override {
|
||||
/// Allow bit flags
|
||||
allow: u64,
|
||||
/// Disallow bit flags
|
||||
deny: u64,
|
||||
}
|
||||
|
||||
/// Representation of a single permission override
|
||||
/// as it appears on models and in the database
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Copy, Default)]
|
||||
pub struct OverrideField {
|
||||
/// Allow bit flags
|
||||
a: i64,
|
||||
/// Disallow bit flags
|
||||
d: i64,
|
||||
}
|
||||
|
||||
impl Override {
|
||||
/// Into allows
|
||||
pub fn allows(&self) -> u64 {
|
||||
self.allow
|
||||
}
|
||||
|
||||
/// Into denies
|
||||
pub fn denies(&self) -> u64 {
|
||||
self.deny
|
||||
}
|
||||
}
|
||||
|
||||
impl PermissionValue {
|
||||
/// Apply a given override to this value
|
||||
pub fn apply(&mut self, v: Override) {
|
||||
self.0 |= v.allow;
|
||||
self.0 &= !v.deny;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Override> for OverrideField {
|
||||
fn from(v: Override) -> Self {
|
||||
Self {
|
||||
a: v.allow as i64,
|
||||
d: v.deny as i64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OverrideField> for Override {
|
||||
fn from(v: OverrideField) -> Self {
|
||||
Self {
|
||||
allow: v.a as u64,
|
||||
deny: v.d as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OverrideField> for Bson {
|
||||
fn from(v: OverrideField) -> Self {
|
||||
Self::Document(bson::to_document(&v).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for PermissionValue {
|
||||
fn from(v: i64) -> Self {
|
||||
Self(v as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for PermissionValue {
|
||||
fn from(v: u64) -> Self {
|
||||
Self(v as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PermissionValue> for u64 {
|
||||
fn from(v: PermissionValue) -> Self {
|
||||
v.0
|
||||
}
|
||||
}
|
||||
156
crates/quark/src/permissions/defn/permission.rs
Normal file
156
crates/quark/src/permissions/defn/permission.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use num_enum::TryFromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops;
|
||||
|
||||
/// Permission value on Revolt
|
||||
///
|
||||
/// This should be restricted to the lower 52 bits to prevent any
|
||||
/// potential issues with Javascript. Also leave empty spaces for
|
||||
/// future permission flags to be added.
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone, JsonSchema,
|
||||
)]
|
||||
#[repr(u64)]
|
||||
pub enum Permission {
|
||||
// * Generic permissions
|
||||
/// Manage the channel or channels on the server
|
||||
ManageChannel = 1 << 0,
|
||||
/// Manage the server
|
||||
ManageServer = 1 << 1,
|
||||
/// Manage permissions on servers or channels
|
||||
ManagePermissions = 1 << 2,
|
||||
/// Manage roles on server
|
||||
ManageRole = 1 << 3,
|
||||
|
||||
// % 2 bits reserved
|
||||
|
||||
// * Member permissions
|
||||
/// Kick other members below their ranking
|
||||
KickMembers = 1 << 6,
|
||||
/// Ban other members below their ranking
|
||||
BanMembers = 1 << 7,
|
||||
/// Timeout other members below their ranking
|
||||
TimeoutMembers = 1 << 8,
|
||||
/// Assign roles to members below their ranking
|
||||
AssignRoles = 1 << 9,
|
||||
/// Change own nickname
|
||||
ChangeNickname = 1 << 10,
|
||||
/// Change or remove other's nicknames below their ranking
|
||||
ManageNicknames = 1 << 11,
|
||||
/// Change own avatar
|
||||
ChangeAvatar = 1 << 12,
|
||||
/// Remove other's avatars below their ranking
|
||||
RemoveAvatars = 1 << 13,
|
||||
|
||||
// % 7 bits reserved
|
||||
|
||||
// * Channel permissions
|
||||
/// View a channel
|
||||
ViewChannel = 1 << 20,
|
||||
/// Read a channel's past message history
|
||||
ReadMessageHistory = 1 << 21,
|
||||
/// Send a message in a channel
|
||||
SendMessage = 1 << 22,
|
||||
/// Delete messages in a channel
|
||||
ManageMessages = 1 << 23,
|
||||
/// Manage webhook entries on a channel
|
||||
ManageWebhooks = 1 << 24,
|
||||
/// Create invites to this channel
|
||||
InviteOthers = 1 << 25,
|
||||
/// Send embedded content in this channel
|
||||
SendEmbeds = 1 << 26,
|
||||
/// Send attachments and media in this channel
|
||||
UploadFiles = 1 << 27,
|
||||
/// Masquerade messages using custom nickname and avatar
|
||||
Masquerade = 1 << 28,
|
||||
|
||||
// % 1 bits reserved
|
||||
|
||||
// * Voice permissions
|
||||
/// Connect to a voice channel
|
||||
Connect = 1 << 30,
|
||||
/// Speak in a voice call
|
||||
Speak = 1 << 31,
|
||||
/// Share video in a voice call
|
||||
Video = 1 << 32,
|
||||
/// Mute other members with lower ranking in a voice call
|
||||
MuteMembers = 1 << 33,
|
||||
/// Deafen other members with lower ranking in a voice call
|
||||
DeafenMembers = 1 << 34,
|
||||
/// Move members between voice channels
|
||||
MoveMembers = 1 << 35,
|
||||
|
||||
// * Misc. permissions
|
||||
// % Bits 36 to 52: free area
|
||||
// % Bits 53 to 64: do not use
|
||||
|
||||
// * Grant all permissions
|
||||
/// Safely grant all permissions
|
||||
GrantAllSafe = 0x000F_FFFF_FFFF_FFFF,
|
||||
|
||||
/// Grant all permissions
|
||||
GrantAll = u64::MAX,
|
||||
}
|
||||
|
||||
impl_op_ex!(+ |a: &Permission, b: &Permission| -> u64 { *a as u64 | *b as u64 });
|
||||
impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 });
|
||||
|
||||
lazy_static! {
|
||||
pub static ref DEFAULT_PERMISSION_VIEW_ONLY: u64 =
|
||||
Permission::ViewChannel + Permission::ReadMessageHistory;
|
||||
pub static ref DEFAULT_PERMISSION: u64 = *DEFAULT_PERMISSION_VIEW_ONLY
|
||||
+ Permission::SendMessage
|
||||
+ Permission::InviteOthers
|
||||
+ Permission::SendEmbeds
|
||||
+ Permission::UploadFiles
|
||||
+ Permission::Connect
|
||||
+ Permission::Speak;
|
||||
pub static ref DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64;
|
||||
pub static ref DEFAULT_PERMISSION_DIRECT_MESSAGE: u64 =
|
||||
*DEFAULT_PERMISSION + Permission::ManageChannel;
|
||||
pub static ref DEFAULT_PERMISSION_SERVER: u64 =
|
||||
*DEFAULT_PERMISSION + Permission::ChangeNickname + Permission::ChangeAvatar;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Default)]
|
||||
pub struct Permissions(MSB0 [u64]);
|
||||
u64;
|
||||
|
||||
// * Server permissions
|
||||
pub can_manage_channel, _: 63;
|
||||
pub can_manage_server, _: 62;
|
||||
pub can_manage_permissions, _: 61;
|
||||
pub can_manage_roles, _: 60;
|
||||
|
||||
// * Member permissions
|
||||
pub can_kick_members, _: 57;
|
||||
pub can_ban_members, _: 56;
|
||||
pub can_timeout_members, _: 55;
|
||||
pub can_assign_roles, _: 54;
|
||||
pub can_change_nickname, _: 53;
|
||||
pub can_manage_nicknames, _: 52;
|
||||
pub can_change_avatar, _: 51;
|
||||
pub can_remove_avatars, _: 50;
|
||||
|
||||
// * Channel permissions
|
||||
pub can_view_channel, _: 42;
|
||||
pub can_read_message_history, _: 41;
|
||||
pub can_send_message, _: 40;
|
||||
pub can_manage_messages, _: 39;
|
||||
pub can_manage_webhooks, _: 38;
|
||||
pub can_invite_others, _: 37;
|
||||
pub can_send_embeds, _: 36;
|
||||
pub can_upload_files, _: 35;
|
||||
pub can_masquerade, _: 34;
|
||||
|
||||
// * Voice permissions
|
||||
pub can_connect, _: 32;
|
||||
pub can_speak, _: 31;
|
||||
pub can_share_video, _: 30;
|
||||
pub can_mute_members, _: 29;
|
||||
pub can_deafen_members, _: 28;
|
||||
pub can_move_members, _: 27;
|
||||
}
|
||||
|
||||
pub type Perms = Permissions<[u64; 1]>;
|
||||
29
crates/quark/src/permissions/defn/user.rs
Normal file
29
crates/quark/src/permissions/defn/user.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use num_enum::TryFromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops;
|
||||
|
||||
/// User permission definitions
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, PartialEq, Eq, TryFromPrimitive, Copy, Clone, JsonSchema,
|
||||
)]
|
||||
#[repr(u32)]
|
||||
pub enum UserPermission {
|
||||
Access = 1 << 0,
|
||||
ViewProfile = 1 << 1,
|
||||
SendMessage = 1 << 2,
|
||||
Invite = 1 << 3,
|
||||
}
|
||||
|
||||
impl_op_ex!(+ |a: &UserPermission, b: &UserPermission| -> u32 { *a as u32 | *b as u32 });
|
||||
impl_op_ex_commutative!(+ |a: &u32, b: &UserPermission| -> u32 { *a | *b as u32 });
|
||||
|
||||
bitfield! {
|
||||
pub struct UserPermissions(MSB0 [u32]);
|
||||
u32;
|
||||
pub get_access, _: 31;
|
||||
pub get_view_profile, _: 30;
|
||||
pub get_send_message, _: 29;
|
||||
pub get_invite, _: 28;
|
||||
}
|
||||
|
||||
pub type UserPerms = UserPermissions<[u32; 1]>;
|
||||
2
crates/quark/src/permissions/impl/mod.rs
Normal file
2
crates/quark/src/permissions/impl/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod permission;
|
||||
pub mod user;
|
||||
219
crates/quark/src/permissions/impl/permission.rs
Normal file
219
crates/quark/src/permissions/impl/permission.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
models::Channel, permissions::PermissionCalculator, Override, Permission, PermissionValue,
|
||||
Permissions, Perms, Result, DEFAULT_PERMISSION_DIRECT_MESSAGE,
|
||||
DEFAULT_PERMISSION_SAVED_MESSAGES, DEFAULT_PERMISSION_VIEW_ONLY,
|
||||
};
|
||||
|
||||
use super::super::Permission::GrantAllSafe;
|
||||
|
||||
impl PermissionCalculator<'_> {
|
||||
/// Calculate the permissions from our perspective to the given server or channel
|
||||
///
|
||||
/// Refer to https://developers.revolt.chat/stack/delta/permissions#flow-chart for more information
|
||||
pub async fn calc(&mut self, db: &crate::Database) -> Result<Perms> {
|
||||
if self.perspective.privileged {
|
||||
return Ok(Permissions([GrantAllSafe as u64]));
|
||||
}
|
||||
|
||||
let value = if self.channel.has() {
|
||||
calculate_channel_permission(self, db).await?
|
||||
} else if self.server.has() {
|
||||
calculate_server_permission(self, db).await?
|
||||
} else {
|
||||
panic!("Expected `PermissionCalculator.(user|server) to exist.");
|
||||
}
|
||||
.into();
|
||||
|
||||
self.cached_permission = Some(value);
|
||||
Ok(Permissions([value]))
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal helper function for calculating server permission
|
||||
async fn calculate_server_permission(
|
||||
data: &mut PermissionCalculator<'_>,
|
||||
db: &crate::Database,
|
||||
) -> Result<PermissionValue> {
|
||||
let server = data.server.get().unwrap();
|
||||
|
||||
// 1. Check if owner.
|
||||
if data.perspective.id == server.owner {
|
||||
return Ok((Permission::GrantAllSafe as u64).into());
|
||||
}
|
||||
|
||||
// 2. Fetch member.
|
||||
if !data.member.has() {
|
||||
data.member
|
||||
.set(db.fetch_member(&server.id, &data.perspective.id).await?);
|
||||
}
|
||||
|
||||
let member = data.member.get().expect("Member should be present by now.");
|
||||
|
||||
// 3. Apply allows from default_permissions.
|
||||
let mut permissions: PermissionValue = server.default_permissions.into();
|
||||
|
||||
// 4. Resolve each role in order.
|
||||
let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() {
|
||||
roles.iter().collect()
|
||||
} else {
|
||||
HashSet::new()
|
||||
};
|
||||
|
||||
if !member_roles.is_empty() {
|
||||
let mut roles = server
|
||||
.roles
|
||||
.iter()
|
||||
.filter(|(id, _)| member_roles.contains(id))
|
||||
.map(|(_, role)| {
|
||||
let v: Override = role.permissions.into();
|
||||
(role.rank, v)
|
||||
})
|
||||
.collect::<Vec<(i64, Override)>>();
|
||||
|
||||
roles.sort_by(|a, b| b.0.cmp(&a.0));
|
||||
|
||||
// 5. Apply allows and denies from roles.
|
||||
for (_, v) in roles {
|
||||
permissions.apply(v);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(permissions)
|
||||
}
|
||||
|
||||
/// Internal helper function for calculating channel permission
|
||||
async fn calculate_channel_permission(
|
||||
data: &mut PermissionCalculator<'_>,
|
||||
db: &crate::Database,
|
||||
) -> Result<PermissionValue> {
|
||||
// Pre-calculate server permissions if applicable.
|
||||
// We do this to satisfy the borrow checker.
|
||||
let server_id = match data.channel.get().unwrap() {
|
||||
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => Some(server),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut permissions = if let Some(server) = server_id {
|
||||
if !data.server.has() {
|
||||
data.server.set(db.fetch_server(server).await?);
|
||||
}
|
||||
|
||||
calculate_server_permission(data, db).await?
|
||||
} else {
|
||||
0_u64.into()
|
||||
};
|
||||
|
||||
// Borrow the channel now and continue as normal.
|
||||
let channel = data.channel.get().unwrap();
|
||||
|
||||
// 1. Check channel type.
|
||||
let value: PermissionValue = match channel {
|
||||
Channel::SavedMessages { .. } => (*DEFAULT_PERMISSION_SAVED_MESSAGES).into(),
|
||||
Channel::DirectMessage { recipients, .. } => {
|
||||
// 2. Fetch user.
|
||||
let other_user = recipients
|
||||
.iter()
|
||||
.find(|x| x != &&data.perspective.id)
|
||||
.unwrap();
|
||||
|
||||
let user = db.fetch_user(other_user).await?;
|
||||
data.user.set(user);
|
||||
|
||||
// 3. Calculate user permissions.
|
||||
let perms = data.calc_user(db).await;
|
||||
|
||||
// 4. Check if the user can send messages.
|
||||
if perms.get_send_message() {
|
||||
(*DEFAULT_PERMISSION_DIRECT_MESSAGE).into()
|
||||
} else {
|
||||
(*DEFAULT_PERMISSION_VIEW_ONLY).into()
|
||||
}
|
||||
}
|
||||
Channel::Group {
|
||||
owner,
|
||||
permissions,
|
||||
recipients,
|
||||
..
|
||||
} => {
|
||||
// 2. Check if user is owner.
|
||||
if &data.perspective.id == owner {
|
||||
(Permission::GrantAllSafe as u64).into()
|
||||
} else {
|
||||
// 3. Check that we are actually in the group.
|
||||
if recipients.contains(&data.perspective.id) {
|
||||
// 4. Pull out group permissions.
|
||||
permissions
|
||||
.map(|x| x as u64)
|
||||
.unwrap_or(*DEFAULT_PERMISSION_DIRECT_MESSAGE)
|
||||
.into()
|
||||
} else {
|
||||
0_u64.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
Channel::TextChannel {
|
||||
default_permissions,
|
||||
role_permissions,
|
||||
..
|
||||
}
|
||||
| Channel::VoiceChannel {
|
||||
default_permissions,
|
||||
role_permissions,
|
||||
..
|
||||
} => {
|
||||
// 2. If server owner, just grant all permissions.
|
||||
//
|
||||
// Member may be present and we need to check or
|
||||
// we can just grant all if member is not present.
|
||||
//
|
||||
// In the case member isn't present, the previous
|
||||
// step did not fetch member as we are the server owner.
|
||||
if let Some(member) = data.member.get() {
|
||||
let server = data.server.get().unwrap();
|
||||
if server.owner == member.id.user {
|
||||
return Ok((Permission::GrantAllSafe as u64).into());
|
||||
}
|
||||
|
||||
// 3. Apply default allows and denies for channel.
|
||||
if let Some(default) = default_permissions {
|
||||
permissions.apply((*default).into());
|
||||
}
|
||||
|
||||
// 4. Resolve each role in order.
|
||||
let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() {
|
||||
roles.iter().collect()
|
||||
} else {
|
||||
HashSet::new()
|
||||
};
|
||||
|
||||
if !member_roles.is_empty() {
|
||||
let mut roles = role_permissions
|
||||
.iter()
|
||||
.filter(|(id, _)| member_roles.contains(id))
|
||||
.filter_map(|(id, permission)| {
|
||||
server.roles.get(id).map(|role| {
|
||||
let v: Override = (*permission).into();
|
||||
(role.rank, v)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<(i64, Override)>>();
|
||||
|
||||
roles.sort_by(|a, b| b.0.cmp(&a.0));
|
||||
|
||||
// 5. Apply allows and denies from roles.
|
||||
for (_, v) in roles {
|
||||
permissions.apply(v);
|
||||
}
|
||||
}
|
||||
|
||||
permissions
|
||||
} else {
|
||||
(Permission::GrantAllSafe as u64).into()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
100
crates/quark/src/permissions/impl/user.rs
Normal file
100
crates/quark/src/permissions/impl/user.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::{
|
||||
models::{user::RelationshipStatus, User},
|
||||
permissions::PermissionCalculator,
|
||||
UserPermission, UserPermissions, UserPerms,
|
||||
};
|
||||
|
||||
impl PermissionCalculator<'_> {
|
||||
/// Calculate the permissions from our perspective to the given user
|
||||
///
|
||||
/// How the permission is calculated:
|
||||
/// 1. Are we the target?
|
||||
/// - If so: return maximum permissions
|
||||
/// 2. Do we have a relationship with the target?
|
||||
/// - If we are friends: return maximum permissions
|
||||
/// - If either user blocked each other: return only `Access`
|
||||
/// - If incoming / outgoing request: add `Access` to the list
|
||||
/// 3. Determine whether there is a mutual connection:
|
||||
/// 1. Check if the "mutual connection" flag is set.
|
||||
/// 2. Check if we share any servers with the target.
|
||||
/// 3. Check if we share any DMs or groups with the target.
|
||||
/// 4. Do we have a mutual connection with the target?
|
||||
/// - If so: return `Access` + `ViewProfile`
|
||||
/// 5. Return no permissions
|
||||
pub async fn calc_user(&mut self, db: &crate::Database) -> UserPerms {
|
||||
if self.user.has() {
|
||||
let v = calculate_permission(self, db).await;
|
||||
self.cached_user_permission = Some(v);
|
||||
UserPermissions([v])
|
||||
} else {
|
||||
panic!("Expected `PermissionCalculator.user` to exist.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the relationship between two users
|
||||
pub fn get_relationship(a: &User, b: &str) -> RelationshipStatus {
|
||||
if a.id == b {
|
||||
return RelationshipStatus::User;
|
||||
}
|
||||
|
||||
if let Some(relations) = &a.relations {
|
||||
if let Some(relationship) = relations.iter().find(|x| x.id == b) {
|
||||
return relationship.status.clone();
|
||||
}
|
||||
}
|
||||
|
||||
RelationshipStatus::None
|
||||
}
|
||||
|
||||
/// Internal helper function for calculating permission
|
||||
async fn calculate_permission(data: &mut PermissionCalculator<'_>, db: &crate::Database) -> u32 {
|
||||
let user = data.user.get().unwrap();
|
||||
|
||||
if data.perspective.id == user.id {
|
||||
return u32::MAX;
|
||||
}
|
||||
|
||||
let relationship = data.flag_known_relationship.cloned().unwrap_or_else(|| {
|
||||
user.relationship
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| get_relationship(data.perspective, &user.id))
|
||||
});
|
||||
|
||||
let mut permissions: u32 = 0;
|
||||
match relationship {
|
||||
RelationshipStatus::Friend => return u32::MAX,
|
||||
RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => {
|
||||
return UserPermission::Access as u32
|
||||
}
|
||||
RelationshipStatus::Incoming | RelationshipStatus::Outgoing => {
|
||||
permissions = UserPermission::Access as u32;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// ! FIXME: add boolean switch for permission for users to globally message a user
|
||||
// maybe an enum?
|
||||
// PrivacyLevel { Private, Friends, Mutual, Public, Global }
|
||||
|
||||
// ! FIXME: add boolean switch for permission for users to mutually DM a user
|
||||
|
||||
if data.flag_has_mutual_connection
|
||||
|| data
|
||||
.perspective
|
||||
.has_mutual_connection(db, &user.id)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
{
|
||||
permissions = UserPermission::Access + UserPermission::ViewProfile;
|
||||
|
||||
if user.bot.is_some() || data.perspective.bot.is_some() {
|
||||
permissions += UserPermission::SendMessage as u32;
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
permissions
|
||||
}
|
||||
171
crates/quark/src/permissions/mod.rs
Normal file
171
crates/quark/src/permissions/mod.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
use crate::{
|
||||
models::{user::RelationshipStatus, Channel, Member, Server, User},
|
||||
util::value::Value,
|
||||
Database, Error, Override, Permission, Result,
|
||||
};
|
||||
|
||||
pub mod defn;
|
||||
pub mod r#impl;
|
||||
|
||||
pub use r#impl::user::get_relationship;
|
||||
|
||||
/// Permissions calculator
|
||||
#[derive(Clone)]
|
||||
pub struct PermissionCalculator<'a> {
|
||||
perspective: &'a User,
|
||||
|
||||
pub user: Value<'a, User>,
|
||||
pub channel: Value<'a, Channel>,
|
||||
pub server: Value<'a, Server>,
|
||||
pub member: Value<'a, Member>,
|
||||
|
||||
flag_known_relationship: Option<&'a RelationshipStatus>,
|
||||
flag_has_mutual_connection: bool,
|
||||
|
||||
cached_user_permission: Option<u32>,
|
||||
cached_permission: Option<u64>,
|
||||
}
|
||||
|
||||
impl<'a> PermissionCalculator<'a> {
|
||||
/// Create a new permission calculator
|
||||
pub fn new(perspective: &'a User) -> PermissionCalculator {
|
||||
PermissionCalculator {
|
||||
perspective,
|
||||
|
||||
user: Value::None,
|
||||
channel: Value::None,
|
||||
server: Value::None,
|
||||
member: Value::None,
|
||||
|
||||
flag_known_relationship: None,
|
||||
flag_has_mutual_connection: false,
|
||||
|
||||
cached_user_permission: None,
|
||||
cached_permission: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use user by ref
|
||||
pub fn user(self, user: &'a User) -> PermissionCalculator {
|
||||
PermissionCalculator {
|
||||
user: Value::Ref(user),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Use channel by ref
|
||||
pub fn channel(self, channel: &'a Channel) -> PermissionCalculator {
|
||||
PermissionCalculator {
|
||||
channel: Value::Ref(channel),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Use server by ref
|
||||
pub fn server(self, server: &'a Server) -> PermissionCalculator {
|
||||
PermissionCalculator {
|
||||
server: Value::Ref(server),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Use member by ref
|
||||
pub fn member(self, member: &'a Member) -> PermissionCalculator {
|
||||
PermissionCalculator {
|
||||
member: Value::Ref(member),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Use existing relationship by ref
|
||||
pub fn with_relationship(self, relationship: &'a RelationshipStatus) -> PermissionCalculator {
|
||||
PermissionCalculator {
|
||||
flag_known_relationship: Some(relationship),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the calculated permission contains a given value
|
||||
pub async fn has_permission_value(&mut self, db: &Database, value: u64) -> Result<bool> {
|
||||
let perms = if let Some(perms) = self.cached_permission {
|
||||
perms
|
||||
} else {
|
||||
self.calc(db).await?.0[0]
|
||||
};
|
||||
|
||||
Ok((value) & perms == (value))
|
||||
}
|
||||
|
||||
/// Check whether we have a given permission
|
||||
pub async fn has_permission(&mut self, db: &Database, permission: Permission) -> Result<bool> {
|
||||
self.has_permission_value(db, permission as u64).await
|
||||
}
|
||||
|
||||
/// Check whether we have a given permission, otherwise throw an error
|
||||
pub async fn throw_permission_value(&mut self, db: &Database, value: u64) -> Result<()> {
|
||||
if self.has_permission_value(db, value).await? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::CannotGiveMissingPermissions)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether we have a given permission, otherwise throw an error
|
||||
pub async fn throw_permission(&mut self, db: &Database, permission: Permission) -> Result<()> {
|
||||
if self.has_permission(db, permission).await? {
|
||||
Ok(())
|
||||
} else {
|
||||
Error::from_permission(permission)
|
||||
}
|
||||
}
|
||||
|
||||
/// Throw an error if we cannot grant permissions on either allows or denies
|
||||
/// going from the previous given value to the next given value.
|
||||
///
|
||||
/// We need to check any:
|
||||
/// - allows added (permissions now granted)
|
||||
/// - denies removed (permissions now neutral or granted)
|
||||
pub async fn throw_permission_override<C>(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
current_value: C,
|
||||
next_value: Override,
|
||||
) -> Result<()>
|
||||
where
|
||||
C: Into<Option<Override>>,
|
||||
{
|
||||
let current_value = current_value.into();
|
||||
|
||||
if let Some(current_value) = current_value {
|
||||
self.throw_permission_value(db, !current_value.allows() & next_value.allows())
|
||||
.await?;
|
||||
|
||||
self.throw_permission_value(db, current_value.denies() & !next_value.denies())
|
||||
.await
|
||||
} else {
|
||||
self.throw_permission_value(db, next_value.allows()).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether we has the ViewChannel and another given permission, otherwise throw an error
|
||||
pub async fn throw_permission_and_view_channel(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
permission: Permission,
|
||||
) -> Result<()> {
|
||||
self.throw_permission(db, Permission::ViewChannel).await?;
|
||||
self.throw_permission(db, permission).await
|
||||
}
|
||||
|
||||
/// Get the known member's current ranking
|
||||
pub fn get_member_rank(&self) -> Option<i64> {
|
||||
self.member
|
||||
.get()
|
||||
.map(|member| member.get_ranking(self.server.get().unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Short-hand for creating a permission calculator
|
||||
pub fn perms(perspective: &'_ User) -> PermissionCalculator<'_> {
|
||||
PermissionCalculator::new(perspective)
|
||||
}
|
||||
Reference in New Issue
Block a user