diff --git a/compose.yml b/compose.yml index 3e94d1adc..04b0b5e7e 100644 --- a/compose.yml +++ b/compose.yml @@ -3,13 +3,13 @@ services: redis: image: eqalpha/keydb ports: - - "14079:6379" + - "6379:6379" # MongoDB database: image: mongo ports: - - "14017:27017" + - "27017:27017" volumes: - ./.data/db:/data/db diff --git a/crates/core/config/Revolt.test.toml b/crates/core/config/Revolt.test.toml index 084a46ee0..7ac98ee26 100644 --- a/crates/core/config/Revolt.test.toml +++ b/crates/core/config/Revolt.test.toml @@ -1,3 +1,9 @@ [database] mongodb = "mongodb://localhost" redis = "redis://localhost/" + +[rabbit] +host = "127.0.0.1" +port = 5672 +username = "rabbituser" +password = "rabbitpass" diff --git a/crates/core/database/src/util/bulk_permissions.rs b/crates/core/database/src/util/bulk_permissions.rs index eb9bca475..400359772 100644 --- a/crates/core/database/src/util/bulk_permissions.rs +++ b/crates/core/database/src/util/bulk_permissions.rs @@ -174,7 +174,7 @@ async fn calculate_members_permissions<'a>( ) -> HashMap { let mut resp = HashMap::new(); - let (_, channel_role_permissions) = match query + let (_, channel_role_permissions, channel_default_permissions) = match query .channel .as_ref() .expect("A channel must be assigned to calculate channel permissions") @@ -183,13 +183,15 @@ async fn calculate_members_permissions<'a>( Channel::TextChannel { id, role_permissions, + default_permissions, .. } | Channel::VoiceChannel { id, role_permissions, + default_permissions, .. - } => (id, role_permissions), + } => (id, role_permissions, default_permissions), _ => panic!("Calculation of member permissions must be done on a server channel"), }; @@ -273,6 +275,10 @@ async fn calculate_members_permissions<'a>( // Get the user's server permissions let mut permission = calculate_server_permissions(&query.server, user, member); + if let Some(defaults) = channel_default_permissions { + permission.apply(defaults.into()); + } + // Get the applicable role overrides let mut roles = channel_role_permissions .iter() diff --git a/crates/delta/src/main.rs b/crates/delta/src/main.rs index 5f737e4a4..478a212f6 100644 --- a/crates/delta/src/main.rs +++ b/crates/delta/src/main.rs @@ -10,7 +10,7 @@ pub mod util; use revolt_config::config; use revolt_database::events::client::EventV1; -use revolt_database::{Database, MongoDb, AMQP}; +use revolt_database::AMQP; use rocket::{Build, Rocket}; use rocket_cors::{AllowedOrigins, CorsOptions}; use rocket_prometheus::PrometheusMetrics; @@ -34,7 +34,7 @@ pub async fn web() -> Rocket { db.migrate_database().await.unwrap(); // Setup Authifier event channel - let (sender, receiver) = unbounded(); + let (_, receiver) = unbounded(); // Setup Authifier let authifier = db.clone().to_authifier().await; diff --git a/crates/delta/src/routes/channels/message_send.rs b/crates/delta/src/routes/channels/message_send.rs index ab3ba5582..55c133088 100644 --- a/crates/delta/src/routes/channels/message_send.rs +++ b/crates/delta/src/routes/channels/message_send.rs @@ -109,3 +109,218 @@ pub async fn message_send( .into_model(Some(model_user), model_member), )) } + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use crate::{rocket, util::test::TestHarness}; + use revolt_database::{ + util::{idempotency::IdempotencyKey, reference::Reference}, + Channel, Member, Message, PartialChannel, PartialMember, Role, Server, + }; + use revolt_models::v0::{self, DataCreateServerChannel}; + use revolt_permissions::{ChannelPermission, OverrideField}; + + #[rocket::async_test] + async fn message_mention_constraints() { + let harness = TestHarness::new().await; + let (_, _, user) = harness.new_user().await; + let (_, _, second_user) = harness.new_user().await; + + let (server, channels) = Server::create( + &harness.db, + v0::DataCreateServer { + name: "Test Server".to_string(), + ..Default::default() + }, + &user, + true, + ) + .await + .expect("Failed to create test server"); + + let server_mut: &mut Server = &mut server.clone(); + let mut locked_channel = Channel::create_server_channel( + &harness.db, + server_mut, + DataCreateServerChannel { + channel_type: v0::LegacyServerChannelType::Text, + name: "Hidden Channel".to_string(), + description: None, + nsfw: Some(false), + }, + true, + ) + .await + .expect("Failed to make new channel"); + + let role = Role { + name: "Show Hidden Channel".to_string(), + permissions: OverrideField { a: 0, d: 0 }, + colour: None, + hoist: false, + rank: 5, + }; + + let role_id = role + .create(&harness.db, &server.id) + .await + .expect("Failed to create the role"); + + let mut overrides = HashMap::new(); + overrides.insert( + role_id.clone(), + OverrideField { + a: (ChannelPermission::ViewChannel) as i64, + d: 0, + }, + ); + + let partial = PartialChannel { + name: None, + owner: None, + description: None, + icon: None, + nsfw: None, + active: None, + permissions: None, + role_permissions: Some(overrides), + default_permissions: Some(OverrideField { + a: 0, + d: ChannelPermission::ViewChannel as i64, + }), + last_message_id: None, + }; + locked_channel + .update(&harness.db, partial, vec![]) + .await + .expect("Failed to update the channel permissions for special role"); + + Member::create(&harness.db, &server, &user, Some(channels.clone())) + .await + .expect("Failed to create member"); + let member = Reference::from_unchecked(user.id.clone()) + .as_member(&harness.db, &server.id) + .await + .expect("Failed to get member"); + + // Second user is not part of the server + let message = Message::create_from_api( + &harness.db, + Some(&harness.amqp), + locked_channel.clone(), + v0::DataMessageSend { + content: Some(format!("<@{}>", second_user.id)), + nonce: None, + attachments: None, + replies: None, + embeds: None, + masquerade: None, + interactions: None, + flags: None, + }, + v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await), + Some(user.clone().into(&harness.db, Some(&user)).await), + Some(member.clone().into()), + user.limits().await, + IdempotencyKey::unchecked_from_string("0".to_string()), + false, + true, + ) + .await + .expect("Failed to create message"); + + // The mention should not go through here + assert!( + message.mentions.is_none() || message.mentions.unwrap().is_empty(), + "Mention failed to be scrubbed when the user is not part of the server" + ); + + Member::create(&harness.db, &server, &second_user, Some(channels.clone())) + .await + .expect("Failed to create second member"); + let mut second_member = Reference::from_unchecked(second_user.id.clone()) + .as_member(&harness.db, &server.id) + .await + .expect("Failed to get second member"); + + // Second user cannot see the channel + let message = Message::create_from_api( + &harness.db, + Some(&harness.amqp), + locked_channel.clone(), + v0::DataMessageSend { + content: Some(format!("<@{}>", second_user.id)), + nonce: None, + attachments: None, + replies: None, + embeds: None, + masquerade: None, + interactions: None, + flags: None, + }, + v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await), + Some(user.clone().into(&harness.db, Some(&user)).await), + Some(member.clone().into()), + user.limits().await, + IdempotencyKey::unchecked_from_string("1".to_string()), + false, + true, + ) + .await + .expect("Failed to create message"); + + // The mention should not go through here + assert!( + message.mentions.is_none() || message.mentions.unwrap().is_empty(), + "Mention failed to be scrubbed when the user cannot see the channel" + ); + + let second_member_roles = vec![role_id.clone()]; + let partial = PartialMember { + id: None, + joined_at: None, + nickname: None, + avatar: None, + timeout: None, + roles: Some(second_member_roles), + }; + second_member + .update(&harness.db, partial, vec![]) + .await + .expect("Failed to update the second user's roles"); + + // This time the mention SHOULD go through + let message = Message::create_from_api( + &harness.db, + Some(&harness.amqp), + locked_channel.clone(), + v0::DataMessageSend { + content: Some(format!("<@{}>", second_user.id)), + nonce: None, + attachments: None, + replies: None, + embeds: None, + masquerade: None, + interactions: None, + flags: None, + }, + v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await), + Some(user.clone().into(&harness.db, Some(&user)).await), + Some(member.clone().into()), + user.limits().await, + IdempotencyKey::unchecked_from_string("2".to_string()), + false, + true, + ) + .await + .expect("Failed to create message"); + + // The mention SHOULD go through here + assert!( + message.mentions.is_some() && !message.mentions.unwrap().is_empty(), + "Mention was scrubbed when the user can see the channel" + ); + } +} diff --git a/crates/delta/src/util/test.rs b/crates/delta/src/util/test.rs index 467e945be..9b3f80b86 100644 --- a/crates/delta/src/util/test.rs +++ b/crates/delta/src/util/test.rs @@ -5,7 +5,7 @@ use authifier::{ use futures::StreamExt; use rand::Rng; use redis_kiss::redis::aio::PubSub; -use revolt_database::{events::client::EventV1, Database, User}; +use revolt_database::{events::client::EventV1, Database, User, AMQP}; use revolt_models::v0; use rocket::local::asynchronous::Client; @@ -13,6 +13,7 @@ pub struct TestHarness { pub client: Client, authifier: Authifier, pub db: Database, + pub amqp: AMQP, sub: PubSub, event_buffer: Vec<(String, EventV1)>, } @@ -20,7 +21,7 @@ pub struct TestHarness { impl TestHarness { pub async fn new() -> TestHarness { dotenv::dotenv().ok(); - + let config = revolt_config::config().await; let client = Client::tracked(crate::web().await) .await .expect("valid rocket instance"); @@ -43,10 +44,25 @@ impl TestHarness { .expect("`Authifier`") .clone(); + let connection = amqprs::connection::Connection::open( + &amqprs::connection::OpenConnectionArguments::new( + &config.rabbit.host, + config.rabbit.port, + &config.rabbit.username, + &config.rabbit.password, + ), + ) + .await + .unwrap(); + let channel = connection.open_channel(None).await.unwrap(); + + let amqp = AMQP::new(connection, channel); + TestHarness { client, authifier, db, + amqp, sub, event_buffer: vec![], }