Add basic support for MODE-setting on users in channels
Diff
src/channel.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/client.rs | 18 +++++++++++++++---
src/messages.rs | 10 ++++++++++
src/channel/permissions.rs | 32 ++++++++++++++++++++++++++++++++
4 files changed, 137 insertions(+), 11 deletions(-)
@@ -9,8 +9,8 @@
};
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 @@
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, Persistence},
persistence::{
events::{FetchUserChannelPermissions, SetUserChannelPermissions},
Persistence,
},
server::Server,
};
@@ -136,6 +139,81 @@
command: Command::PRIVMSG(self.name.to_string(), msg.message.clone()),
},
});
}
}
}
impl Handler<ChannelSetMode> 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 {
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;
};
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 {
}
}
}
}
@@ -15,9 +15,9 @@
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 @@
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;
@@ -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};
@@ -82,6 +83,15 @@
#[rtype(result = "super::channel::response::ChannelTopic")]
pub struct ChannelFetchTopic {
pub span: Span,
}
#[derive(Message)]
#[rtype(result = "()")]
pub struct ChannelSetMode {
pub span: Span,
pub client: Addr<Client>,
pub modes: Vec<Mode<ChannelMode>>,
}
@@ -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<ChannelMode> for Permission {
type Error = anyhow::Error;
fn try_from(value: ChannelMode) -> Result<Self, Self::Error> {
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 {
@@ -31,5 +50,14 @@
#[must_use]
pub fn can_kick(self) -> bool {
self == Self::Operator
}
#[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)
}
}