construct/matrix/room_power.cc

825 lines
14 KiB
C++

// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2019 Jason Volk <jason@zemos.net>
//
// 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<string_view> &prop_key)
:returns<bool>{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<string_view> &prop_key,
const int64_t &level)
:returns<bool>{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<int64_t>::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<void (const json::object &)> &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<int64_t>(value);
}
bool
ircd::m::room::power::is_level(const json::string &value)
noexcept
{
return lex_castable<int64_t>(value);
}