From 46cae6f24bf346545ae4a8505f61dfefe191c7f0 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Sun, 3 Jul 2022 20:16:07 -0700 Subject: [PATCH] modules/m_room_member: Support knocking membership/auth_rules. --- include/ircd/m/membership.h | 5 +- matrix/membership.cc | 1 + modules/m_room_member.cc | 92 +++++++++++++++++++++++++++++++++---- 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/include/ircd/m/membership.h b/include/ircd/m/membership.h index 0d601d415..4f4144126 100644 --- a/include/ircd/m/membership.h +++ b/include/ircd/m/membership.h @@ -42,8 +42,9 @@ namespace ircd::m bool membership(const room &, const id::user &, const vector_view &); // Convenience definitions of membership states for atomic queries. - extern const std::initializer_list membership_positive; // join, invite - extern const std::initializer_list membership_negative; // leave, ban, [non-membership] + extern const std::initializer_list + membership_positive, // join, invite + membership_negative; // leave, ban, knock, [non-membership] } inline bool diff --git a/matrix/membership.cc b/matrix/membership.cc index 8724f3aab..4babaa244 100644 --- a/matrix/membership.cc +++ b/matrix/membership.cc @@ -20,6 +20,7 @@ ircd::m::membership_negative { "leave"_sv, "ban"_sv, + "knock"_sv, ""_sv, }; diff --git a/modules/m_room_member.cc b/modules/m_room_member.cc index 28023ddc9..7ea55dbe8 100644 --- a/modules/m_room_member.cc +++ b/modules/m_room_member.cc @@ -10,6 +10,9 @@ namespace ircd::m { + static void auth_room_member_knock(const event &, room::auth::hookdata &); + extern m::hookfn auth_room_member_knock_hookfn; + static void auth_room_member_ban(const event &, room::auth::hookdata &); extern m::hookfn auth_room_member_ban_hookfn; @@ -92,6 +95,10 @@ ircd::m::auth_room_member(const m::event &event, if(membership == "ban") return; // see hook handler + // 6. If membership is knock + if(membership == "knock") + return; // see hook handler + // f. Otherwise, the membership is unknown. Reject. throw FAIL { @@ -157,7 +164,7 @@ ircd::m::auth_room_member_join(const m::event &event, // iv. If the join_rule is invite then allow if membership state // is invite or join. - if(join_rule == "invite") + if(join_rule == "invite" || join_rule == "knock") { if(!data.auth_member_target) throw FAIL @@ -315,13 +322,12 @@ ircd::m::auth_room_member_leave(const m::event &event, // user's current membership state is invite or join. if(json::get<"sender"_>(event) == json::get<"state_key"_>(event)) { - if(data.auth_member_target && membership(*data.auth_member_target) == "join") + static const string_view allowed[] { - data.allow = true; - return; - } + "join", "invite", "knock" + }; - if(data.auth_member_target && membership(*data.auth_member_target) == "invite") + if(data.auth_member_target && membership(*data.auth_member_target, allowed)) { data.allow = true; return; @@ -329,7 +335,8 @@ ircd::m::auth_room_member_leave(const m::event &event, throw FAIL { - "m.room.member membership=leave self-target must have membership=join|invite." + "m.room.member membership=leave " + "self-target must have membership=join|invite|knock." }; } @@ -383,7 +390,6 @@ ircd::m::auth_room_member_leave(const m::event &event, }; } - decltype(ircd::m::auth_room_member_ban_hookfn) ircd::m::auth_room_member_ban_hookfn { @@ -441,3 +447,73 @@ ircd::m::auth_room_member_ban(const m::event &event, "m.room.member membership=ban fails authorization." }; } + +decltype(ircd::m::auth_room_member_knock_hookfn) +ircd::m::auth_room_member_knock_hookfn +{ + auth_room_member_knock, + { + { "_site", "room.auth" }, + { "type", "m.room.member" }, + { "content", + { + { "membership", "knock" }, + }}, + } +}; + +void +ircd::m::auth_room_member_knock(const m::event &event, + room::auth::hookdata &data) +{ + using FAIL = m::room::auth::FAIL; + + // e. If membership is knock + assert(membership(event) == "knock"); + + const json::string &join_rule + { + data.auth_join_rules? + json::get<"content"_>(*data.auth_join_rules).get("join_rule"): + "invite"_sv + }; + + // 1. If the join_rule is anything other than knock, reject. + if(join_rule != "knock" && join_rule != "knock_restricted") + throw FAIL + { + "m.room.member membership=knock :join rule anything other than knock." + }; + + // 2. If sender does not match state_key, reject. + if(at<"sender"_>(event) != at<"state_key"_>(event)) + throw FAIL + { + "m.room.member membership=knock sender != state_key" + }; + + // 3. If the sender’s current membership + if(!data.auth_member_sender) + throw FAIL + { + "m.room.member membership=knock missing sender member auth event." + }; + + static const string_view is_not[] + { + "ban", "invite", "join" + }; + + // ... is not ban, invite, or join, allow. + if(!membership(*data.auth_member_sender, is_not)) + { + data.allow = true; + return; + }; + + // 4. Otherwise, reject. + throw FAIL + { + "m.room.member membership=knock fails authorization." + }; +}