chanserv: support param mode locks (+f flood, +j joinflood)
Some checks failed
CI / check (push) Has been cancelled
Some checks failed
CI / check (push) Has been cancelled
Mode locks were char-only, so Anope FLOOD/JOINFLOOD locks were dropped on migration. Carry a value per locked param-mode through the event log, MLOCK parsing/display, drift enforcement, compaction, and the Anope importer, so a migrated network keeps its flood protection verbatim.
This commit is contained in:
parent
3f97a66dbb
commit
9d94dd5eb0
9 changed files with 168 additions and 26 deletions
|
|
@ -11,7 +11,7 @@ impl Db {
|
|||
self.log
|
||||
.append(Event::ChannelRegistered { name: name.to_string(), founder: founder.to_string(), ts })
|
||||
.map_err(|_| ChanError::Internal)?;
|
||||
self.channels.insert(k, ChannelInfo { name: name.to_string(), founder: founder.to_string(), ts, lock_on: String::new(), lock_off: String::new(), access: Vec::new(), akick: Vec::new(), successor: None, desc: String::new(), entrymsg: String::new(), url: String::new(), email: String::new(), settings: ChanSettings::default(), topic: String::new(), suspension: None, assigned_bot: None , kickers: KickerSettings::default() , badwords: Vec::new(), badwords_rev: 0 , triggers: Vec::new(), triggers_rev: 0, last_used: ts, noexpire: false, expiry_warned: false, oper_note: None });
|
||||
self.channels.insert(k, ChannelInfo { name: name.to_string(), founder: founder.to_string(), ts, lock_on: String::new(), lock_off: String::new(), lock_params: Vec::new(), access: Vec::new(), akick: Vec::new(), successor: None, desc: String::new(), entrymsg: String::new(), url: String::new(), email: String::new(), settings: ChanSettings::default(), topic: String::new(), suspension: None, assigned_bot: None , kickers: KickerSettings::default() , badwords: Vec::new(), badwords_rev: 0 , triggers: Vec::new(), triggers_rev: 0, last_used: ts, noexpire: false, expiry_warned: false, oper_note: None });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -45,15 +45,21 @@ impl Db {
|
|||
}
|
||||
|
||||
/// Set the mode-lock (chars to keep set / unset) for a registered channel.
|
||||
#[cfg(test)]
|
||||
pub fn set_mlock(&mut self, name: &str, on: &str, off: &str) -> Result<(), ChanError> {
|
||||
self.set_mlock_params(name, on, off, Vec::new())
|
||||
}
|
||||
|
||||
pub fn set_mlock_params(&mut self, name: &str, on: &str, off: &str, params: Vec<(char, String)>) -> Result<(), ChanError> {
|
||||
let k = key(name);
|
||||
if !self.channels.contains_key(&k) {
|
||||
return Err(ChanError::NoChannel);
|
||||
}
|
||||
self.log
|
||||
.append(Event::ChannelMlock { name: name.to_string(), on: on.to_string(), off: off.to_string() })
|
||||
.append(Event::ChannelMlock { name: name.to_string(), on: on.to_string(), off: off.to_string(), params: params.clone() })
|
||||
.map_err(|_| ChanError::Internal)?;
|
||||
let c = self.channels.get_mut(&k).unwrap();
|
||||
c.lock_params = params;
|
||||
c.lock_on = on.to_string();
|
||||
c.lock_off = off.to_string();
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ pub enum Event {
|
|||
NickUngrouped { nick: String },
|
||||
ChannelRegistered { name: String, founder: String, ts: u64 },
|
||||
ChannelDropped { name: String },
|
||||
ChannelMlock { name: String, on: String, off: String },
|
||||
ChannelMlock { name: String, on: String, off: String, #[serde(default)] params: Vec<(char, String)> },
|
||||
ChannelAccessAdd { channel: String, account: String, level: String },
|
||||
ChannelAccessDel { channel: String, account: String },
|
||||
ChannelAkickAdd { channel: String, mask: String, reason: String },
|
||||
|
|
@ -429,15 +429,16 @@ pub(crate) fn apply(accounts: &mut HashMap<String, Account>, channels: &mut Hash
|
|||
grouped.remove(&key(&nick));
|
||||
}
|
||||
Event::ChannelRegistered { name, founder, ts } => {
|
||||
channels.insert(key(&name), ChannelInfo { name, founder, ts, lock_on: String::new(), lock_off: String::new(), access: Vec::new(), akick: Vec::new(), successor: None, desc: String::new(), entrymsg: String::new(), url: String::new(), email: String::new(), settings: ChanSettings::default(), topic: String::new(), suspension: None, assigned_bot: None , kickers: KickerSettings::default() , badwords: Vec::new(), badwords_rev: 0 , triggers: Vec::new(), triggers_rev: 0, last_used: ts, noexpire: false, expiry_warned: false, oper_note: None });
|
||||
channels.insert(key(&name), ChannelInfo { name, founder, ts, lock_on: String::new(), lock_off: String::new(), lock_params: Vec::new(), access: Vec::new(), akick: Vec::new(), successor: None, desc: String::new(), entrymsg: String::new(), url: String::new(), email: String::new(), settings: ChanSettings::default(), topic: String::new(), suspension: None, assigned_bot: None , kickers: KickerSettings::default() , badwords: Vec::new(), badwords_rev: 0 , triggers: Vec::new(), triggers_rev: 0, last_used: ts, noexpire: false, expiry_warned: false, oper_note: None });
|
||||
}
|
||||
Event::ChannelDropped { name } => {
|
||||
channels.remove(&key(&name));
|
||||
}
|
||||
Event::ChannelMlock { name, on, off } => {
|
||||
Event::ChannelMlock { name, on, off, params } => {
|
||||
if let Some(c) = channels.get_mut(&key(&name)) {
|
||||
c.lock_on = on;
|
||||
c.lock_off = off;
|
||||
c.lock_params = params;
|
||||
}
|
||||
}
|
||||
Event::ChannelAccessAdd { channel, account, level } => {
|
||||
|
|
|
|||
|
|
@ -408,6 +408,10 @@ pub struct ChannelInfo {
|
|||
pub lock_on: String,
|
||||
#[serde(default)]
|
||||
pub lock_off: String,
|
||||
// Params for locked +modes that take one (e.g. ('f', "mute:7:8s"), ('j', "10:6")).
|
||||
// Each char here is also in `lock_on`; applied and re-asserted with its param.
|
||||
#[serde(default)]
|
||||
pub lock_params: Vec<(char, String)>,
|
||||
#[serde(default)]
|
||||
pub access: Vec<ChanAccess>,
|
||||
#[serde(default)]
|
||||
|
|
@ -613,6 +617,13 @@ impl ChannelInfo {
|
|||
s.push('-');
|
||||
s.push_str(&self.lock_off);
|
||||
}
|
||||
// Params for locked +modes that take one, in the order they appear in lock_on.
|
||||
for ch in self.lock_on.chars() {
|
||||
if let Some((_, p)) = self.lock_params.iter().find(|(c, _)| *c == ch) {
|
||||
s.push(' ');
|
||||
s.push_str(p);
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
|
|
@ -647,6 +658,13 @@ impl ChannelInfo {
|
|||
s.push('-');
|
||||
s.push_str(&reremove);
|
||||
}
|
||||
// A re-asserted param mode (e.g. +f) must carry its locked param.
|
||||
for ch in readd.chars() {
|
||||
if let Some((_, p)) = self.lock_params.iter().find(|(c, _)| *c == ch) {
|
||||
s.push(' ');
|
||||
s.push_str(p);
|
||||
}
|
||||
}
|
||||
Some(s)
|
||||
}
|
||||
}
|
||||
|
|
@ -1087,7 +1105,7 @@ impl Db {
|
|||
for c in self.channels.values() {
|
||||
snapshot.push(Event::ChannelRegistered { name: c.name.clone(), founder: c.founder.clone(), ts: c.ts });
|
||||
if !c.lock_on.is_empty() || !c.lock_off.is_empty() {
|
||||
snapshot.push(Event::ChannelMlock { name: c.name.clone(), on: c.lock_on.clone(), off: c.lock_off.clone() });
|
||||
snapshot.push(Event::ChannelMlock { name: c.name.clone(), on: c.lock_on.clone(), off: c.lock_off.clone(), params: c.lock_params.clone() });
|
||||
}
|
||||
for a in &c.access {
|
||||
snapshot.push(Event::ChannelAccessAdd { channel: c.name.clone(), account: a.account.clone(), level: a.level.clone() });
|
||||
|
|
|
|||
|
|
@ -371,8 +371,8 @@ impl Store for Db {
|
|||
fn drop_channel(&mut self, name: &str) -> Result<(), ChanError> {
|
||||
Db::drop_channel(self, name)
|
||||
}
|
||||
fn set_mlock(&mut self, name: &str, on: &str, off: &str) -> Result<(), ChanError> {
|
||||
Db::set_mlock(self, name, on, off)
|
||||
fn set_mlock(&mut self, name: &str, on: &str, off: &str, params: Vec<(char, String)>) -> Result<(), ChanError> {
|
||||
Db::set_mlock_params(self, name, on, off, params)
|
||||
}
|
||||
fn set_desc(&mut self, channel: &str, desc: &str) -> Result<(), ChanError> {
|
||||
Db::set_desc(self, channel, desc)
|
||||
|
|
@ -560,6 +560,7 @@ fn channel_view(c: &ChannelInfo) -> ChannelView {
|
|||
ts: c.ts,
|
||||
lock_on: c.lock_on.clone(),
|
||||
lock_off: c.lock_off.clone(),
|
||||
lock_params: c.lock_params.clone(),
|
||||
access: c.access.iter().map(|a| ChanAccessView { account: a.account.clone(), level: a.level.clone() }).collect(),
|
||||
akick: c.akick.iter().map(|k| ChanAkickView { mask: k.mask.clone(), reason: k.reason.clone() }).collect(),
|
||||
desc: c.desc.clone(),
|
||||
|
|
|
|||
|
|
@ -438,6 +438,27 @@
|
|||
assert_eq!(db.channel("#chan").unwrap().lock_modes(), "+rnt-s", "survives reopen");
|
||||
}
|
||||
|
||||
// A param mode lock (+f flood) carries its value through render, enforcement,
|
||||
// and a reopen — the Anope FLOOD/JOINFLOOD migration must not lose it.
|
||||
#[test]
|
||||
fn param_mode_lock_carries_its_value() {
|
||||
let p = tmp("mlock_param");
|
||||
let mut db = Db::open(&p, "local");
|
||||
db.register_channel("#chan", "alice").unwrap();
|
||||
db.set_mlock_params("#chan", "ntf", "", vec![('f', "mute:7:8s".to_string())]).unwrap();
|
||||
|
||||
let info = db.channel("#chan").unwrap();
|
||||
assert_eq!(info.lock_modes(), "+rntf mute:7:8s");
|
||||
// Removing the locked param mode re-asserts it with its value.
|
||||
assert_eq!(info.enforce("-f"), Some("+f mute:7:8s".to_string()));
|
||||
|
||||
drop(db);
|
||||
let db = Db::open(&p, "local");
|
||||
let info = db.channel("#chan").unwrap();
|
||||
assert_eq!(info.lock_params, vec![('f', "mute:7:8s".to_string())], "param survives reopen");
|
||||
assert_eq!(info.lock_modes(), "+rntf mute:7:8s");
|
||||
}
|
||||
|
||||
// Access list drives join modes, is case-insensitive, and persists.
|
||||
#[test]
|
||||
fn access_list_grants_join_modes() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue