// Matrix Construct // // Copyright (C) Matrix Construct Developers, Authors & Contributors // Copyright (C) 2016-2018 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::mapi::header IRCD_MODULE { "Matrix event library; modular components." }; namespace ircd::m::vm { extern const m::hookfn issue_missing_auth; extern const m::hookfn conform_check_origin; extern const m::hookfn conform_check_size; extern const m::hookfn conform_report; } /// Check if an eval with a copts structure (indicating this server is /// creating the event) has an origin set to !my_host(). decltype(ircd::m::vm::conform_check_origin) ircd::m::vm::conform_check_origin { { { "_site", "vm.conform" } }, [](const m::event &event, eval &eval) { if(unlikely(eval.copts && !my_host(at<"origin"_>(event)))) throw error { fault::GENERAL, "Issuing event for origin: %s", at<"origin"_>(event) }; } }; /// Check if an event originating from this server exceeds maximum size. decltype(ircd::m::vm::conform_check_size) ircd::m::vm::conform_check_size { { { "_site", "vm.conform" }, { "origin", my_host() } }, [](const m::event &event, eval &eval) { const size_t &event_size { serialized(event) }; if(event_size > size_t(event::max_size)) throw m::BAD_JSON { "Event is %zu bytes which is larger than the maximum %zu bytes", event_size, size_t(event::max_size) }; } }; /// Check if an event originating from this server exceeds maximum size. decltype(ircd::m::vm::conform_report) ircd::m::vm::conform_report { { { "_site", "vm.conform" } }, [](const m::event &event, eval &eval) { assert(eval.opts); auto &opts(*eval.opts); // When opts.conformed is set the report is already generated if(opts.conformed) { eval.report = opts.report; return; } // Generate the report here. eval.report = event::conforms { event, opts.non_conform.report }; // When opts.conforming is false a bad report is not an error. if(!opts.conforming) return; // Otherwise this will kill the eval if(!eval.report.clean()) throw error { fault::INVALID, "Non-conforming event: %s", string(eval.report) }; } }; std::ostream & IRCD_MODULE_EXPORT ircd::m::pretty(std::ostream &s, const event &event) { const auto out{[&s] (const string_view &key, auto&& val) { if(defined(json::value(val))) s << std::setw(16) << std::right << key << " :" << val << std::endl; }}; const string_view top_keys[] { "origin", "event_id", "room_id", "sender", "type", "depth", "state_key", "redacts", }; json::for_each(event, top_keys, out); const auto &ts{json::get<"origin_server_ts"_>(event)}; { thread_local char buf[128]; s << std::setw(16) << std::right << "origin_server_ts" << " :" << timef(buf, ts / 1000L, ircd::localtime) << " (" << ts << ")" << std::endl; } const json::object &contents{json::get<"content"_>(event)}; if(!contents.empty()) s << std::setw(16) << std::right << "content" << " :" << size(contents) << " keys; " << size(string_view{contents}) << " bytes." << std::endl; const auto &hashes{json::get<"hashes"_>(event)}; for(const auto &hash : hashes) { s << std::setw(16) << std::right << "[hash]" << " :" << hash.first << " " << unquote(hash.second) << std::endl; } const auto &signatures{json::get<"signatures"_>(event)}; for(const auto &signature : signatures) { s << std::setw(16) << std::right << "[signature]" << " :" << signature.first << " "; for(const auto &key : json::object{signature.second}) s << key.first << " "; s << std::endl; } const auto &auth_events{json::get<"auth_events"_>(event)}; for(const json::array auth_event : auth_events) { s << std::setw(16) << std::right << "[auth event]" << " :" << unquote(auth_event[0]); for(const auto &hash : json::object{auth_event[1]}) s << " " << unquote(hash.first) << ": " << unquote(hash.second); s << std::endl; } const auto &prev_states{json::get<"prev_state"_>(event)}; for(const json::array prev_state : prev_states) { s << std::setw(16) << std::right << "[prev state]" << " :" << unquote(prev_state[0]); for(const auto &hash : json::object{prev_state[1]}) s << " " << unquote(hash.first) << ": " << unquote(hash.second); s << std::endl; } const auto &prev_events{json::get<"prev_events"_>(event)}; for(const json::array prev_event : prev_events) { s << std::setw(16) << std::right << "[prev_event]" << " :" << unquote(prev_event[0]); for(const auto &hash : json::object{prev_event[1]}) s << " " << unquote(hash.first) << ": " << unquote(hash.second); s << std::endl; } if(!contents.empty()) for(const json::object::member &content : contents) s << std::setw(16) << std::right << "[content]" << " :" << std::setw(7) << std::left << reflect(json::type(content.second)) << " " << std::setw(5) << std::right << size(string_view{content.second}) << " bytes " << ':' << content.first << std::endl; return s; } std::ostream & IRCD_MODULE_EXPORT ircd::m::pretty_oneline(std::ostream &s, const event &event, const bool &content_keys) { if(defined(json::get<"room_id"_>(event))) s << json::get<"room_id"_>(event) << " "; else s << "* "; if(json::get<"depth"_>(event) != json::undefined_number) s << json::get<"depth"_>(event) << " "; else s << "* "; if(json::get<"origin_server_ts"_>(event) != json::undefined_number) s << json::get<"origin_server_ts"_>(event) << " "; else s << "* "; if(defined(json::get<"origin"_>(event))) s << ':' << json::get<"origin"_>(event) << " "; else s << ":* "; if(defined(json::get<"sender"_>(event))) s << json::get<"sender"_>(event) << " "; else s << "* "; if(defined(json::get<"event_id"_>(event))) s << json::get<"event_id"_>(event) << " "; else s << "* "; const auto &auth_events{json::get<"auth_events"_>(event)}; s << "A:" << auth_events.count() << " "; const auto &prev_states{json::get<"prev_state"_>(event)}; s << "S:" << prev_states.count() << " "; const auto &prev_events{json::get<"prev_events"_>(event)}; s << "E:" << prev_events.count() << " "; const auto &hashes{json::get<"hashes"_>(event)}; s << "[ "; for(const auto &hash : hashes) s << hash.first << " "; s << "] "; const auto &signatures{json::get<"signatures"_>(event)}; s << "[ "; for(const auto &signature : signatures) { s << signature.first << "[ "; for(const auto &key : json::object{signature.second}) s << key.first << " "; s << "] "; } s << "] "; if(defined(json::get<"type"_>(event))) s << json::get<"type"_>(event) << " "; else s << "* "; const auto &state_key { json::get<"state_key"_>(event) }; if(defined(state_key) && empty(state_key)) s << "\"\"" << " "; else if(defined(state_key)) s << state_key << " "; else s << "*" << " "; const string_view &membership { json::get<"type"_>(event) == "m.room.member"? m::membership(event): "*"_sv }; s << membership << " "; if(defined(json::get<"redacts"_>(event))) s << json::get<"redacts"_>(event) << " "; else s << "* "; const json::object &contents { content_keys? json::get<"content"_>(event): json::object{} }; if(!contents.empty()) { s << "+" << string_view{contents}.size() << " bytes :"; for(const auto &content : contents) s << content.first << " "; } return s; } std::ostream & IRCD_MODULE_EXPORT ircd::m::pretty_msgline(std::ostream &s, const event &event) { s << json::get<"depth"_>(event) << " :"; s << json::get<"type"_>(event) << " "; s << json::get<"sender"_>(event) << " "; s << json::get<"event_id"_>(event) << " "; const auto &state_key { json::get<"state_key"_>(event) }; if(defined(state_key) && empty(state_key)) s << "\"\"" << " "; else if(defined(state_key)) s << state_key << " "; else s << "*" << " "; const json::object &content { json::get<"content"_>(event) }; switch(hash(json::get<"type"_>(event))) { case "m.room.message"_: s << unquote(content.get("msgtype")) << " "; s << unquote(content.get("body")) << " "; break; default: s << string_view{content}; break; } return s; } std::ostream & IRCD_MODULE_EXPORT ircd::m::pretty(std::ostream &s, const event::prev &prev) { const auto out{[&s] (const string_view &key, auto&& val) { if(json::defined(val)) s << key << " :" << val << std::endl; }}; const auto &auth_events{json::get<"auth_events"_>(prev)}; for(const json::array auth_event : auth_events) { s << std::setw(16) << std::right << "[auth event]" << " :" << unquote(auth_event[0]); for(const auto &hash : json::object{auth_event[1]}) s << " " << unquote(hash.first) << ": " << unquote(hash.second); s << std::endl; } const auto &prev_states{json::get<"prev_state"_>(prev)}; for(const json::array prev_state : prev_states) { s << std::setw(16) << std::right << "[prev state]" << " :" << unquote(prev_state[0]); for(const auto &hash : json::object{prev_state[1]}) s << " " << unquote(hash.first) << ": " << unquote(hash.second); s << std::endl; } const auto &prev_events{json::get<"prev_events"_>(prev)}; for(const json::array prev_event : prev_events) { s << std::setw(16) << std::right << "[prev_event]" << " :" << unquote(prev_event[0]); for(const auto &hash : json::object{prev_event[1]}) s << " " << unquote(hash.first) << ": " << unquote(hash.second); s << std::endl; } return s; } std::ostream & IRCD_MODULE_EXPORT ircd::m::pretty_oneline(std::ostream &s, const event::prev &prev) { const auto &auth_events{json::get<"auth_events"_>(prev)}; s << "A[ "; for(const json::array auth_event : auth_events) s << unquote(auth_event[0]) << " "; s << "] "; const auto &prev_states{json::get<"prev_state"_>(prev)}; s << "S[ "; for(const json::array prev_state : prev_states) s << unquote(prev_state[0]) << " "; s << "] "; const auto &prev_events{json::get<"prev_events"_>(prev)}; s << "E[ "; for(const json::array prev_event : prev_events) s << unquote(prev_event[0]) << " "; s << "] "; return s; } void IRCD_MODULE_EXPORT ircd::m::append(json::stack::object &object, const event &event, const event_append_opts &opts) { const bool has_event_idx { opts.event_idx && *opts.event_idx }; const bool has_client_txnid { opts.client_txnid && *opts.client_txnid }; const bool has_user { opts.user_id && opts.user_room }; const bool sender_is_user { has_user && json::get<"sender"_>(event) == *opts.user_id }; const auto txnid_idx { !has_client_txnid && sender_is_user && opts.query_txnid? opts.user_room->get(std::nothrow, "ircd.client.txnid", at<"event_id"_>(event)): 0UL }; #if defined(RB_DEBUG) && 0 if(!has_client_txnid && !txnid_idx && sender_is_user && opts.query_txnid) log::dwarning { log, "Could not find transaction_id for %s from %s in %s", json::get<"event_id"_>(event), json::get<"sender"_>(event), json::get<"room_id"_>(event) }; #endif object.append(event); if(json::get<"state_key"_>(event) && has_event_idx) { const auto prev_idx { room::state::prev(*opts.event_idx) }; if(prev_idx) m::get(std::nothrow, prev_idx, "content", [&object] (const json::object &content) { json::stack::member { object, "prev_content", content }; }); } json::stack::object unsigned_ { object, "unsigned" }; json::stack::member { unsigned_, "age", json::value { has_event_idx && opts.age != std::numeric_limits::min()? long(vm::current_sequence - *opts.event_idx): opts.age != std::numeric_limits::min()? opts.age: 0L } }; if(has_client_txnid) json::stack::member { unsigned_, "transaction_id", *opts.client_txnid }; if(txnid_idx) m::get(std::nothrow, txnid_idx, "content", [&unsigned_] (const json::object &content) { json::stack::member { unsigned_, "transaction_id", unquote(content.get("transaction_id")) }; }); } bool IRCD_MODULE_EXPORT ircd::m::event::auth::chain::for_each(const auth::chain &c, const closure_bool &closure) { m::event::fetch e, a; std::set ae; std::deque aq {c.idx}; do { const auto idx(aq.front()); aq.pop_front(); if(!seek(e, idx, std::nothrow)) continue; const m::event::prev prev{e}; const auto count(prev.auth_events_count()); for(size_t i(0); i < count && i < 4; ++i) { const auto &auth_event_id(prev.auth_event(i)); const auto &auth_event_idx(index(auth_event_id, std::nothrow)); if(!auth_event_idx) continue; auto it(ae.lower_bound(auth_event_idx)); if(it == end(ae) || *it != auth_event_idx) { seek(a, auth_event_idx, std::nothrow); ae.emplace_hint(it, auth_event_idx); if(a.valid) aq.emplace_back(auth_event_idx); } } } while(!aq.empty()); for(const auto &idx : ae) if(!closure(idx)) return false; return true; } void IRCD_MODULE_EXPORT ircd::m::event::refs::rebuild() { static const size_t pool_size{96}; static const size_t log_interval{8192}; db::txn txn { *m::dbs::events }; auto &column { dbs::event_json }; auto it { column.begin() }; ctx::dock dock; ctx::pool pool; pool.min(pool_size); size_t i(0), j(0); const ctx::uninterruptible::nothrow ui; for(; it; ++it) { if(ctx::interruption_requested()) break; const m::event::idx event_idx { byte_view(it->first) }; std::string event{it->second}; pool([&txn, &dock, &i, &j, event(std::move(event)), event_idx] { m::dbs::write_opts wopts; wopts.event_idx = event_idx; m::dbs::_index_event_refs(txn, json::object{event}, wopts); if(++j % log_interval == 0) log::info { m::log, "Refs builder @%zu:%zu of %lu (@idx: %lu)", i, j, m::vm::current_sequence, event_idx }; if(j >= i) dock.notify_one(); }); ++i; } dock.wait([&i, &j] { return i == j; }); txn(); } ircd::string_view IRCD_MODULE_EXPORT ircd::m::event::auth::failed(const m::event &event) { const m::event::prev refs(event); const auto count(refs.auth_events_count()); if(count > 4) return "Too many auth_events references."; const m::event *authv[4]; const m::event::fetch auth[4] { { count > 0? refs.auth_event(0) : event::id{}, std::nothrow }, { count > 1? refs.auth_event(1) : event::id{}, std::nothrow }, { count > 2? refs.auth_event(2) : event::id{}, std::nothrow }, { count > 3? refs.auth_event(3) : event::id{}, std::nothrow }, }; size_t i(0), j(0); for(; i < 4; ++i) if(auth[i].valid) authv[j++] = &auth[i]; return failed(event, {authv, j}); } ircd::string_view IRCD_MODULE_EXPORT ircd::m::event::auth::failed(const m::event &event, const vector_view &auth_events) { const m::event::prev refs{event}; // 1. If type is m.room.create if(json::get<"type"_>(event) == "m.room.create") { // a. If it has any previous events, reject. if(count(refs) || !empty(auth_events)) return "m.room.create has previous events."; // b. If the domain of the room_id does not match the domain of the // sender, reject. assert(!conforms(event).has(conforms::MISMATCH_CREATE_SENDER)); if(conforms(event).has(conforms::MISMATCH_CREATE_SENDER)) return "m.room.create room_id domain does not match sender domain."; // c. If content.room_version is present and is not a recognised // version, reject. if(json::get<"content"_>(event).has("room_version")) if(unquote(json::get<"content"_>(event).get("room_version")) != "1") return "m.room.create room_version is not recognized."; // d. If content has no creator field, reject. assert(!empty(json::get<"content"_>(event).get("creator"))); if(empty(json::get<"content"_>(event).get("creator"))) return "m.room.create content.creator is missing."; // e. Otherwise, allow. return {}; } // 2. Reject if event has auth_events that: const m::event *auth_create{nullptr}; const m::event *auth_power{nullptr}; const m::event *auth_join_rules{nullptr}; const m::event *auth_member_target{nullptr}; const m::event *auth_member_sender{nullptr}; for(size_t i(0); i < auth_events.size(); ++i) { // a. have duplicate entries for a given type and state_key pair assert(auth_events.at(i)); const m::event &a(*auth_events.at(i)); for(size_t j(0); j < auth_events.size(); ++j) if(i != j) { assert(auth_events.at(j)); const auto &b(*auth_events.at(j)); if(json::get<"type"_>(a) == json::get<"type"_>(b)) if(json::get<"state_key"_>(a) == json::get<"state_key"_>(b)) return "Duplicate (type,state_key) in auth_events."; } // b. have entries whose type and state_key don't match those specified by // the auth events selection algorithm described in the server... const auto &type(json::get<"type"_>(a)); if(type == "m.room.create") { assert(!auth_create); auth_create = &a; continue; } if(type == "m.room.power_levels") { assert(!auth_power); auth_power = &a; continue; } if(type == "m.room.join_rules") { assert(!auth_join_rules); auth_join_rules = &a; continue; } if(type == "m.room.member") { if(json::get<"sender"_>(event) == json::get<"state_key"_>(a)) { assert(!auth_member_sender); auth_member_sender = &a; } if(json::get<"state_key"_>(event) == json::get<"state_key"_>(a)) { assert(!auth_member_target); auth_member_target = &a; } continue; } return "Reference in auth_events is not an auth_event."; } // 3. If event does not have a m.room.create in its auth_events, reject. if(!auth_create) return "Missing m.room.create in auth_events"; const m::room::power power { auth_power? *auth_power : m::event{}, *auth_create }; // 4. If type is m.room.aliases: if(json::get<"type"_>(event) == "m.room.aliases") { // a. If event has no state_key, reject. assert(!conforms(event).has(conforms::MISMATCH_ALIASES_STATE_KEY)); if(empty(json::get<"state_key"_>(event))) return "m.room.aliases event is missing a state_key."; // b. If sender's domain doesn't matches state_key, reject. if(json::get<"state_key"_>(event) != m::user::id(json::get<"sender"_>(event)).host()) return "m.room.aliases event state_key is not the sender's domain."; // c. Otherwise, allow return {}; } // 5. If type is m.room.member: if(json::get<"type"_>(event) == "m.room.member") { // a. If no state_key key ... assert(!conforms(event).has(conforms::MISSING_MEMBER_STATE_KEY)); if(empty(json::get<"state_key"_>(event))) return "m.room.member event is missing a state_key."; // a. ... or membership key in content, reject. assert(!conforms(event).has(conforms::MISSING_CONTENT_MEMBERSHIP)); if(empty(unquote(json::get<"content"_>(event).get("membership")))) return "m.room.member event is missing a content.membership."; assert(!conforms(event).has(conforms::INVALID_MEMBER_STATE_KEY)); if(!valid(m::id::USER, json::get<"state_key"_>(event))) return "m.room.member event state_key is not a valid user mxid."; // b. If membership is join if(membership(event) == "join") { // i. If the only previous event is an m.room.create and the // state_key is the creator, allow. if(refs.prev_events_count() == 1 && refs.auth_events_count() == 1) if(auth_create && at<"event_id"_>(*auth_create) == refs.prev_event(0)) return {}; // ii. If the sender does not match state_key, reject. if(json::get<"sender"_>(event) != json::get<"state_key"_>(event)) return "m.room.member membership=join sender does not match state_key."; // iii. If the sender is banned, reject. if(auth_member_target) if(membership(*auth_member_target) == "ban") return "m.room.member membership=join references membership=ban auth_event."; if(auth_member_sender) if(membership(*auth_member_sender) == "ban") return "m.room.member membership=join references membership=ban auth_event."; if(auth_join_rules) { // iv. If the join_rule is invite then allow if membership state // is invite or join. if(unquote(json::get<"content"_>(*auth_join_rules).get("join_rule")) == "invite") if(auth_member_target) { if(membership(*auth_member_target) == "invite") return {}; if(membership(*auth_member_target) == "join") return {}; } // v. If the join_rule is public, allow. if(unquote(json::get<"content"_>(*auth_join_rules).get("join_rule")) == "public") return {}; } // vi. Otherwise, reject. return "m.room.member membership=join fails authorization."; } // c. If membership is invite if(membership(event) == "invite") { // i. If content has third_party_invite key //TODO: XXX // ii. If the sender's current membership state is not join, reject. if(auth_member_sender) if(membership(*auth_member_sender) != "join") return "m.room.member membership=invite sender must have membership=join."; // iii. If target user's current membership state is join or ban, reject. if(auth_member_target) { if(membership(*auth_member_target) == "join") return "m.room.member membership=invite target cannot have membership=join."; if(membership(*auth_member_target) == "ban") return "m.room.member membership=invite target cannot have membership=ban."; } // iv. If the sender's power level is greater than or equal to the invite level, // allow. if(power(at<"sender"_>(event), "invite")) return {}; // v. Otherwise, reject. return "m.room.member membership=invite fails authorization."; } // d. If membership is leave if(membership(event) == "leave") { // i. If the sender matches state_key, allow if and only if that // user's current membership state is invite or join. if(json::get<"sender"_>(event) == json::get<"state_key"_>(event)) { if(membership(*auth_member_target) == "join") return {}; if(membership(*auth_member_target) == "invite") return {}; return "m.room.member membership=leave self-target must have membership=join|invite."; } // ii. If the sender's current membership state is not join, reject. if(auth_member_sender) if(membership(*auth_member_sender) != "join") return "m.room.member membership=leave sender must have membership=join."; // iii. If the target user's current membership state is ban, and the sender's // power level is less than the ban level, reject. if(auth_member_target) if(membership(*auth_member_target) == "ban") if(!power(at<"sender"_>(event), "ban")) return "m.room.member membership=ban->leave sender must have ban power to unban."; // iv. If the sender's power level is greater than or equal to the // kick level, and the target user's power level is less than the // sender's power level, allow. if(power(at<"sender"_>(event), "kick")) if(power.level_user(at<"state_key"_>(event)) < power.level_user(at<"sender"_>(event))) return {}; // v. Otherwise, reject. return "m.room.member membership=leave fails authorization."; } // e. If membership is ban if(membership(event) == "ban") { // i. If the sender's current membership state is not join, reject. if(auth_member_sender) if(membership(*auth_member_sender) != "join") return "m.room.member membership=ban sender must have membership=join."; // ii. If the sender's power level is greater than or equal to the // ban level, and the target user's power level is less than the // sender's power level, allow. if(power(at<"sender"_>(event), "ban")) if(power.level_user(at<"state_key"_>(event)) < power.level_user(at<"sender"_>(event))) return {}; // iii. Otherwise, reject. return "m.room.member membership=ban fails authorization."; } // f. Otherwise, the membership is unknown. Reject. return "m.room.member membership=unknown."; } // 6. If the sender's current membership state is not join, reject. if(auth_member_sender) if(membership(*auth_member_sender) != "join") return "sender is not joined to room."; // 7. If type is m.room.third_party_invite: if(json::get<"type"_>(event) == "m.room.third_party_invite") { //TODO: XXX } // 8. If the event type's required power level is greater than the // sender's power level, reject. if(!power(at<"sender"_>(event), "events", at<"type"_>(event))) return "sender has insufficient power for event type."; // 9. If the event has a state_key that starts with an @ and does not // match the sender, reject. if(startswith(json::get<"state_key"_>(event), '@')) if(at<"state_key"_>(event) != at<"sender"_>(event)) return "sender cannot set another user's mxid in a state_key."; // 10. If type is m.room.power_levels: if(json::get<"type"_>(event) == "m.room.power_levels") { // a. If users key in content is not a dictionary with keys that are // valid user IDs with values that are integers (or a string that is // an integer), reject. if(json::type(json::get<"content"_>(event).get("users")) != json::OBJECT) return "m.room.power_levels content.users is not a json object."; for(const auto &member : json::object(at<"content"_>(event).at("users"))) { if(!m::valid(m::id::USER, member.first)) return "m.room.power_levels content.users key is not a user mxid"; if(!try_lex_cast(unquote(member.second))) return "m.room.power_levels content.users value is not an integer."; } // b. If there is no previous m.room.power_levels event in the room, allow. if(!auth_power) return {}; const m::room::power old_power{*auth_power, *auth_create}; const m::room::power new_power{event, *auth_create}; const int64_t current_level { old_power.level_user(at<"sender"_>(event)) }; // c. For each of the keys users_default, events_default, // state_default, ban, redact, kick, invite, as well as each entry // being changed under the events or users keys: static const string_view keys[] { "users_default", "events_default", "state_default", "ban", "redact", "kick", "invite", }; for(const auto &key : keys) { const int64_t old_level(old_power.level(key)); const int64_t new_level(new_power.level(key)); if(old_level == new_level) continue; // i. If the current value is higher than the sender's current // power level, reject. if(old_level > current_level) return "m.room.power_levels property denied to sender's power level."; // ii. If the new value is higher than the sender's current power // level, reject. if(new_level > current_level) return "m.room.power_levels property exceeds sender's power level."; } string_view ret; using closure = m::room::power::closure_bool; old_power.for_each("users", closure{[&ret, &new_power, ¤t_level] (const string_view &user_id, const int64_t &old_level) { if(new_power.has_user(user_id)) if(new_power.level_user(user_id) == old_level) return true; // i. If the current value is higher than the sender's current // power level, reject. if(old_level > current_level) { ret = "m.room.power_levels user property denied to sender's power level."; return false; }; // ii. If the new value is higher than the sender's current power // level, reject. if(new_power.level_user(user_id) > current_level) { ret = "m.room.power_levels user property exceeds sender's power level."; return false; }; return true; }}); if(ret) return ret; new_power.for_each("users", closure{[&ret, &old_power, ¤t_level] (const string_view &user_id, const int64_t &new_level) { if(old_power.has_user(user_id)) if(old_power.level_user(user_id) == new_level) return true; // i. If the current value is higher than the sender's current // power level, reject. if(new_level > current_level) { ret = "m.room.power_levels user property exceeds sender's power level."; return false; }; return true; }}); if(ret) return ret; old_power.for_each("events", closure{[&ret, &new_power, ¤t_level] (const string_view &type, const int64_t &old_level) { if(new_power.has_event(type)) if(new_power.level_event(type) == old_level) return true; // i. If the current value is higher than the sender's current // power level, reject. if(old_level > current_level) { ret = "m.room.power_levels event property denied to sender's power level."; return false; }; // ii. If the new value is higher than the sender's current power // level, reject. if(new_power.level_event(type) > current_level) { ret = "m.room.power_levels event property exceeds sender's power level."; return false; }; return true; }}); if(ret) return ret; new_power.for_each("events", closure{[&ret, &old_power, ¤t_level] (const string_view &type, const int64_t &new_level) { if(old_power.has_event(type)) if(old_power.level_event(type) == new_level) return true; // i. If the current value is higher than the sender's current // power level, reject. if(new_level > current_level) { ret = "m.room.power_levels event property exceeds sender's power level."; return false; }; return true; }}); // d. For each entry being changed under the users key... old_power.for_each("users", closure{[&ret, &event, &new_power, ¤t_level] (const string_view &user_id, const int64_t &old_level) { // ...other than the sender's own entry: if(user_id == at<"sender"_>(event)) return true; if(new_power.has_user(user_id)) if(new_power.level_user(user_id) == old_level) return true; // i. If the current value is equal to the sender's current power // level, reject. if(old_level == current_level) { ret = "m.room.power_levels user property denied to sender's power level."; return false; }; return true; }}); if(ret) return ret; // e. Otherwise, allow. assert(!ret); return ret; } // 11. If type is m.room.redaction: if(json::get<"type"_>(event) == "m.room.redaction") { // a. If the sender's power level is greater than or equal to the // redact level, allow. if(power(at<"sender"_>(event), "redact")) return {}; // b. If the domain of the event_id of the event being redacted is the // same as the domain of the event_id of the m.room.redaction, allow. if(event::id(json::get<"redacts"_>(event)).host() == event::id(at<"event_id"_>(event)).host()) return {}; // c. Otherwise, reject. return "m.room.redaction fails authorization."; } // 12. Otherwise, allow. return {}; } bool IRCD_MODULE_EXPORT ircd::m::event::auth::is_power_event(const m::event &event) { if(json::get<"type"_>(event) == "m.room.create") return true; if(json::get<"type"_>(event) == "m.room.power_levels") return true; if(json::get<"type"_>(event) == "m.room.join_rules") return true; if(json::get<"type"_>(event) != "m.room.member") return false; if(at<"sender"_>(event) == at<"state_key"_>(event)) return false; if(membership(event) == "leave" || membership(event) == "ban") return true; return false; } void IRCD_MODULE_EXPORT ircd::m::event::essential(json::iov &event, const json::iov &contents, const event::closure_iov_mutable &closure) { const auto &type { event.at("type") }; if(type == "m.room.aliases") { const json::iov::push _content{event, { "content", json::members { { "aliases", contents.at("aliases") } } }}; closure(event); } else if(type == "m.room.create") { const json::iov::push _content{event, { "content", json::members { { "creator", contents.at("creator") } } }}; closure(event); } else if(type == "m.room.history_visibility") { const json::iov::push _content{event, { "content", json::members { { "history_visibility", contents.at("history_visibility") } } }}; closure(event); } else if(type == "m.room.join_rules") { const json::iov::push _content{event, { "content", json::members { { "join_rule", contents.at("join_rule") } } }}; closure(event); } else if(type == "m.room.member") { const json::iov::push _content{event, { "content", json::members { { "membership", contents.at("membership") } } }}; closure(event); } else if(type == "m.room.power_levels") { const json::iov::push _content{event, { "content", json::members { { "ban", contents.at("ban") }, { "events", contents.at("events") }, { "events_default", contents.at("events_default") }, { "kick", contents.at("kick") }, { "redact", contents.at("redact") }, { "state_default", contents.at("state_default") }, { "users", contents.at("users") }, { "users_default", contents.at("users_default") }, } }}; closure(event); } else if(type == "m.room.redaction") { // This simply finds the redacts key and swaps it with jsundefined for // the scope's duration. The redacts key will still be present and // visible in the json::iov which is incorrect if directly serialized. // However, this iov is turned into a json::tuple (m::event) which ends // up being serialized for signing. That serialization is where the // jsundefined redacts value is ignored. auto &redacts{event.at("redacts")}; json::value temp(std::move(redacts)); redacts = json::value{}; const unwind _{[&redacts, &temp] { redacts = std::move(temp); }}; const json::iov::push _content { event, { "content", "{}" } }; closure(event); } else { const json::iov::push _content { event, { "content", "{}" } }; closure(event); } } ircd::m::event IRCD_MODULE_EXPORT ircd::m::essential(m::event event, const mutable_buffer &contentbuf) { const auto &type { json::at<"type"_>(event) }; json::object &content { json::get<"content"_>(event) }; mutable_buffer essential { contentbuf }; if(type == "m.room.aliases") { content = json::stringify(essential, json::members { { "aliases", unquote(content.at("aliases")) } }); } else if(type == "m.room.create") { content = json::stringify(essential, json::members { { "creator", unquote(content.at("creator")) } }); } else if(type == "m.room.history_visibility") { content = json::stringify(essential, json::members { { "history_visibility", unquote(content.at("history_visibility")) } }); } else if(type == "m.room.join_rules") { content = json::stringify(essential, json::members { { "join_rule", unquote(content.at("join_rule")) } }); } else if(type == "m.room.member") { content = json::stringify(essential, json::members { { "membership", unquote(content.at("membership")) } }); } else if(type == "m.room.power_levels") { content = json::stringify(essential, json::members { { "ban", unquote(content.at("ban")) }, { "events", unquote(content.at("events")) }, { "events_default", unquote(content.at("events_default")) }, { "kick", unquote(content.at("kick")) }, { "redact", unquote(content.at("redact")) }, { "state_default", unquote(content.at("state_default")) }, { "users", unquote(content.at("users")) }, { "users_default", unquote(content.at("users_default")) }, }); } else if(type == "m.room.redaction") { json::get<"redacts"_>(event) = string_view{}; content = "{}"_sv; } else { content = "{}"_sv; } json::get<"signatures"_>(event) = {}; return event; }