Servers: New permissions implementation.

Feature: Roles and group permissions. (server-side)
This commit is contained in:
Paul
2021-06-26 22:16:37 +01:00
parent 35b0c69cdb
commit 07e74e1074
11 changed files with 195 additions and 35 deletions

View File

@@ -1,3 +1,5 @@
use std::collections::HashMap;
use crate::database::*;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
@@ -50,6 +52,9 @@ pub enum Channel {
icon: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]
last_message: Option<LastMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
permissions: Option<i32>,
},
TextChannel {
#[serde(rename = "_id")]
@@ -61,10 +66,16 @@ pub enum Channel {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]
last_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
default_permissions: Option<i32>,
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
role_permissions: HashMap<String, i32>
},
VoiceChannel {
#[serde(rename = "_id")]
@@ -78,6 +89,11 @@ pub enum Channel {
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]
default_permissions: Option<i32>,
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
role_permissions: HashMap<String, i32>
},
}

View File

@@ -1,3 +1,5 @@
use std::collections::HashMap;
use crate::database::*;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
@@ -25,6 +27,23 @@ pub struct Member {
pub nickname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roles: Option<Vec<String>>
}
pub type PermissionTuple = (
i32, // server permission
i32 // channel permission
);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Role {
pub name: String,
pub permissions: PermissionTuple,
#[serde(skip_serializing_if = "Option::is_none")]
pub colour: Option<String>
// Bri'ish API conventions
}
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -59,10 +78,15 @@ pub struct Server {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub channels: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_messages: Option<SystemMessageChannels>,
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
pub roles: HashMap<String, Role>,
pub default_permissions: PermissionTuple,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]

View File

@@ -1,11 +1,8 @@
use crate::database::{get_collection, get_db};
use crate::database::{permissions, get_collection, get_db, PermissionTuple};
use futures::StreamExt;
use log::info;
use mongodb::{
bson::{doc, from_document},
options::FindOptions,
};
use mongodb::{bson::{doc, from_document, to_document}, options::FindOptions};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
@@ -14,7 +11,7 @@ struct MigrationInfo {
revision: i32,
}
pub const LATEST_REVISION: i32 = 5;
pub const LATEST_REVISION: i32 = 6;
pub async fn migrate_database() {
let migrations = get_collection("migrations");
@@ -157,6 +154,33 @@ pub async fn run_migrations(revision: i32) -> i32 {
.expect("Failed to create channel_invites collection.");
}
if revision <= 5 {
info!("Running migration [revision 5 / 2021-06-26]: Add permissions.");
#[derive(Serialize)]
struct Server {
pub default_permissions: PermissionTuple,
}
let server = Server {
default_permissions: (
*permissions::server::DEFAULT_PERMISSION as i32,
*permissions::channel::DEFAULT_PERMISSION_SERVER as i32
)
};
get_collection("servers")
.update_many(
doc! { },
doc! {
"$set": to_document(&server).unwrap()
},
None
)
.await
.expect("Failed to migrate servers.");
}
// Reminder to update LATEST_REVISION when adding new migrations.
LATEST_REVISION
}

View File

