Compare commits

...

32 Commits

Author SHA1 Message Date
Jason Volk dabc8b4304 modules/federation/user_keys_query: Cross signatures. 2023-04-22 22:04:59 -07:00
Jason Volk d80f29b65a ircd::json::stack::member: Conversion constructions from other member categories. 2023-04-22 21:44:55 -07:00
Jason Volk 6c3420afbc modules/client/sync/device_lists: Fix structure; indicate changes to own device. 2023-04-22 00:21:29 -07:00
Jason Volk 6072229dcc modules/client/sync: Stub device_unused_fallback_key_types. 2023-04-22 00:21:29 -07:00
Jason Volk 956aa7682f ircd::net::dns::resolver: Fix case sensitivity of the tag host. 2023-04-22 00:21:29 -07:00
Jason Volk 238cc10489 ircd:Ⓜ️:rooms: Implement matrix-org/matrix-spec-proposals#3827 2023-04-21 21:40:50 -07:00
Jason Volk 1a032b28b7 ircd:Ⓜ️:room: Add boolean query for room type. 2023-04-21 21:40:50 -07:00
Jason Volk f08b9e85cd ircd:Ⓜ️:createroom: Bump the default version. 2023-04-21 21:40:50 -07:00
Jason Volk 9ce44aadd5 modules/client/devices: Add de facto user_id to response. 2023-04-21 17:45:57 -07:00
Jason Volk 78d6e4ce03 modules/client/capabilities: List additional capabilities. 2023-04-21 17:42:27 -07:00
Jason Volk 882629ab53 modules/federation/user_devices: Fix property name; additional for the loopback. 2023-04-21 17:42:27 -07:00
Jason Volk 8ade7c814e modules/client/room_keys: Add missing count and etag to responses. 2023-04-20 18:22:02 -07:00
Jason Volk 4d5d99ab2c modules/client/room_keys: Unify version and keys into single module. 2023-04-20 16:51:09 -07:00
Jason Volk 502a03a8cf modules/console: Add redact state convenience; fix missing newline. 2023-04-20 13:34:47 -07:00
Jason Volk 1e42fa16a7 modules/client/room_keys/keys: Add missing DELETE for all rooms and sessions. 2023-04-20 13:34:47 -07:00
Jason Volk 0a151a3a90 modules/client/room_keys/keys: Fix sessions request and response structure.
closes #142
2023-04-20 13:34:44 -07:00
Jason Volk fa46231e3a modules/client/room_keys/keys: Add util to split state_key parts. 2023-04-20 13:34:23 -07:00
Jason Volk 336026dde6 modules/client/keys/signatures/upload: Fix inhibition to interactive verification.
closes #162
2023-04-20 13:33:08 -07:00
Jason Volk d5df04a183 modules/federation/user_keys_query: Add missing user_signing_keys on the loopback. 2023-04-20 00:54:01 -07:00
Jason Volk 080748f0af modules/client/keys/signatures/upload: Fix POST interpretation. 2023-04-19 15:01:24 -07:00
Jason Volk 321ea3d641 modules/client/room_keys/version: Implement missing PUT replacement functionality. 2023-04-19 15:01:24 -07:00
Jason Volk d41926bc6f modules/client/versions: Add more version. 2023-04-19 14:56:11 -07:00
Jason Volk 58476f59c3 modules/console: Overload parameters of room events cmd to filter by type. 2023-04-19 14:56:11 -07:00
Jason Volk 8dc50db2dc modules/console: Add user devices delete cmd. 2023-04-19 14:56:11 -07:00
Jason Volk 2122412c6d modules/console: Split user tokens clear into separate cmd. 2023-04-19 14:56:11 -07:00
Jason Volk 15cb6bfdca ircd:Ⓜ️🆔 de facto device id. 2023-04-19 14:56:11 -07:00
Jason Volk fa0365fa31 ircd:Ⓜ️ Add convenience interface for querying m.replace relation. 2023-04-18 20:11:29 -07:00
Jason Volk 02e09728a5 ircd::rest: Add option to supply temporary/headers buffer in lieu of allocating. 2023-04-18 20:11:29 -07:00
Jason Volk dc13381822 ircd::json: Reduce replace() overloads to single linked procedure. 2023-04-18 20:11:29 -07:00
Jason Volk 22b9cf515c ircd::json: Add overload to insert multiple members. 2023-04-18 19:43:33 -07:00
Jason Volk a10992b813 ircd::json: Split back into tool.h 2023-04-18 19:43:33 -07:00
Jason Volk c77df219b5 ircd::json: Optimize json::type() ABI.
ircd::json: Fix indentation; minor cleanup.
2023-04-17 19:31:44 -07:00
37 changed files with 1050 additions and 229 deletions

View File

@ -39,20 +39,7 @@ namespace ircd::json
#include "strung.h"
#include "tuple/tuple.h"
#include "stack/stack.h"
// Convenience toolset for higher level operations.
namespace ircd::json
{
strung append(const array &, const string_view &val);
strung prepend(const array &, const string_view &val);
void merge(stack::object &out, const vector &);
strung remove(const object &, const string_view &key);
strung remove(const object &, const size_t &index);
strung insert(const object &, const member &);
strung replace(const object &, const member &);
strung replace(const object &, const members &);
}
#include "tool.h"
// Exports to ircd::
namespace ircd

View File

@ -41,6 +41,10 @@ struct ircd::json::stack::member
member(stack &s, const string_view &name, const json::value &);
template<class... T> member(object &po, const string_view &name, const json::tuple<T...> &t);
template<class... T> member(stack &s, const string_view &name, const json::tuple<T...> &t);
explicit member(object &, const json::object::member &);
explicit member(stack &, const json::object::member &);
explicit member(object &, const json::member &);
explicit member(stack &, const json::member &);
member() = default;
member(const member &) = delete;
member(member &&) noexcept;

View File

