// Matrix Construct // // Copyright (C) Matrix Construct Developers, Authors & Contributors // Copyright (C) 2016-2019 Jason Volk // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. ircd::m::room::power::revoke::revoke(json::stack::object &out, const room::power &power, const pair &prop_key) :returns{false} { bool &ret(*this); const auto replace{[&ret] (json::stack::object &out, const json::object &object, const string_view &sought) { for(const auto &[key, val] : object) { if(key == sought) { ret = true; continue; } json::stack::member { out, key, val }; } }}; power.view([&out, &prop_key, &replace] (const json::object &power) { const auto &[revoke_prop, revoke_key] { prop_key }; if(!revoke_prop) { replace(out, power, revoke_key); return; } for(const auto &[key, val] : power) { if(key == revoke_prop) { json::stack::object prop { out, key }; replace(prop, val, revoke_key); continue; } json::stack::member { out, key, val }; } }); } ircd::m::room::power::grant::grant(json::stack::object &out, const room::power &power, const pair &prop_key, const int64_t &level) :returns{false} { bool &ret(*this); const auto replace{[&ret, &level] (json::stack::object &out, const json::object &object, const string_view &sought) { for(const auto &[key, val] : object) { if(key == sought) { json::stack::member { out, key, json::value{level} }; ret = true; continue; } else if(key > sought && !ret) // maintains lexical sorting { json::stack::member { out, sought, json::value{level} }; ret = true; } json::stack::member { out, key, val }; } }}; power.view([&out, &prop_key, &level, &replace] (const json::object &power) { const auto &[grant_prop, grant_key] { prop_key }; if(!grant_prop) { replace(out, power, grant_key); return; } for(const auto &[key, val] : power) { if(key == grant_prop) { json::stack::object prop { out, key }; replace(prop, val, grant_key); continue; } json::stack::member { out, key, val }; } }); } // // room::power // decltype(ircd::m::room::power::default_creator_level) ircd::m::room::power::default_creator_level { { "name", "ircd.m.room.power.default.creator_level" }, { "default", 100L }, }; decltype(ircd::m::room::power::default_power_level) ircd::m::room::power::default_power_level { { "name", "ircd.m.room.power.default.power_level" }, { "default", 50L }, }; decltype(ircd::m::room::power::default_event_level) ircd::m::room::power::default_event_level { { "name", "ircd.m.room.power.default.event_level" }, { "default", 0L }, }; decltype(ircd::m::room::power::default_user_level) ircd::m::room::power::default_user_level { { "name", "ircd.m.room.power.default.user_level" }, { "default", 0L }, }; ircd::json::object ircd::m::room::power::default_content(const mutable_buffer &buf, const m::user::id &creator) { return compose_content(buf, [&creator] (const string_view &key, json::stack::object &object) { if(key != "users") return; assert(default_creator_level == 100); json::stack::member { object, creator, json::value(default_creator_level) }; }); } ircd::json::object ircd::m::room::power::compose_content(const mutable_buffer &buf, const compose_closure &closure) { json::stack out{buf}; json::stack::object content{out}; assert(default_power_level == 50); json::stack::member { content, "ban", json::value(default_power_level) }; { json::stack::object events { content, "events" }; json::stack::member { events, "m.room.encryption", json::value(default_creator_level) }; json::stack::member { events, "m.room.server_acl", json::value(default_creator_level) }; json::stack::member { events, "m.room.tombstone", json::value(default_creator_level) }; closure("events", events); } assert(default_event_level == 0); json::stack::member { content, "events_default", json::value(default_event_level) }; json::stack::member { content, "historical", json::value(default_creator_level) }; json::stack::member { content, "invite", json::value(default_power_level) }; json::stack::member { content, "kick", json::value(default_power_level) }; { json::stack::object notifications { content, "notifications" }; json::stack::member { notifications, "room", json::value(default_power_level) }; closure("notifications", notifications); } json::stack::member { content, "redact", json::value(default_power_level) }; json::stack::member { content, "state_default", json::value(default_power_level) }; { json::stack::object users { content, "users" }; closure("users", users); } assert(default_user_level == 0); json::stack::member { content, "users_default", json::value(default_user_level) }; content.~object(); return json::object{out.completed()}; } // // room::power::power // ircd::m::room::power::power(const m::room &room) :power { room, room.get(std::nothrow, "m.room.power_levels", "") } { } ircd::m::room::power::power(const m::room &room, const event::idx &power_event_idx) :room { room } ,power_event_idx { power_event_idx } { } ircd::m::room::power::power(const m::event &power_event, const m::event &create_event) :power { power_event, m::user::id(unquote(json::get<"content"_>(create_event).get("creator"))) } { } ircd::m::room::power::power(const m::event &power_event, const m::user::id &room_creator_id) :power { json::get<"content"_>(power_event), room_creator_id } { } ircd::m::room::power::power(const json::object &power_event_content, const m::user::id &room_creator_id) :power_event_content { power_event_content } ,room_creator_id { room_creator_id } { } /// "all who attain great power and riches make use of either force or fraud" /// /// Returns bool for "allow" or "deny" /// /// Provide the user invoking the power. The return value indicates whether /// they have the power. /// /// Provide the property/event_type. There are two usages here: 1. This is a /// string corresponding to one of the spec top-level properties like "ban" /// and "redact". In this case, the type and state_key parameters to this /// function are not used. 2. This string is empty or "events" in which case /// the type parameter is used to fetch the power threshold for that type. /// For state events of a type, the state_key must be provided for inspection /// here as well. bool ircd::m::room::power::operator()(const m::user::id &user_id, const string_view &prop, const string_view &type, const string_view &state_key) const { const auto &user_level { level_user(user_id) }; const auto &required_level { empty(prop) || prop == "events"? level_event(type, state_key): level(prop) }; return user_level >= required_level; } int64_t ircd::m::room::power::level_user(const m::user::id &user_id) const try { int64_t ret { default_user_level }; const auto closure{[&user_id, &ret] (const json::object &content) { const json::string &users_default { content.get("users_default") }; const json::object &users { content.get("users") }; const json::string &value { users.get(user_id) }; ret = as_level(value, as_level(users_default, default_user_level)); }}; const bool has_power_levels_event { view(closure) }; if(!has_power_levels_event) { if(room_creator_id && user_id == room_creator_id) ret = default_creator_level; if(room.room_id && creator(room, user_id)) ret = default_creator_level; } return ret; } catch(const json::error &e) { log::derror { log, "power users :%s", e.what(), }; return default_user_level; } int64_t ircd::m::room::power::level_event(const string_view &type) const try { int64_t ret { default_event_level }; const auto closure{[&type, &ret] (const json::object &content) { const json::string &events_default { content.get("events_default") }; const json::object &events { content.get("events") }; const json::string &value { events.get(type) }; ret = as_level(value, as_level(events_default, default_event_level)); }}; const bool has_power_levels_event { view(closure) }; return ret; } catch(const json::error &e) { log::derror { log, "power events type=%s :%s", type, e.what(), }; return default_event_level; } int64_t ircd::m::room::power::level_event(const string_view &type, const string_view &state_key) const try { if(!defined(state_key)) return level_event(type); int64_t ret { default_power_level }; const auto closure{[&type, &ret] (const json::object &content) { const json::string &state_default { content.get("state_default") }; const json::object &events { content.get("events") }; const json::string &value { events.get(type) }; ret = as_level(value, as_level(state_default, default_power_level)); }}; const bool has_power_levels_event { view(closure) }; return ret; } catch(const json::error &e) { log::derror { log, "power events type=%s state_key=%s :%s", type, state_key, e.what(), }; return default_power_level; } int64_t ircd::m::room::power::level(const string_view &prop) const try { int64_t ret { default_power_level }; view([&prop, &ret] (const json::object &content) { const json::string &value { content.at(prop) }; ret = as_level(value, ret); }); return ret; } catch(const json::error &e) { log::derror { log, "power level prop=%s :%s", prop, e.what(), }; return prop == "invite"? default_event_level: default_power_level; } size_t ircd::m::room::power::count_levels() const { size_t ret{0}; for_each([&ret] (const string_view &, const int64_t &) noexcept { ++ret; return true; }); return ret; } size_t ircd::m::room::power::count_collections() const { size_t ret{0}; view([&ret] (const json::object &content) { for(const auto &[key, val] : content) ret += json::type(val, json::OBJECT); }); return ret; } size_t ircd::m::room::power::count(const string_view &prop) const { size_t ret{0}; for_each(prop, [&ret] (const string_view &, const int64_t &) noexcept { ++ret; return true; }); return ret; } bool ircd::m::room::power::has_event(const string_view &type) const try { bool ret{false}; view([&type, &ret] (const json::object &content) { const json::object &events { content.at("events") }; const json::string &value { events.at(type) }; ret = is_level(value); }); return ret; } catch(const json::error &) { return false; } bool ircd::m::room::power::has_user(const m::user::id &user_id) const try { bool ret{false}; view([&user_id, &ret] (const json::object &content) { const json::object &users { content.at("users") }; const json::string &value { users.at(user_id) }; ret = is_level(value); }); return ret; } catch(const json::error &) { return false; } bool ircd::m::room::power::has_collection(const string_view &prop) const { bool ret{false}; view([&prop, &ret] (const json::object &content) { const string_view &value { content.get(prop) }; ret = json::type(value, json::OBJECT); }); return ret; } bool ircd::m::room::power::has_level(const string_view &prop) const { bool ret{false}; view([&prop, &ret] (const json::object &content) { const json::string &value { content.get(prop) }; ret = is_level(value); }); return ret; } bool ircd::m::room::power::for_each_collection(const closure &closure) const { bool ret{true}; view([&closure, &ret] (const json::object &content) { for(const auto &[key, val] : content) { if(!json::type(val, json::OBJECT)) continue; if(!closure(key, std::numeric_limits::min())) { ret = false; break; } } }); return ret; } bool ircd::m::room::power::for_each(const closure &closure) const { return for_each(string_view{}, closure); } bool ircd::m::room::power::for_each(const string_view &prop, const closure &closure) const { bool ret{true}; view([&prop, &closure, &ret] (const json::object &content) { const json::object &collection { // This little cmov gimmick sets collection to be the outer object // itself if no property was given, allowing us to reuse this func // for all iterations of key -> level mappings. prop? json::object{content.get(prop)} : content }; const string_view _collection{collection}; if(prop && (!_collection || !json::type(_collection, json::OBJECT))) return; for(auto it(begin(collection)); it != end(collection) && ret; ++it) { const auto &[key, val] { *it }; if(!is_level(val)) continue; ret = closure(key, as_level(val)); } }); return ret; } bool ircd::m::room::power::view(const std::function &closure) const { if(power_event_idx) if(m::get(std::nothrow, power_event_idx, "content", closure)) return true; closure(power_event_content); return !empty(power_event_content); } int64_t ircd::m::room::power::as_level(const json::string &value, const int64_t &default_) noexcept { return is_level(value)? as_level(value): default_; } int64_t ircd::m::room::power::as_level(const json::string &value) { return lex_cast(value); } bool ircd::m::room::power::is_level(const json::string &value) noexcept { return lex_castable(value); }