diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 302a96a..981e000 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,8 @@ For admins ---------- - Command line option --test-config (or -t) has been added. When used, biboumi will just exit without any error if the configuration is correct +- Options persist_user_denylist and persist_user_allowlist have been added to + granualy control who can set the persist option For packagers ------------- diff --git a/doc/admin.rst b/doc/admin.rst index 08ef2f2..a04a59a 100644 --- a/doc/admin.rst +++ b/doc/admin.rst @@ -246,6 +246,19 @@ configuration file is located: for example if biboumi reads its configuration from /etc/biboumi/biboumi.cfg, the policy_directory value will be /etc/biboumi. +persist_user_denylist +~~~~~~~~~~~~~~~~~~~~~ + +A list of XMPP users or domains who are not allowed to change the `persist` +ad-hoc option and who will not be impacted by the `persistent_by_default` +config option. Specific JIDs in this list will take priority over allowed JIDs +or domains. + +persist_user_allowlist +~~~~~~~~~~~~~~~~~~~~~~ + +A list of XMPP XMPP users or domains who are allowed to change the `persist` +ad-hoc option. TLS configuration ----------------- diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 0b90c8b..b123f63 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -499,7 +500,9 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con bool persistent = false; #ifdef USE_DATABASE const auto goptions = Database::get_global_options(this->user_jid); - if (goptions.col()) + if (!utils::is_requester_allowed_to_persist(Jid(this->user_jid))) + persistent = false; + else if (goptions.col()) persistent = true; else { diff --git a/src/utils/is_requester_allowed_to_persist.hpp b/src/utils/is_requester_allowed_to_persist.hpp new file mode 100644 index 0000000..5dc5956 --- /dev/null +++ b/src/utils/is_requester_allowed_to_persist.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "xmpp/jid.hpp" +#include + +namespace utils +{ +/** + * Check config if a given requester is allowed to persist target + * + * The following values are checked + * + * - A user's jid (foo@example.com) + * - A user's domain (example.com) + * + * The following config options are checked in order + * + * 1. `persist_user_denylist` contains the requester's jid + * 2. `persist_user_allowlist` contains the requester's jid + * 3. `persist_user_denylist` contains the requester's domain + * 4. `persist_user_allowlist` contains the contains the requester's domain + * 5. allow + */ +inline bool is_requester_allowed_to_persist(const Jid &requester) { + // requester.bare checks + // deny + // 1. + bool jid_in_user_denylist = Config::is_in_list("persist_user_denylist", requester.bare()); + if (jid_in_user_denylist) + return false; + + // allow + // 2. + bool jid_in_user_allowlist = Config::is_in_list("persist_user_allowlist", requester.bare()); + if (jid_in_user_allowlist) + return true; + + // requester.domain checks + // deny + // 3. + bool domain_in_user_denylist = Config::is_in_list("persist_user_denylist", requester.domain); + if (domain_in_user_denylist) + return false; + + // allow + // 4. + bool domain_in_user_allowlist = Config::is_in_list("persist_user_allowlist", requester.domain); + if (domain_in_user_allowlist) + return true; + + // default allow + // 5. + return true; +} +} diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index aea316d..4a267e9 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -159,6 +160,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman } } + if (utils::is_requester_allowed_to_persist(Jid(session.get_owner_jid()))) { XmlSubNode persistent(x, "field"); persistent["var"] = "persistent"; @@ -208,7 +210,8 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, bridge->set_record_history(options.col()); } else if (field->get_tag("var") == "persistent" && - value) + value + && utils::is_requester_allowed_to_persist(Jid(session.get_owner_jid()))) options.col() = to_bool(value->get_inner()); } @@ -640,6 +643,7 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester, } } + if (utils::is_requester_allowed_to_persist(requester)) { XmlSubNode persistent(x, "field"); persistent["var"] = "persistent"; @@ -699,7 +703,7 @@ bool handle_irc_channel_configuration_form(XmppComponent& xmpp_component, const else if (field->get_tag("var") == "encoding_in" && value) options.col() = value->get_inner(); - else if (field->get_tag("var") == "persistent" && value) + else if (field->get_tag("var") == "persistent" && value && utils::is_requester_allowed_to_persist(requester)) options.col() = to_bool(value->get_inner()); else if (field->get_tag("var") == "record_history" && value && !value->get_inner().empty()) @@ -953,3 +957,4 @@ void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, message = ss.str(); } + diff --git a/tests/is_requester_allowed_to_persist.cpp b/tests/is_requester_allowed_to_persist.cpp new file mode 100644 index 0000000..69cf5f4 --- /dev/null +++ b/tests/is_requester_allowed_to_persist.cpp @@ -0,0 +1,89 @@ +#include "catch2/catch_test_macros.hpp" +#include "utils/is_requester_allowed_to_persist.hpp" +#include "xmpp/jid.hpp" + + +TEST_CASE("default") +{ + Jid jid("foo@example.com"); + Config::clear(); + Config::set("persist_user_denylist", ""); + Config::set("persist_user_allowlist", ""); + bool result = utils::is_requester_allowed_to_persist(jid); + CHECK(result == true); + Config::clear(); +} + +TEST_CASE("denylist - jid") +{ + Jid jid("foo@example.com"); + Config::clear(); + Config::set("persist_user_denylist", "foo@example.com"); + bool result = utils::is_requester_allowed_to_persist(jid); + CHECK(result == false); + Config::clear(); +} + +TEST_CASE("denylist - domain") +{ + Jid jid("foo@example.com"); + Config::clear(); + Config::set("persist_user_denylist", "example.com"); + bool result = utils::is_requester_allowed_to_persist(jid); + CHECK(result == false); + Config::clear(); +} + +TEST_CASE("allowlist - jid") +{ + Jid jid("foo@example.com"); + Config::clear(); + Config::set("persist_user_allowlist", "foo@example.com"); + bool result = utils::is_requester_allowed_to_persist(jid); + CHECK(result == true); + Config::clear(); +} + +TEST_CASE("allowlist - domain") +{ + Jid jid("foo@example.com"); + Config::clear(); + Config::set("persist_user_allowlist", "example.com"); + bool result = utils::is_requester_allowed_to_persist(jid); + CHECK(result == true); + Config::clear(); +} + +TEST_CASE("denylist + allowlist - deny domain, allow jid") +{ + Jid jid("foo@example.com"); + Config::clear(); + Config::set("persist_user_denylist", "example.com"); + Config::set("persist_user_allowlist", "foo@example.com"); + bool result = utils::is_requester_allowed_to_persist(jid); + CHECK(result == true); + Config::clear(); +} + +TEST_CASE("denylist + allowlist - deny jid, allow domain") +{ + Jid jid("foo@example.com"); + Config::clear(); + Config::set("persist_user_denylist", "foo@example.com"); + Config::set("persist_user_allowlist", "example.com"); + bool result = utils::is_requester_allowed_to_persist(jid); + CHECK(result == false); + Config::clear(); +} + +TEST_CASE("denylist + allowlist - deny jid, allow jid") +{ + Jid jid("foo@example.com"); + Config::clear(); + Config::set("persist_user_denylist", "foo@example.com"); + Config::set("persist_user_allowlist", "foo@example.com"); + bool result = utils::is_requester_allowed_to_persist(jid); + CHECK(result == false); + Config::clear(); +} +