@ -105,28 +105,28 @@ template<>
inline ircd::json::stack::array &
ircd::json::stack::stack::top<ircd::json::stack::array>(stack &s)
{
return array::top(s);
return array::top(s);
}
template<>
inline const ircd::json::stack::array &
ircd::json::stack::stack::top<ircd::json::stack::array>(const stack &s)
{
return array::top(s);
return array::top(s);
}
template<>
inline ircd::json::stack::object &
ircd::json::stack::stack::top<ircd::json::stack::object>(stack &s)
{
return object::top(s);
return object::top(s);
}
template<>
inline const ircd::json::stack::object &
ircd::json::stack::stack::top<ircd::json::stack::object>(const stack &s)
{
return object::top(s);
return object::top(s);
}
template<>

44
include/ircd/json/tool.h Normal file
View File

@ -0,0 +1,44 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 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.
#pragma once
#define HAVE_IRCD_JSON_TOOL_H
// Convenience toolset for higher level operations.
namespace ircd::json
{
strung append(const array &, const string_view &val);
strung prepend(const array &, const string_view &val);
void merge(stack::object &out, const vector &);
strung remove(const object &, const string_view &key);
strung remove(const object &, const size_t &index);
strung insert(const object &, const members &);
strung insert(const object &, const member &);
strung replace(const object &, const members &);
strung replace(const object &, const member &);
}
inline ircd::json::strung
ircd::json::replace(const object &s,
const json::member &m)
{
return replace(s, json::members{m});
}
inline ircd::json::strung
ircd::json::insert(const object &s,
const json::member &m)
{
return insert(s, json::members{m});
}

View File

@ -29,26 +29,18 @@ namespace ircd::json
/// not use the strict overload.
IRCD_OVERLOAD(strict)
// Determine the type
enum type type(const string_view &);
enum type type(const string_view &, std::nothrow_t);
enum type type(const string_view &, strict_t);
enum type type(const string_view &, strict_t, std::nothrow_t);
// Query if type
bool type(const string_view &, const enum type &, strict_t);
bool type(const string_view &, const enum type &);
// Utils
string_view reflect(const enum type &);
[[gnu::pure]] string_view reflect(const enum type) noexcept;
extern const string_view literal_null;
extern const string_view literal_true;
extern const string_view literal_false;
extern const string_view empty_string;
extern const string_view empty_object;
extern const string_view empty_array;
extern const int64_t undefined_number;
// Determine the type w/ strict correctness (full scan)
[[gnu::pure]] bool type(const string_view &, const enum type, strict_t) noexcept;
[[gnu::pure]] enum type type(const string_view &, strict_t, std::nothrow_t) noexcept;
enum type type(const string_view &, strict_t);
// Determine the type quickly
[[gnu::pure]] bool type(const string_view &, const enum type) noexcept;
[[gnu::pure]] enum type type(const string_view &, std::nothrow_t) noexcept;
enum type type(const string_view &);
}
enum ircd::json::type

View File

@ -36,7 +36,15 @@ namespace ircd::json
void valid(const string_view &);
std::string why(const string_view &);
struct stats extern stats;
extern const string_view literal_null;
extern const string_view literal_true;
extern const string_view literal_false;
extern const string_view empty_string;
extern const string_view empty_object;
extern const string_view empty_array;
extern const int64_t undefined_number;
extern struct stats stats;
}
/// Statistics counter access; unfortunately these cannot participate as

View File

@ -89,6 +89,7 @@ namespace ircd::m
#include "direct_to_device.h"
#include "visible.h"
#include "redacted.h"
#include "replaced.h"
#include "feds.h"
#include "app.h"
#include "bridge.h"

80
include/ircd/m/replaced.h Normal file
View File

@ -0,0 +1,80 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 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.
#pragma once
#define HAVE_IRCD_M_REPLACED_H
namespace ircd::m
{
struct replaced;
}
class ircd::m::replaced
:public returns<event::idx>
{
m::relates relates;
public:
IRCD_OVERLOAD(latest);
replaced(const event::idx &, latest_t);
replaced(const event::idx &);
replaced(const event::id &, latest_t);
replaced(const event::id &);
explicit replaced(const event &, latest_t);
explicit replaced(const event &);
};
inline
ircd::m::replaced::replaced(const event &event)
:replaced{event.event_id}
{}
inline
ircd::m::replaced::replaced(const event &event,
latest_t)
:replaced{event.event_id, latest}
{}
inline
ircd::m::replaced::replaced(const event::id &event_id)
:replaced{index(std::nothrow, event_id)}
{}
inline
ircd::m::replaced::replaced(const event::id &event_id,
latest_t)
:replaced{index(std::nothrow, event_id), latest}
{}
inline
ircd::m::replaced::replaced(const event::idx &event_idx)
:relates
{
.refs = event_idx,
.match_sender = true,
}
{
this->returns::ret = relates.has("m.replace")? -1UL: 0UL;
}
inline
ircd::m::replaced::replaced(const event::idx &event_idx,
latest_t)
:relates
{
.refs = event_idx,
.match_sender = true,
}
{
this->returns::ret = relates.latest("m.replace");
}

View File

@ -31,6 +31,7 @@ namespace ircd::m
bool exists(const id::room_alias &, const bool &remote = false);
bool internal(const id::room &);
bool federated(const id::room &);
bool type(const id::room &, const string_view &);
bool creator(const id::room &, const id::user &);
bool contains(const id::room &, const event::idx &);
bool membership(const room &, const id::user &, const string_view & = "join");

View File

@ -48,6 +48,9 @@ struct ircd::m::rooms::opts
/// Room alias prefix search
string_view room_alias;
/// Room type search
string_view room_type;
/// user::rooms convenience
id::user user_id;

View File

