From ba2e69f369e67d81d7d8ae5da6362aabed1303d6 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Mon, 9 Jan 2023 22:29:33 +0000 Subject: [PATCH] Add basic support for MODE-setting on users in channels --- src/channel.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/channel/permissions.rs | 32 ++++++++++++++++++++++++++++++-- src/client.rs | 18 ++++++++++++++---- src/messages.rs | 10 ++++++++++ 4 files changed, 137 insertions(+), 11 deletions(-) diff --git a/src/channel.rs b/src/channel.rs index 0dfff9f..25e08d4 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -9,8 +9,8 @@ use actix::{ }; use chrono::{DateTime, Utc}; use futures::future::Either; -use irc_proto::{Command, Message}; -use tracing::{debug, error, info, instrument, Span}; +use irc_proto::{Command, Message, Mode}; +use tracing::{debug, error, info, instrument, warn, Span}; use crate::{ channel::{ @@ -23,10 +23,13 @@ use crate::{ connection::InitiatedConnection, messages::{ Broadcast, ChannelFetchTopic, ChannelInvite, ChannelJoin, ChannelKickUser, - ChannelMemberList, ChannelMessage, ChannelPart, ChannelUpdateTopic, FetchClientByNick, - ServerDisconnect, UserKickedFromChannel, UserNickChange, + ChannelMemberList, ChannelMessage, ChannelPart, ChannelSetMode, ChannelUpdateTopic, + FetchClientByNick, ServerDisconnect, UserKickedFromChannel, UserNickChange, + }, + persistence::{ + events::{FetchUserChannelPermissions, SetUserChannelPermissions}, + Persistence, }, - persistence::{events::FetchUserChannelPermissions, Persistence}, server::Server, }; @@ -140,6 +143,81 @@ impl Handler for Channel { } } +impl Handler for Channel { + type Result = (); + + #[instrument(parent = &msg.span, skip_all)] + fn handle(&mut self, msg: ChannelSetMode, ctx: &mut Self::Context) -> Self::Result { + let Some((permissions, client)) = self.clients.get(&msg.client).cloned() else { + return; + }; + + for mode in msg.modes { + // TODO + let (add, channel_mode, arg) = match mode.clone() { + Mode::Plus(mode, arg) => (true, mode, arg), + Mode::Minus(mode, arg) => (false, mode, arg), + }; + + if let Ok(user_mode) = Permission::try_from(channel_mode) { + let Some(affected_nick) = arg else { + error!("No user given"); + continue; + }; + + // TODO: this should allow setting perms not currently in the channel, this probably + // ties into fetching all user permissions on boot of the channel + let Some((_, (affected_user_perms, affected_user))) = + self.clients.iter_mut().find(|(_, (_, connection))| { + connection.nick == affected_nick + }) else { + error!("Unknown user to set perms on"); + continue; + }; + + let new_affected_user_perms = if add { + user_mode + } else if *affected_user_perms == user_mode { + Permission::Normal + } else { + error!("Removing the given permission would do nothing"); + continue; + }; + + if !permissions.can_set_permission(new_affected_user_perms, *affected_user_perms) { + error!( + ?permissions, + ?new_affected_user_perms, + ?affected_user_perms, + "User is not allowed to set permissions for this user" + ); + + continue; + } + + self.persistence.do_send(SetUserChannelPermissions { + channel_id: self.channel_id, + user_id: affected_user.user_id, + permissions: new_affected_user_perms, + }); + + *affected_user_perms = new_affected_user_perms; + + ctx.notify(Broadcast { + message: Message { + tags: None, + prefix: Some(client.to_nick()), + command: Command::ChannelMODE(self.name.to_string(), vec![mode]), + }, + span: Span::current(), + }); + } else { + // TODO + } + } + } +} + /// Received when a user changes their nick. impl Handler for Channel { type Result = (); diff --git a/src/channel/permissions.rs b/src/channel/permissions.rs index 8ce47ac..aa538af 100644 --- a/src/channel/permissions.rs +++ b/src/channel/permissions.rs @@ -1,11 +1,30 @@ +use anyhow::anyhow; +use irc_proto::ChannelMode; + #[derive(Copy, Clone, Debug, Eq, PartialEq, sqlx::Type)] #[repr(i16)] pub enum Permission { Ban = -1, Normal = 0, Voice = 1, - HalfOperator = 2, - Operator = i16::MAX, + HalfOperator = i16::MAX - 2, + Operator = i16::MAX - 1, + Founder = i16::MAX, +} + +impl TryFrom for Permission { + type Error = anyhow::Error; + + fn try_from(value: ChannelMode) -> Result { + match value { + ChannelMode::Ban => Ok(Self::Ban), + ChannelMode::Voice => Ok(Self::Voice), + ChannelMode::Halfop => Ok(Self::HalfOperator), + ChannelMode::Oper => Ok(Self::Operator), + ChannelMode::Founder => Ok(Self::Founder), + _ => Err(anyhow!("unknown user access level: {value:?}")), + } + } } impl Permission { @@ -32,4 +51,13 @@ impl Permission { pub fn can_kick(self) -> bool { self == Self::Operator } + + /// Returns true, if the user is allowed to set the given permission on another + /// user. + #[must_use] + pub const fn can_set_permission(self, new: Self, old: Self) -> bool { + (self as i16) >= (Self::HalfOperator as i16) + && (self as i16) > (new as i16) + && (self as i16) > (old as i16) + } } diff --git a/src/client.rs b/src/client.rs index ba93af0..4e32da1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -15,9 +15,9 @@ use crate::{ connection::{InitiatedConnection, MessageSink, SaslAlreadyAuthenticated}, messages::{ Broadcast, ChannelFetchTopic, ChannelInvite, ChannelJoin, ChannelKickUser, ChannelList, - ChannelMemberList, ChannelMessage, ChannelPart, ChannelUpdateTopic, FetchClientDetails, - ServerDisconnect, ServerFetchMotd, UserKickedFromChannel, UserNickChange, - UserNickChangeInternal, + ChannelMemberList, ChannelMessage, ChannelPart, ChannelSetMode, ChannelUpdateTopic, + FetchClientDetails, ServerDisconnect, ServerFetchMotd, UserKickedFromChannel, + UserNickChange, UserNickChangeInternal, }, persistence::{ events::{FetchUnseenMessages, FetchUserChannels}, @@ -397,7 +397,17 @@ impl StreamHandler> for Client { span: Span::current(), }); } - Command::ChannelMODE(_, _) => {} + Command::ChannelMODE(channel, modes) => { + let Some(channel) = self.channels.get(&channel) else { + return; + }; + + channel.do_send(ChannelSetMode { + span: Span::current(), + client: ctx.address(), + modes, + }); + } Command::TOPIC(channel, topic) => { let Some(channel) = self.channels.get(&channel) else { return; diff --git a/src/messages.rs b/src/messages.rs index d8fc843..f261b95 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,5 +1,6 @@ use actix::{Addr, Message}; use anyhow::Result; +use irc_proto::{ChannelMode, Mode}; use tracing::Span; use crate::{channel::Channel, client::Client, connection::InitiatedConnection}; @@ -84,6 +85,15 @@ pub struct ChannelFetchTopic { pub span: Span, } +/// Sets the given modes on a channel. +#[derive(Message)] +#[rtype(result = "()")] +pub struct ChannelSetMode { + pub span: Span, + pub client: Addr, + pub modes: Vec>, +} + /// Attempts to kick a user from a channel. #[derive(Message)] #[rtype(result = "()")] -- libgit2 1.7.2