🏡 index : ~doyle/titanirc.git

author Jordan Doyle <jordan@doyle.la> 2023-01-09 22:29:33.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2023-01-09 22:37:42.0 +00:00:00
commit
ba2e69f369e67d81d7d8ae5da6362aabed1303d6 [patch]
tree
b80212bba93e336c6200da27fc816a3bed1dca0f
parent
b62d80bd9e9b51b082136943359644e05a4f8275
download
ba2e69f369e67d81d7d8ae5da6362aabed1303d6.tar.gz

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(-)

diff --git a/src/channel.rs b/src/channel.rs
index 0dfff9f..25e08d4 100644
--- a/src/channel.rs
+++ a/src/channel.rs
@@ -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 {
            // 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
            }
        }
    }
}
diff --git a/src/client.rs b/src/client.rs
index ba93af0..4e32da1 100644
--- a/src/client.rs
+++ a/src/client.rs
@@ -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;
diff --git a/src/messages.rs b/src/messages.rs
index d8fc843..f261b95 100644
--- a/src/messages.rs
+++ a/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};
@@ -82,6 +83,15 @@
#[rtype(result = "super::channel::response::ChannelTopic")]
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<Client>,
    pub modes: Vec<Mode<ChannelMode>>,
}

/// Attempts to kick a user from a channel.

diff --git a/src/channel/permissions.rs b/src/channel/permissions.rs
index 8ce47ac..aa538af 100644
--- a/src/channel/permissions.rs
+++ a/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<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
    }

    /// 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)
    }
}