@ -132,11 +132,11 @@ ircd::net::dns::tag::tag(const hostport &hp,
{
this->hp.host =
{
hostbuf, copy(hostbuf, hp.host)
tolower(hostbuf, hp.host)
};
this->hp.service =
{
servicebuf, copy(servicebuf, hp.service)
tolower(servicebuf, hp.service)
};
}

View File

@ -100,6 +100,10 @@ struct ircd::rest::opts
/// receiving dynamic content. Supply an empty unique_buffer instance.
unique_const_buffer *out {nullptr};
/// Optionally supply the temporary buffer for headers in/out in lieu of
/// any internally allocated.
mutable_buffer buf;
/// Timeout for the yielding/synchronous calls of this interface.
seconds timeout {20s};

View File

@ -604,11 +604,11 @@ ircd::json::replace(const object &s,
{
[](const json::members &r, const object::member &m)
{
return std::any_of(begin(r), end(r), [&m]
(const json::member &r)
{
return string_view{r.first} == m.first;
});
for(const auto &[k, v] : r)
if(string_view{k} == m.first)
return true;
return false;
}
};
@ -634,33 +634,9 @@ ircd::json::replace(const object &s,
};
}
ircd::json::strung
ircd::json::replace(const object &s,
const json::member &m_)
{
if(unlikely(!empty(s) && type(s) != type::OBJECT))
throw type_error
{
"Cannot replace member into JSON of type %s",
reflect(type(s))
};
size_t mctr {0};
auto &mb(member_buffer);
for(const object::member &m : object{s})
if(m.first != string_view{m_.first})
mb.at(mctr++) = member{m};
mb.at(mctr++) = m_;
return strung
{
mb.data(), mb.data() + mctr
};
}
ircd::json::strung
ircd::json::insert(const object &s,
const json::member &m)
const json::members &m)
{
if(unlikely(!empty(s) && type(s) != type::OBJECT))
throw type_error
@ -674,7 +650,9 @@ ircd::json::insert(const object &s,
for(const object::member &m : object{s})
mb.at(mctr++) = member{m};
mb.at(mctr++) = m;
for(const auto &_m : m)
mb.at(mctr++) = _m;
return strung
{
mb.data(), mb.data() + mctr
@ -1734,6 +1712,42 @@ ircd::json::stack::member::member(object &po,
s->append(string_view{tmp, size_t(data(buf) - tmp)});
}
ircd::json::stack::member::member(stack &s,
const json::member &m)
:member
{
stack::top<object>(s), m
}
{
}
ircd::json::stack::member::member(object &po,
const json::member &m)
:member
{
po, string_view{m.first}, m.second
}
{
}
ircd::json::stack::member::member(stack &s,
const json::object::member &om)
:member
{
stack::top<object>(s), om
}
{
}
ircd::json::stack::member::member(object &po,
const json::object::member &om)
:member
{
po, om.first, om.second
}
{
}
ircd::json::stack::member::member(stack &s,
const string_view &name,
const json::value &value)
@ -4754,7 +4768,8 @@ namespace ircd::json::parser
bool
ircd::json::type(const string_view &buf,
const enum type &type)
const enum type type)
noexcept
{
const bool ret
{
@ -4780,6 +4795,7 @@ ircd::json::type(const string_view &buf)
enum ircd::json::type
ircd::json::type(const string_view &buf,
std::nothrow_t)
noexcept
{
enum type ret;
if(!parser::parse(begin(buf), end(buf), parser::type_parse, ret))
@ -4842,8 +4858,9 @@ namespace ircd::json::parser
bool
ircd::json::type(const string_view &buf,
const enum type &type,
const enum type type,
strict_t)
noexcept
{
const bool ret
{
@ -4871,6 +4888,7 @@ enum ircd::json::type
ircd::json::type(const string_view &buf,
strict_t,
std::nothrow_t)
noexcept
{
enum type ret;
if(!parser::parse(begin(buf), end(buf), parser::type_parse_strict, ret))
@ -4880,7 +4898,8 @@ ircd::json::type(const string_view &buf,
}
ircd::string_view
ircd::json::reflect(const enum type &type)
ircd::json::reflect(const enum type type)
noexcept
{
switch(type)
{
@ -4891,8 +4910,6 @@ ircd::json::reflect(const enum type &type)
case STRING: return "STRING";
}
throw type_error
{
"Unknown type %x", uint(type)
};
assert(false);
return "STRING";
}

View File

@ -14,13 +14,25 @@ ircd::rest::request::request(const rfc3986::uri &uri,
if(!opts.remote)
opts.remote = net::hostport{uri};
const unique_mutable_buffer buf
const bool need_alloc
{
empty(opts.sout.head) || empty(opts.sin.head)?
16_KiB: 0_KiB
empty(opts.buf)
&& (empty(opts.sout.head) || empty(opts.sin.head))
};
const unique_mutable_buffer _buf
{
need_alloc? 16_KiB: 0_KiB
};
if(!empty(_buf))
opts.buf = _buf;
window_buffer window
{
opts.buf
};
window_buffer window{buf};
if(empty(opts.sout.head))
{
assert(opts.remote);
@ -80,13 +92,25 @@ ircd::rest::request::request(const mutable_buffer &out,
if(!opts.remote)
opts.remote = net::hostport{uri};
const unique_mutable_buffer buf
const bool need_alloc
{
empty(opts.sout.head) || empty(opts.sin.head)?
16_KiB: 0_KiB
empty(opts.buf)
&& (empty(opts.sout.head) || empty(opts.sin.head))
};
const unique_mutable_buffer _buf
{
need_alloc? 16_KiB: 0_KiB
};
if(!empty(_buf))
opts.buf = _buf;
window_buffer window
{
opts.buf
};
window_buffer window{buf};
if(empty(opts.sout.head))
{
assert(opts.remote);

View File

@ -132,6 +132,13 @@ ircd::m::id::parser
,"event_id version 4"
};
// de-facto device id
const rule<> device_id
{
device_sigil >> localpart
,"device_id (de facto)"
};
/// (Appendix 4.1) Server Name
/// A homeserver is uniquely identified by its server name. This value
/// is used in a number of identifiers, as described below. The server
@ -159,6 +166,7 @@ ircd::m::id::parser
(prefix >> ':' >> server_name)
| event_id_v4
| event_id_v3
| device_id
,"mxid"
};
@ -584,7 +592,12 @@ ircd::m::id::id(const enum sigil &sigil,
static const auto &dict{rand::dict::alnum};
const mutable_buffer dst{tmp_buf[0], 10};
name = rand::string(dst, dict);
break;
return fmt::sprintf
{
buf, "%c%s",
char(sigil),
name,
};
}
default:

View File

@ -132,8 +132,7 @@ ircd::m::module_names
"client_keys_query",
"client_keys_signatures_upload",
"client_keys_device_signing_upload",
"client_room_keys_version",
"client_room_keys_keys",
"client_room_keys",
"client_presence",
"client_groups",
"client_joined_groups",
@ -158,6 +157,7 @@ ircd::m::module_names
"client_sync_account_data",
"client_sync_device_lists",
"client_sync_device_one_time_keys_count",
"client_sync_device_unused_fallback_key_types",
"client_sync_groups",
"client_sync_presence",
"client_sync_to_device",

View File

@ -892,6 +892,32 @@ ircd::m::join_rule(const room &room,
return join_rule(buf, room) == rule;
}
bool
ircd::m::type(const room::id &room_id,
const string_view &type_)
{
const m::room room
{
room_id
};
const auto event_idx
{
room.get(std::nothrow, "m.room.create", "")
};
return m::query(std::nothrow, event_idx, "content", false, [&type_]
(const json::object &content)
{
const json::string &type
{
content.get("type")
};
return type == type_;
});
}
bool
ircd::m::creator(const room::id &room_id,
const user::id &user_id)

View File

@ -31,7 +31,7 @@ decltype(ircd::m::createroom::version_default)
ircd::m::createroom::version_default
{
{ "name", "ircd.m.createroom.version_default" },
{ "default", "5" },
{ "default", "6" },
};
decltype(ircd::m::createroom::spec_presets)

View File

@ -54,15 +54,14 @@ ircd::m::replace(room::message &msg,
const event::idx &event_idx)
{
// Find the latest edit of this; otherwise stick with the original.
const m::relates relates
const m::replaced replaced
{
.refs = event_idx,
.match_sender = true,
event_idx, m::replaced::latest
};
const event::idx replace_idx
const event::idx &replace_idx
{
relates.latest("m.replace")
replaced
};
if(!replace_idx)

View File

@ -138,6 +138,10 @@ ircd::m::rooms::for_each(const opts &opts,
if(!join_rule(room, opts.join_rule))
return;
if(opts.room_type)
if(!m::type(room_id, opts.room_type))
return;
if(opts.server && opts.request_node_id && my_host(opts.server))
if(!room::aliases(room_id).has_server(opts.server))
return;

View File

@ -443,6 +443,7 @@ client_client_sync_groups_la_SOURCES = client/sync/groups.cc
client_client_sync_to_device_la_SOURCES = client/sync/to_device.cc
client_client_sync_device_lists_la_SOURCES = client/sync/device_lists.cc
client_client_sync_device_one_time_keys_count_la_SOURCES = client/sync/device_one_time_keys_count.cc
client_client_sync_device_unused_fallback_key_types_la_SOURCES = client/sync/device_unused_fallback_key_types.cc
client_module_LTLIBRARIES += \
client/client_sync_account_data.la \
@ -452,6 +453,7 @@ client_module_LTLIBRARIES += \
client/client_sync_to_device.la \
client/client_sync_device_lists.la \
client/client_sync_device_one_time_keys_count.la \
client/client_sync_device_unused_fallback_key_types.la \
###
# client/sync/rooms/
@ -506,12 +508,14 @@ client_module_LTLIBRARIES += \
# client/room_keys/
#
client_client_room_keys_version_la_SOURCES = client/room_keys/version.cc
client_client_room_keys_keys_la_SOURCES = client/room_keys/keys.cc
client_client_room_keys_la_SOURCES = \
client/room_keys/keys.cc \
client/room_keys/version.cc \
client/room_keys/room_keys.cc \
###
client_module_LTLIBRARIES += \
client/client_room_keys_version.la \
client/client_room_keys_keys.la \
client/client_room_keys.la \
###
#

View File

@ -51,6 +51,16 @@ ircd::m::client_capabilities::get(client &client,
mods::loaded("client_account")
};
const bool m_set_displayname__enabled
{
mods::loaded("client_profile")
};
const bool m_set_avatar_url__enabled
{
mods::loaded("client_profile")
};
const json::value default_room_version
{
string_view{m::createroom::version_default}, json::STRING
@ -66,6 +76,14 @@ ircd::m::client_capabilities::get(client &client,
{
{ "enabled", m_change_password__enabled },
}},
{ "m.set_displayname", json::members
{
{ "enabled", m_set_displayname__enabled },
}},
{ "m.set_avatar_url", json::members
{
{ "enabled", m_set_avatar_url__enabled },
}},
{ "m.room_versions", json::members
{
{ "default", default_room_version },

View File

@ -274,6 +274,11 @@ _get_device(json::stack::object &obj,
obj, "device_id", device_id
};
json::stack::member
{
obj, "user_id", devices.user.user_id
};
devices.get(std::nothrow, device_id, "display_name", [&obj]
(const auto &, const string_view &value)
{

View File

@ -43,34 +43,21 @@ ircd::m::resource::response
ircd::m::post_keys_signatures_upload(client &client,
const resource::request &request)
{
const m::device::id::buf device_id
{
m::user::tokens::device(request.access_token)
};
const m::user::room user_room
{
request.user_id
};
for(const auto &[_user_id, devices_keys_] : request)
{
if(!valid(m::id::USER, _user_id))
continue;
if(_user_id != request.user_id)
throw m::ACCESS_DENIED
{
"Uploading for user %s by %s not allowed or supported",
_user_id,
string_view{request.user_id},
};
const m::user::id user_id
{
_user_id
};
const m::user::room user_room
{
user_id
};
const m::user::devices devices
{
user_id
@ -88,22 +75,6 @@ ircd::m::post_keys_signatures_upload(client &client,
device_keys_
};
if(json::get<"device_id"_>(device_keys) != _device_id)
throw m::BAD_REQUEST
{
"device_id '%s' does not match object property name '%s'",
json::get<"device_id"_>(device_keys),
_device_id,
};
if((false) && _device_id != device_id) // is this the "cross-sign?" gotta find out!
throw m::ACCESS_DENIED
{
"device_id '%s' does not match your current device_id '%s'",
_device_id,
string_view{device_id},
};
const bool set
{
devices.set(_device_id, "signatures", device_keys_)

View File

@ -121,12 +121,6 @@ post__login_password(client &client,
m::id::device::buf{m::id::generate, my_host()}
};
if(!my(device_id))
throw m::UNSUPPORTED
{
"Device ID's with foreign hostparts are not supported."
};
char access_token_buf[32];
const string_view access_token
{

View File

@ -134,6 +134,7 @@ get__publicrooms(client &client,
opts.lower_bound = true;
opts.room_id = since;
opts.request_user_id = request.user_id;
opts.room_type = json::string{filter["room_type"]};
if(m::valid(m::id::USER, search_term))
opts.user_id = search_term;

View File

@ -8,10 +8,10 @@
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#include "room_keys.h"
namespace ircd::m
{
static string_view make_state_key(const mutable_buffer &, const string_view &, const string_view &, const event::idx &);
static resource::response _get_room_keys_keys(client &, const resource::request &, const room::state &, const event::idx &, const string_view &, const string_view &);
static void _get_room_keys_keys(client &, const resource::request &, const room::state &, const event::idx &, const string_view &, json::stack::object &);
static resource::response get_room_keys_keys(client &, const resource::request &);
@ -21,18 +21,14 @@ namespace ircd::m
static resource::response put_room_keys_keys(client &, const resource::request &);
extern resource::method room_keys_keys_put;
static event::id::buf delete_room_keys_key(client &, const resource::request &, const room &, const event::idx &);
static event::id::buf delete_room_keys_key(client &, const resource::request &, const room &, const room::id &, const string_view &, const event::idx &);
static resource::response delete_room_keys_keys(client &, const resource::request &);
extern resource::method room_keys_keys_delete;
extern resource room_keys_keys;
}
ircd::mapi::header
IRCD_MODULE
{
"Client (undocumented) :e2e Room Keys Keys"
};
decltype(ircd::m::room_keys_keys)
ircd::m::room_keys_keys
{
@ -79,7 +75,7 @@ ircd::m::delete_room_keys_keys(client &client,
const event::idx version
{
request.query.at<event::idx>("version")
request.query.get<event::idx>("version", 0)
};
const m::user::room user_room
@ -92,15 +88,104 @@ ircd::m::delete_room_keys_keys(client &client,
user_room
};
if(!room_id && !session_id)
{
state.for_each("ircd.room_keys.key", [&client, &request, &user_room, &version]
(const string_view &, const string_view &state_key, const event::idx &event_idx)
{
const auto &[room_id, session_id, _version]
{
unmake_state_key(state_key)
};
if(version && _version != lex_cast(version))
return true;
delete_room_keys_key(client, request, user_room, event_idx);
return true;
});
}
else if(!session_id)
{
state.for_each("ircd.room_keys.key", [&client, &request, &user_room, &version, &room_id]
(const string_view &, const string_view &state_key, const event::idx &event_idx)
{
const auto &[_room_id, session_id, _version]
{
unmake_state_key(state_key)
};
if(version && _version != lex_cast(version))
return true;
if(_room_id != room_id)
return true;
delete_room_keys_key(client, request, user_room, event_idx);
return true;
});
}
else delete_room_keys_key(client, request, user_room, room_id, session_id, version);
const auto &[count, etag]
{
count_etag(state, version)
};
const json::value _etag
{
lex_cast(etag), json::STRING
};
return resource::response
{
client, json::members
{
{ "count", count },
{ "etag", _etag },
}
};
}
ircd::m::event::id::buf
ircd::m::delete_room_keys_key(client &client,
const resource::request &request,
const room &user_room,
const room::id &room_id,
const string_view &session_id,
const event::idx &version)
{
char state_key_buf[event::STATE_KEY_MAX_SIZE];
const string_view state_key
{
make_state_key(state_key_buf, room_id, session_id, version)
};
const room::state state
{
user_room
};
const auto event_idx
{
state.get(std::nothrow, "ircd.room_keys.key", state_key)
};
if(!event_idx)
return {};
return delete_room_keys_key(client, request, user_room, event_idx);
}
ircd::m::event::id::buf
ircd::m::delete_room_keys_key(client &client,
const resource::request &request,
const room &user_room,
const event::idx &event_idx)
{
const auto event_id
{
m::event_id(state.get("ircd.room_keys.key", state_key))
m::event_id(event_idx)
};
const auto redact_id
@ -108,10 +193,7 @@ ircd::m::delete_room_keys_keys(client &client,
m::redact(user_room, request.user_id, event_id, "deleted by client")
};
return resource::response
{
client, http::OK
};
return redact_id;
}
//
@ -160,6 +242,16 @@ ircd::m::put_room_keys_keys(client &client,
request.query.at<event::idx>("version")
};
const m::user::room user_room
{
request.user_id
};
const m::room::state state
{
user_room
};
if(!room_id && !session_id)
{
const json::object &rooms
@ -167,9 +259,16 @@ ircd::m::put_room_keys_keys(client &client,
request["rooms"]
};
for(const auto &[room_id, sessions] : rooms)
for(const auto &[session_id, session] : json::object(sessions))
for(const auto &[room_id, room_data] : rooms)
{
const json::object sessions
{
json::object(room_data)["sessions"]
};
for(const auto &[session_id, session] : sessions)
put_room_keys_keys_key(client, request, room_id, session_id, version, session);
}
}
else if(!session_id)
{
@ -183,9 +282,23 @@ ircd::m::put_room_keys_keys(client &client,
}
else put_room_keys_keys_key(client, request, room_id, session_id, version, request);
const auto &[count, etag]
{
count_etag(state, version)
};
const json::value _etag
{
lex_cast(etag), json::STRING
};
return resource::response
{
client, http::OK
client, json::members
{
{ "count", count },
{ "etag", _etag },
}
};
}
@ -320,12 +433,12 @@ ircd::m::get_room_keys_keys(client &client,
state.for_each("ircd.room_keys.key", [&client, &request, &state, &version, &rooms, &last_room]
(const string_view &, const string_view &state_key, const event::idx &)
{
const auto &room_id
const auto &[room_id, _session_id, _version]
{
token(state_key, ":::", 0)
unmake_state_key(state_key)
};
if(!m::valid(id::ROOM, room_id))
if(_version != lex_cast(version))
return true;
if(room_id == last_room)
@ -359,29 +472,28 @@ ircd::m::_get_room_keys_keys(client &client,
state.for_each("ircd.room_keys.key", [&room_id, &version, &sessions]
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
{
string_view part[3]; const auto parts
const auto &[_room_id, _session_id, _version]
{
tokens(state_key, ":::", part)
unmake_state_key(state_key)
};
const auto &_room_id{part[0]};
const auto &_session_id{part[1]};
const auto &_version{part[2]};
if(!m::valid(id::ROOM, _room_id))
return true;
if(_room_id != room_id)
return true;
if(_version != lex_cast<event::idx>(version))
if(_version != lex_cast(version))
return true;
m::get(std::nothrow, event_idx, "content", [&sessions, &_session_id]
const string_view &session_id
{
_session_id
};
m::get(std::nothrow, event_idx, "content", [&sessions, &session_id]
(const json::object &session)
{
json::stack::member
{
sessions, _session_id, session
sessions, session_id, session
};
});
@ -419,18 +531,3 @@ ircd::m::_get_room_keys_keys(client &client,
return {}; // responded from closure or thrown
}
ircd::string_view
ircd::m::make_state_key(const mutable_buffer &buf,
const string_view &room_id,
const string_view &session_id,
const event::idx &version)
{
return fmt::sprintf
{
buf, "%s:::%s:::%u",
room_id,
session_id,
version,
};
}

View File

@ -0,0 +1,94 @@
// The Construct
//
// Copyright (C) The Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 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.
#include "room_keys.h"
ircd::mapi::header
IRCD_MODULE
{
"Client :e2e Room Keys"
};
std::tuple<int64_t, int64_t>
ircd::m::count_etag(const room::state &state,
const event::idx &version)
{
char version_buf[64];
const auto version_str
{
lex_cast(version, version_buf)
};
uint64_t count(0), etag(0);
state.for_each("ircd.room_keys.key", [&]
(const string_view &type, const string_view &state_key, const event::idx &event_idx)
{
const auto &[room_id, session_id, _version_str]
{
unmake_state_key(state_key)
};
if(_version_str != version_str)
return true;
etag += event_idx;
count += 1;
return true;
});
return
{
int64_t(count),
int64_t(etag),
};
}
std::tuple<ircd::string_view, ircd::string_view, ircd::string_view>
ircd::m::unmake_state_key(const string_view &state_key)
{
assert(state_key);
string_view part[3];
const auto parts
{
tokens(state_key, ":::", part)
};
assert(parts == 3);
if(unlikely(!m::valid(id::ROOM, part[0])))
part[0] = {};
if(unlikely(!lex_castable<ulong>(part[2])))
part[2] = {};
return std::make_tuple
(
part[0], part[1], part[2]
);
}
ircd::string_view
ircd::m::make_state_key(const mutable_buffer &buf,
const string_view &room_id,
const string_view &session_id,
const event::idx &version)
{
assert(room_id);
assert(m::valid(id::ROOM, room_id));
assert(session_id);
assert(session_id != "sessions");
assert(version != 0);
return fmt::sprintf
{
buf, "%s:::%s:::%u",
room_id,
session_id,
version,
};
}

View File

@ -0,0 +1,18 @@
// The Construct
//
// Copyright (C) The Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 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.
#pragma GCC visibility push(hidden)
namespace ircd::m
{
string_view make_state_key(const mutable_buffer &, const string_view &, const string_view &, const event::idx &);
std::tuple<string_view, string_view, string_view> unmake_state_key(const string_view &);
std::tuple<int64_t, int64_t> count_etag(const room::state &, const event::idx &version);
}
#pragma GCC visibility pop

View File

@ -8,6 +8,8 @@
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#include "room_keys.h"
namespace ircd::m
{
static resource::response get_room_keys_version(client &, const resource::request &);
@ -25,12 +27,6 @@ namespace ircd::m
extern resource room_keys_version;
}
ircd::mapi::header
IRCD_MODULE
{
"Client (undocumented) :e2e Room Keys Version"
};
decltype(ircd::m::room_keys_version)
ircd::m::room_keys_version
{
@ -209,11 +205,6 @@ ircd::m::put_room_keys_version(client &client,
event_idx,
};
const auto event_id
{
m::event_id(event_idx)
};
const json::string &algorithm
{
request["algorithm"]
@ -224,13 +215,36 @@ ircd::m::put_room_keys_version(client &client,
request["auth_data"]
};
//
// TODO: XXX
//
const auto event_id
{
m::event_id(event_idx)
};
const json::member relates[]
{
{ "event_id", event_id },
{ "rel_type", "m.replace" },
};
const json::strung content
{
json::insert(request, json::members
{
{ "m.relates_to", relates }
})
};
const auto update_id
{
m::send(user_room, request.user_id, "ircd.room_keys.version", json::object
{
content
})
};
return resource::response
{
client, http::NOT_IMPLEMENTED
client, http::OK
};
}
@ -301,9 +315,36 @@ ircd::m::get_room_keys_version(client &client,
"No version found.",
};
m::get(event_idx, "content", [&client, &event_idx]
const m::replaced latest_idx
{
event_idx, m::replaced::latest
};
const event::idx version_idx
{
latest_idx?
event::idx{latest_idx}:
event_idx
};
const m::room::state state
{
user_room
};
m::get(version_idx, "content", [&client, &event_idx, &state]
(const json::object &content)
{
const auto &[count, etag]
{
count_etag(state, event_idx)
};
const json::value _etag
{
lex_cast(etag), json::STRING
};
const json::value version
{
lex_cast(event_idx), json::STRING
@ -313,9 +354,11 @@ ircd::m::get_room_keys_version(client &client,
{
client, json::members
{
{ "version", version },
{ "algorithm", content["algorithm"] },
{ "auth_data", content["auth_data"] },
{ "count", count },
{ "etag", _etag },
{ "version", version },
}
};
});

View File

@ -41,6 +41,12 @@ ircd::m::sync::device_lists_linear(data &data)
if(!startswith(json::get<"type"_>(event), "ircd.device"))
return false;
if(startswith(json::get<"type"_>(event), "ircd.device.signing"))
return false;
if(startswith(json::get<"type"_>(event), "ircd.device.one_time_key"))
return false;
const m::user sender
{
m::user::id(json::get<"sender"_>(event))
@ -56,7 +62,9 @@ ircd::m::sync::device_lists_linear(data &data)
const bool changed
{
mitsein.has(data.user, "join")
false
|| sender == data.user.user_id
|| mitsein.has(data.user, "join")
};
const bool left
@ -67,6 +75,11 @@ ircd::m::sync::device_lists_linear(data &data)
if(!changed && !left)
return false;
json::stack::object device_lists
{
*data.out, "device_lists"
};
json::stack::array array
{
*data.out, left? "left": "changed"

View File

@ -0,0 +1,64 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2023 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.
namespace ircd::m::sync
{
static bool device_unused_fallback_key_types_polylog(data &);
static bool device_unused_fallback_key_types_linear(data &);
extern item device_unused_fallback_key_types;
}
ircd::mapi::header
IRCD_MODULE
{
"Client Sync :Device Unused Fallback Key Types"
};
decltype(ircd::m::sync::device_unused_fallback_key_types)
ircd::m::sync::device_unused_fallback_key_types
{
"device_unused_fallback_key_types",
device_unused_fallback_key_types_polylog,
device_unused_fallback_key_types_linear
};
bool
ircd::m::sync::device_unused_fallback_key_types_linear(data &data)
{
if(!data.device_id)
return false;
if(!data.event || !data.event->event_id)
return false;
if(!startswith(json::get<"type"_>(*data.event), "ircd.device"))
return false;
if(json::get<"room_id"_>(*data.event) != data.user_room)
return false;
json::stack::array array
{
*data.out, "device_unused_fallback_key_types"
};
array.append("signed_curve25519");
return true;
}
bool
ircd::m::sync::device_unused_fallback_key_types_polylog(data &data)
{
if(!data.device_id)
return false;
return false;
}

View File

@ -85,6 +85,7 @@ ircd::m::client_versions::versions_default
" v1.3"
" v1.4"
" v1.5"
" v1.6"
};
/// Note this conf item doesn't persist to and from the database, which means
@ -240,4 +241,13 @@ ircd::m::client_versions::append_unstable_features(client &client,
bool(e2ee_forced_trusted_private)
}
};
// Supports filtering of /publicRooms by room type as per MSC3827
json::stack::member
{
out, "org.matrix.msc3827.stable", json::value
{
true
}
};
}

View File

@ -11455,31 +11455,47 @@ console_cmd__room__count(opt &out, const string_view &line)
bool
console_cmd__room__events(opt &out, const string_view &line)
{
const params param{line, " ",
const params param_any{line, " ",
{
"room_id", "depth|-limit", "order", "limit"
}};
const params param_type{line, " ",
{
"room_id", "type", "depth|-limit", "order", "limit"
}};
const bool use_type
{
param_type["type"] && !lex_castable<int64_t>(param_type["type"])
};
// decide which argument overload to use
const params &param
{
use_type? param_type : param_any
};
const auto &room_id
{
m::room_id(param.at(0))
m::room_id(param.at("room_id"))
};
const int64_t depth
{
param.at<int64_t>(1, std::numeric_limits<int64_t>::max())
param.at<int64_t>("depth|-limit", std::numeric_limits<int64_t>::max())
};
const char order
{
param.at(2, "b"_sv).at(0)
param.at("order", "b"_sv).at(0)
};
ssize_t limit
{
depth < 0?
std::abs(depth):
param.at(3, ssize_t(32))
param.at("depth|-limit", ssize_t(32))
};
const m::room room
@ -11493,15 +11509,21 @@ console_cmd__room__events(opt &out, const string_view &line)
};
m::event::fetch event;
for(; it && limit > 0; order == 'b'? --it : ++it, --limit)
for(; it && limit > 0; order == 'b'? --it : ++it)
{
if(!seek(std::nothrow, event, it.event_idx()))
continue;
if(use_type)
if(!globular_imatch(param["type"])(json::get<"type"_>(event)))
continue;
out
<< std::left << std::setw(10) << it.event_idx() << " "
<< pretty_oneline(event)
<< std::endl;
--limit;
}
return true;
@ -13941,7 +13963,7 @@ console_cmd__user__tokens(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id", "clear"
"user_id"
}};
const m::user user
@ -13949,27 +13971,11 @@ console_cmd__user__tokens(opt &out, const string_view &line)
param.at("user_id")
};
const bool clear
{
param["clear"] == "clear"
};
const m::user::tokens tokens
{
user
};
if(clear)
{
const size_t count
{
tokens.del("Invalidated by administrator console.")
};
out << "Invalidated " << count << std::endl;
return true;
}
tokens.for_each([&out]
(const m::event::idx &event_idx, const string_view &token)
{
@ -14010,6 +14016,33 @@ console_cmd__user__tokens(opt &out, const string_view &line)
return true;
}
bool
console_cmd__user__tokens__clear(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id"
}};
const m::user user
{
param.at("user_id")
};
const m::user::tokens tokens
{
user
};
const size_t count
{
tokens.del("Invalidated by administrator console.")
};
out << "Invalidated " << count << std::endl;
return true;
}
bool
console_cmd__user__profile(opt &out, const string_view &line)
{
@ -14370,6 +14403,47 @@ console_cmd__user__devices(opt &out, const string_view &line)
return true;
}
bool
console_cmd__user__devices__delete(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id", "device_id"
}};
const m::user::id &user_id
{
param.at("user_id")
};
const string_view &device_id
{
param.at("device_id", string_view{})
};
const m::user::devices devices
{
user_id
};
if(device_id)
{
devices.del(device_id);
out << device_id << std::endl;
return true;
}
devices.for_each([&out, &devices]
(const auto &event_idx, const string_view &device_id)
{
devices.del(device_id);
out << device_id << std::endl;
return true;
});
return true;
}
bool
console_cmd__user__devices__update(opt &out, const string_view &line)
{
@ -18195,7 +18269,7 @@ console_cmd__redact__last(opt &out, const string_view &line)
<< redact_id
<< " redacted "
<< event_id
;
<< std::endl;
return --count > 0;
@ -18204,6 +18278,93 @@ console_cmd__redact__last(opt &out, const string_view &line)
return true;
}
bool
console_cmd__redact__state(opt &out, const string_view &line)
{
const params param{line, " ",
{
"room_id", "redactor", "type", "state_key"
}};
const m::room::id::buf room_id
{
m::room_id(param.at("room_id"))
};
const m::user::id redactor
{
param.at("redactor")
};
const auto type
{
param["type"]
};
const auto state_key
{
param["state_key"]
};
const m::room room
{
room_id
};
if(state_key)
{
const auto event_idx
{
room.get(type, state_key)
};
const auto event_id
{
m::event_id(std::nothrow, event_idx)
};
const auto redact_id
{
m::redact(room, redactor, event_id, "console")
};
out
<< redact_id
<< " redacted "
<< event_id
<< std::endl;
return true;
}
const m::room::state state
{
room
};
state.for_each(type, [&out, &room, &redactor]
(const auto &, const auto &state_key, const m::event::idx &event_idx)
{
const auto event_id
{
m::event_id(std::nothrow, event_idx)
};
const auto redact_id
{
m::redact(room, redactor, event_id, "console")
};
out
<< redact_id
<< " redacted "
<< event_id
<< std::endl;
return true;
});
return true;
}
//
// well-known
//

View File

@ -114,6 +114,7 @@ handle_get(client &client,
opts.room_id = since;
opts.search_term = search_term;
opts.request_node_id = request.node_id;
opts.room_type = json::string(filter["room_type"]);
size_t count{0};
m::room::id::buf prev_batch_buf;

View File

@ -106,10 +106,27 @@ get__user_devices(client &client,
{
json::stack::member
{
response, "self_signing_keys", content
response, "self_signing_key", content
};
});
if(my_host(request.node_id))
{
const auto user_event_idx
{
user_room.get(std::nothrow, "ircd.device.signing.user", "")
};
m::get(std::nothrow, user_event_idx, "content", [&response]
(const json::object &content)
{
json::stack::member
{
response, "user_signing_key", content
};
});
}
json::stack::array devices
{
response, "devices"

View File

@ -32,6 +32,11 @@ _query_user_device(client &,
const string_view &device_id,
json::stack::object &out);
static void
_query_user_keys(client &,
const m::resource::request &,
json::stack &);
static void
_query_self_keys(client &,
const m::resource::request &,
@ -72,6 +77,9 @@ post__user_keys_query(client &client,
_query_device_keys(client, request, response);
_query_master_keys(client, request, response);
_query_self_keys(client, request, response);
if(my_host(request.node_id))
_query_user_keys(client, request, response);
return response;
}
@ -211,6 +219,49 @@ _query_self_keys(client &client,
}
}
void
_query_user_keys(client &client,
const m::resource::request &request,
json::stack &out)
{
const json::object request_keys
{
request.at("device_keys")
};
json::stack::object response_keys
{
out, "user_signing_keys"
};
for(const auto &[user_id_, device_ids_] : request_keys)
{
const m::user::id &user_id
{
user_id_
};
const m::user::room room
{
user_id
};
const auto event_idx
{
room.get(std::nothrow, "ircd.device.signing.user", "")
};
m::get(std::nothrow, event_idx, "content", [&response_keys, &user_id]
(const json::object &content)
{
json::stack::member
{
response_keys, user_id, content
};
});
}
}
void
_query_user_device(client &client,
const m::resource::request &request,
@ -226,14 +277,66 @@ _query_user_device(client &client,
out, device_id
};
devices.get(std::nothrow, device_id, "keys", [&device_id, &object]
devices.get(std::nothrow, device_id, "keys", [&devices, &device_id, &object]
(const auto &event_idx, const json::object &device_keys)
{
const auto &user_id
{
devices.user.user_id
};
for(const auto &member : device_keys)
if(member.first != "signatures")
json::stack::member
{
object, member
};
json::stack::object sigs
{
object, "signatures"
};
json::stack::object user_sigs
{
sigs, user_id
};
const json::object device_keys_sigs
{
device_keys["signatures"]
};
const json::object device_keys_user_sigs
{
device_keys_sigs[user_id]
};
for(const auto &member : device_keys_user_sigs)
json::stack::member
{
object, member.first, member.second
user_sigs, member
};
devices.get(std::nothrow, device_id, "signatures", [&user_id, &user_sigs]
(const auto &event_idx, const json::object &device_sigs)
{
const json::object device_sigs_sigs
{
device_sigs["signatures"]
};
const json::object device_sigs_user_sigs
{
device_sigs_sigs[user_id]
};
for(const auto &member : device_sigs_user_sigs)
json::stack::member
{
user_sigs, member
};
});
});
devices.get(std::nothrow, device_id, "display_name", [&device_id, &object]