@@ -1,5 +1,5 @@
use crate::database::*;
use crate::util::result::Result;
use crate::util::result::{Error, Result};
use super::PermissionCalculator;
@@ -19,6 +19,25 @@ pub enum ChannelPermission {
UploadFiles = 0b00000000000000000000000010000000, // 128
}
lazy_static! {
pub static ref DEFAULT_PERMISSION_DM: u32 =
ChannelPermission::View
+ ChannelPermission::SendMessage
+ ChannelPermission::ManageChannel
+ ChannelPermission::VoiceCall
+ ChannelPermission::InviteOthers
+ ChannelPermission::EmbedLinks
+ ChannelPermission::UploadFiles;
pub static ref DEFAULT_PERMISSION_SERVER: u32 =
ChannelPermission::View
+ ChannelPermission::SendMessage
+ ChannelPermission::VoiceCall
+ ChannelPermission::InviteOthers
+ ChannelPermission::EmbedLinks
+ ChannelPermission::UploadFiles;
}
impl_op_ex!(+ |a: &ChannelPermission, b: &ChannelPermission| -> u32 { *a as u32 | *b as u32 });
impl_op_ex_commutative!(+ |a: &u32, b: &ChannelPermission| -> u32 { *a | *b as u32 });
@@ -62,11 +81,7 @@ impl<'a> PermissionCalculator<'a> {
let perms = self.for_user(recipient).await?;
if perms.get_send_message() {
return Ok(ChannelPermission::View
+ ChannelPermission::SendMessage
+ ChannelPermission::VoiceCall
+ ChannelPermission::EmbedLinks
+ ChannelPermission::UploadFiles);
return Ok(*DEFAULT_PERMISSION_DM);
}
return Ok(ChannelPermission::View as u32);
@@ -75,36 +90,61 @@ impl<'a> PermissionCalculator<'a> {
Ok(0)
}
Channel::Group { recipients, .. } => {
Channel::Group { recipients, permissions, owner, .. } => {
if &self.perspective.id == owner {
return Ok(*DEFAULT_PERMISSION_DM)
}
if recipients
.iter()
.find(|x| *x == &self.perspective.id)
.is_some()
{
Ok(ChannelPermission::View
+ ChannelPermission::SendMessage
+ ChannelPermission::ManageChannel
+ ChannelPermission::VoiceCall
+ ChannelPermission::InviteOthers
+ ChannelPermission::EmbedLinks
+ ChannelPermission::UploadFiles)
if let Some(permissions) = permissions {
Ok(permissions.clone() as u32)
} else {
Ok(*DEFAULT_PERMISSION_DM)
}
} else {
Ok(0)
}
}
Channel::TextChannel { server, .. }
| Channel::VoiceChannel { server, .. } => {
Channel::TextChannel { server, default_permissions, role_permissions, .. }
| Channel::VoiceChannel { server, default_permissions, role_permissions, .. } => {
let server = Ref::from_unchecked(server.clone()).fetch_server().await?;
if self.perspective.id == server.owner {
Ok(u32::MAX)
} else {
Ok(ChannelPermission::View
+ ChannelPermission::SendMessage
+ ChannelPermission::VoiceCall
+ ChannelPermission::InviteOthers
+ ChannelPermission::EmbedLinks
+ ChannelPermission::UploadFiles)
match Ref::from_unchecked(self.perspective.id.clone()).fetch_member(&server.id).await {
Ok(member) => {
let mut perm = if let Some(permission) = default_permissions {
*permission as u32
} else {
server.default_permissions.1 as u32
};
if let Some(roles) = member.roles {
for role in roles {
if let Some(permission) = role_permissions.get(&role) {
perm |= *permission as u32;
}
if let Some(server_role) = server.roles.get(&role) {
perm |= server_role.permissions.1 as u32;
}
}
}
Ok(perm)
}
Err(error) => {
match &error {
Error::NotFound => Ok(0),
_ => Err(error)
}
}
}
}
}
}

View File

@@ -13,6 +13,7 @@ pub struct PermissionCalculator<'a> {
relationship: Option<&'a RelationshipStatus>,
channel: Option<&'a Channel>,
server: Option<&'a Server>,
// member: Option<&'a Member>,
has_mutual_connection: bool,
}
@@ -26,6 +27,7 @@ impl<'a> PermissionCalculator<'a> {
relationship: None,
channel: None,
server: None,
// member: None,
has_mutual_connection: false,
}
@@ -59,6 +61,13 @@ impl<'a> PermissionCalculator<'a> {
}
}
/* pub fn with_member(self, member: &'a Member) -> PermissionCalculator {
PermissionCalculator {
member: Some(&member),
..self
}
} */
pub fn with_mutual_connection(self) -> PermissionCalculator<'a> {
PermissionCalculator {
has_mutual_connection: true,

View File

@@ -1,6 +1,7 @@
use crate::util::result::Result;
use crate::util::result::{Error, Result};
use super::PermissionCalculator;
use super::Ref;
use num_enum::TryFromPrimitive;
use std::ops;
@@ -22,6 +23,13 @@ pub enum ServerPermission {
// 16 bits of space
}
lazy_static! {
pub static ref DEFAULT_PERMISSION: u32 =
ServerPermission::View
+ ServerPermission::ChangeNickname
+ ServerPermission::ChangeAvatar;
}
impl_op_ex!(+ |a: &ServerPermission, b: &ServerPermission| -> u32 { *a as u32 | *b as u32 });
impl_op_ex_commutative!(+ |a: &u32, b: &ServerPermission| -> u32 { *a | *b as u32 });
@@ -52,9 +60,26 @@ impl<'a> PermissionCalculator<'a> {
if self.perspective.id == server.owner {
Ok(u32::MAX)
} else {
Ok(ServerPermission::View
+ ServerPermission::ChangeNickname
+ ServerPermission::ChangeAvatar)
match Ref::from_unchecked(self.perspective.id.clone()).fetch_member(&server.id).await {
Ok(member) => {
let mut perm = server.default_permissions.0 as u32;
if let Some(roles) = member.roles {
for role in roles {
if let Some(server_role) = server.roles.get(&role) {
perm |= server_role.permissions.0 as u32;
}
}
}
Ok(perm)
}
Err(error) => {
match &error {
Error::NotFound => Ok(0),
_ => Err(error)
}
}
}
}
}

View File

@@ -73,6 +73,7 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
recipients: set.into_iter().collect::<Vec<String>>(),
icon: None,
last_message: None,
permissions: None
};
channel.clone().publish().await?;

View File

@@ -1,3 +1,5 @@
use std::collections::HashMap;
use crate::database::*;
use crate::util::result::{Error, Result};
@@ -76,6 +78,9 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<JsonValue>
description: info.description,
icon: None,
last_message: None,
default_permissions: None,
role_permissions: HashMap::new()
},
ChannelType::Voice => Channel::VoiceChannel {
id: id.clone(),
@@ -84,7 +89,10 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<JsonValue>
name: info.name,
description: info.description,
icon: None
icon: None,
default_permissions: None,
role_permissions: HashMap::new()
}
};

View File

@@ -1,3 +1,5 @@
use std::collections::HashMap;
use crate::database::*;
use crate::util::result::{Error, Result};
@@ -51,6 +53,7 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
name: info.name,
description: info.description,
channels: vec![cid.clone()],
system_messages: Some(SystemMessageChannels {
user_joined: Some(cid.clone()),
@@ -59,6 +62,12 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
user_banned: Some(cid.clone()),
}),
roles: HashMap::new(),
default_permissions: (
*permissions::server::DEFAULT_PERMISSION as i32,
*permissions::channel::DEFAULT_PERMISSION_SERVER as i32
),
icon: None,
banner: None,
};
@@ -69,8 +78,12 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
nonce: Some(info.nonce),
name: "general".to_string(),
description: None,
icon: None,
last_message: None,
default_permissions: None,
role_permissions: HashMap::new()
}
.publish()
.await?;

View File

@@ -1 +1 @@
pub const VERSION: &str = "0.5.1-alpha.0-patch.0";
pub const VERSION: &str = "0.5.1-alpha.1";