1
0
Fork 0
mirror of https://github.com/processone/ejabberd synced 2025-10-03 17:59:31 +02:00

Support older Matrix rooms versions starting from version 4

This commit is contained in:
Alexey Shchepin 2025-05-26 18:11:45 +03:00
parent 9d1d57cd82
commit 038491d2ec
4 changed files with 210 additions and 33 deletions

View file

@ -21,6 +21,13 @@
-record(room_version,
{id :: binary(),
%% use the same field names as in Synapse
enforce_key_validity :: boolean(),
special_case_aliases_auth :: boolean(),
strict_canonicaljson :: boolean(),
limit_notifications_power_levels :: boolean(),
knock_join_rule :: boolean(),
restricted_join_rule :: boolean(),
restricted_join_rule_fix :: boolean(),
knock_restricted_join_rule :: boolean(),
enforce_int_power_levels :: boolean(),
implicit_room_creator :: boolean(),

View file

@ -41,6 +41,7 @@
handle_info/2, terminate/2, code_change/3,
depends/2, mod_opt_type/1, mod_options/1, mod_doc/0]).
-export([parse_auth/1, encode_canonical_json/1,
is_canonical_json/1,
get_id_domain_exn/1,
base64_decode/1, base64_encode/1,
prune_event/2, get_event_id/2, content_hash/1,
@ -155,8 +156,8 @@ process([<<"federation">>, <<"v2">>, <<"invite">>, RoomID, EventID],
%% TODO: check type and userid
Host = ejabberd_config:get_myname(),
PrunedEvent = prune_event(Event, RoomVersion),
?DEBUG("invite ~p~n", [{RoomID, EventID, Event, RoomVer, catch mod_matrix_gw_s2s:check_signature(Host, PrunedEvent), get_pruned_event_id(PrunedEvent)}]),
case mod_matrix_gw_s2s:check_signature(Host, PrunedEvent) of
%?DEBUG("invite ~p~n", [{RoomID, EventID, Event, RoomVer, catch mod_matrix_gw_s2s:check_signature(Host, PrunedEvent, RoomVersion), get_pruned_event_id(PrunedEvent)}]),
case mod_matrix_gw_s2s:check_signature(Host, PrunedEvent, RoomVersion) of
true ->
case get_pruned_event_id(PrunedEvent) of
EventID ->
@ -629,9 +630,15 @@ prune_event(#{<<"type">> := Type, <<"content">> := Content} = Event,
Content2 =
case Type of
<<"m.room.member">> ->
C3 = maps:with([<<"membership">>,
C3 =
case RoomVersion#room_version.restricted_join_rule_fix of
true ->
maps:with([<<"membership">>,
<<"join_authorised_via_users_server">>],
Content),
Content);
false ->
maps:with([<<"membership">>], Content)
end,
case RoomVersion#room_version.updated_redaction_rules of
false ->
C3;
@ -653,7 +660,12 @@ prune_event(#{<<"type">> := Type, <<"content">> := Content} = Event,
Content
end;
<<"m.room.join_rules">> ->
maps:with([<<"join_rule">>, <<"allow">>], Content);
case RoomVersion#room_version.restricted_join_rule of
false ->
maps:with([<<"join_rule">>], Content);
true ->
maps:with([<<"join_rule">>, <<"allow">>], Content)
end;
<<"m.room.power_levels">> ->
case RoomVersion#room_version.updated_redaction_rules of
false ->
@ -677,6 +689,8 @@ prune_event(#{<<"type">> := Type, <<"content">> := Content} = Event,
true ->
maps:with([<<"redacts">>], Content)
end;
<<"m.room.aliases">> when RoomVersion#room_version.special_case_aliases_auth ->
maps:with([<<"aliases">>], Content);
_ -> #{}
end,
Event2#{<<"content">> := Content2}.
@ -716,6 +730,27 @@ sort_json(List) when is_list(List) ->
sort_json(JSON) ->
JSON.
is_canonical_json(N) when is_integer(N),
-16#1FFFFFFFFFFFFF =< N,
N =< 16#1FFFFFFFFFFFFF ->
true;
is_canonical_json(B) when is_binary(B) ->
true;
is_canonical_json(B) when is_boolean(B) ->
true;
is_canonical_json(Map) when is_map(Map) ->
maps:fold(
fun(_K, V, true) ->
is_canonical_json(V);
(_K, _V, false) ->
false
end, true, Map);
is_canonical_json(List) when is_list(List) ->
lists:all(fun is_canonical_json/1, List);
is_canonical_json(_) ->
false.
base64_decode(B) ->
Fixed =
case size(B) rem 4 of

View file

@ -103,6 +103,7 @@
-define(ROOM_MESSAGE, <<"m.room.message">>).
-define(ROOM_HISTORY_VISIBILITY, <<"m.room.history_visibility">>).
-define(ROOM_TOPIC, <<"m.room.topic">>).
-define(ROOM_ALIASES, <<"m.room.aliases">>).
-define(MAX_DEPTH, 16#7FFFFFFFFFFFFFFF).
-define(MAX_TXN_RETRIES, 5).
@ -650,9 +651,7 @@ handle_event(cast, {join_direct, MatrixServer, RoomID, Sender, UserID}, State, D
Host, get, MatrixServer,
[<<"_matrix">>, <<"federation">>, <<"v1">>, <<"make_join">>,
RoomID, UserID],
[{<<"ver">>, <<"9">>},
{<<"ver">>, <<"10">>},
{<<"ver">>, <<"11">>}],
[{<<"ver">>, V} || V <- supported_versions()],
none,
[{timeout, 5000}],
[{sync, true},
@ -770,9 +769,7 @@ handle_event(cast, {join, UserJID, Packet}, _State, Data) ->
Host, get, MatrixServer,
[<<"_matrix">>, <<"federation">>, <<"v1">>, <<"make_join">>,
RoomID, UserID],
[{<<"ver">>, <<"9">>},
{<<"ver">>, <<"10">>},
{<<"ver">>, <<"11">>}],
[{<<"ver">>, V} || V <- supported_versions()],
none,
[{timeout, 5000}],
[{sync, true},
@ -1280,7 +1277,7 @@ check_event_auth(Event, StateMap, Data) ->
<<"ban">> ->
check_event_auth_ban(
Event, StateMap, Data);
<<"knock">> ->
<<"knock">> when (Data#data.room_version)#room_version.knock_join_rule ->
check_event_auth_knock(
Event, StateMap, Data);
_ ->
@ -1289,6 +1286,18 @@ check_event_auth(Event, StateMap, Data) ->
_ ->
false
end;
?ROOM_ALIASES when (Data#data.room_version)#room_version.special_case_aliases_auth ->
case Event#event.state_key of
undefined ->
false;
StateKey ->
case mod_matrix_gw:get_id_domain_exn(Event#event.sender) of
StateKey ->
true;
_ ->
false
end
end;
_ ->
Sender = Event#event.sender,
case maps:find({?ROOM_MEMBER, Sender}, StateMap) of
@ -1372,8 +1381,11 @@ check_event_auth_join(Event, StateMap, Data) ->
case {JoinRule, SenderMembership} of
{<<"public">>, _} -> true;
{<<"invite">>, <<"invite">>} -> true;
{<<"knock">>, <<"invite">>} -> true;
{<<"restricted">>, <<"invite">>} ->
{<<"knock">>, <<"invite">>}
when (Data#data.room_version)#room_version.knock_join_rule ->
true;
{<<"restricted">>, <<"invite">>}
when (Data#data.room_version)#room_version.restricted_join_rule ->
%% TODO
true;
{<<"knock_restricted">>, <<"invite">>}
@ -1442,7 +1454,7 @@ check_event_auth_leave(Event, StateMap, Data) ->
case SenderMembership of
<<"invite">> -> true;
<<"join">> -> true;
<<"knock">> -> true;
<<"knock">> when (Data#data.room_version)#room_version.knock_join_rule -> true;
_ -> false
end;
_ ->
@ -1609,6 +1621,13 @@ check_event_auth_power_levels(Event, StateMap, Data) ->
try
case Event#event.json of
#{<<"content">> := NewPL = #{<<"users">> := Users}} when is_map(Users) ->
CheckKeys =
case (Data#data.room_version)#room_version.limit_notifications_power_levels of
false ->
[<<"events">>, <<"users">>];
true ->
[<<"events">>, <<"users">>, <<"notifications">>]
end,
case (Data#data.room_version)#room_version.enforce_int_power_levels of
true ->
lists:foreach(
@ -1632,7 +1651,7 @@ check_event_auth_power_levels(Event, StateMap, Data) ->
end
end, [], NewMap)
end,
[<<"events">>, <<"users">>, <<"notifications">>]);
CheckKeys);
false ->
ok
end,
@ -1677,7 +1696,7 @@ check_event_auth_power_levels(Event, StateMap, Data) ->
end
end, [], maps:merge(OldMap, NewMap))
end,
[<<"events">>, <<"users">>, <<"notifications">>]),
CheckKeys),
true;
_ ->
true
@ -1772,7 +1791,7 @@ fill_event(JSON, Data) ->
_ -> []
end
end,
compute_event_auth_keys(JSON))),
compute_event_auth_keys(JSON, Data#data.room_version))),
{JSON#{<<"auth_events">> => AuthEvents,
<<"depth">> => Depth2,
<<"origin">> => MatrixServer,
@ -1923,7 +1942,8 @@ get_latest_events(Pid) ->
check_event_signature(Host, Event) ->
PrunedEvent = mod_matrix_gw:prune_event(Event#event.json,
Event#event.room_version),
mod_matrix_gw_s2s:check_signature(Host, PrunedEvent).
mod_matrix_gw_s2s:check_signature(Host, PrunedEvent,
Event#event.room_version).
find_event(Pid, EventID) ->
gen_statem:call(Pid, {find_event, EventID}).
@ -2526,8 +2546,85 @@ find_power_level_event(EventID, Data) ->
end, undefined, Event#event.auth_events).
binary_to_room_version(<<"4">>) ->
#room_version{id = <<"4">>,
enforce_key_validity = false,
special_case_aliases_auth = true,
strict_canonicaljson = false,
limit_notifications_power_levels = false,
knock_join_rule = false,
restricted_join_rule = false,
restricted_join_rule_fix = false,
knock_restricted_join_rule = false,
enforce_int_power_levels = false,
implicit_room_creator = false,
updated_redaction_rules = false
};
binary_to_room_version(<<"5">>) ->
#room_version{id = <<"5">>,
enforce_key_validity = true,
special_case_aliases_auth = true,
strict_canonicaljson = false,
limit_notifications_power_levels = false,
knock_join_rule = false,
restricted_join_rule = false,
restricted_join_rule_fix = false,
knock_restricted_join_rule = false,
enforce_int_power_levels = false,
implicit_room_creator = false,
updated_redaction_rules = false
};
binary_to_room_version(<<"6">>) ->
#room_version{id = <<"6">>,
enforce_key_validity = true,
special_case_aliases_auth = false,
strict_canonicaljson = true,
limit_notifications_power_levels = true,
knock_join_rule = false,
restricted_join_rule = false,
restricted_join_rule_fix = false,
knock_restricted_join_rule = false,
enforce_int_power_levels = false,
implicit_room_creator = false,
updated_redaction_rules = false
};
binary_to_room_version(<<"7">>) ->
#room_version{id = <<"7">>,
enforce_key_validity = true,
special_case_aliases_auth = false,
strict_canonicaljson = true,
limit_notifications_power_levels = true,
knock_join_rule = true,
restricted_join_rule = false,
restricted_join_rule_fix = false,
knock_restricted_join_rule = false,
enforce_int_power_levels = false,
implicit_room_creator = false,
updated_redaction_rules = false
};
binary_to_room_version(<<"8">>) ->
#room_version{id = <<"8">>,
enforce_key_validity = true,
special_case_aliases_auth = false,
strict_canonicaljson = true,
limit_notifications_power_levels = true,
knock_join_rule = true,
restricted_join_rule = true,
restricted_join_rule_fix = false,
knock_restricted_join_rule = false,
enforce_int_power_levels = false,
implicit_room_creator = false,
updated_redaction_rules = false
};
binary_to_room_version(<<"9">>) ->
#room_version{id = <<"9">>,
enforce_key_validity = true,
special_case_aliases_auth = false,
strict_canonicaljson = true,
limit_notifications_power_levels = true,
knock_join_rule = true,
restricted_join_rule = true,
restricted_join_rule_fix = true,
knock_restricted_join_rule = false,
enforce_int_power_levels = false,
implicit_room_creator = false,
@ -2535,6 +2632,13 @@ binary_to_room_version(<<"9">>) ->
};
binary_to_room_version(<<"10">>) ->
#room_version{id = <<"10">>,
enforce_key_validity = true,
special_case_aliases_auth = false,
strict_canonicaljson = true,
limit_notifications_power_levels = true,
knock_join_rule = true,
restricted_join_rule = true,
restricted_join_rule_fix = true,
knock_restricted_join_rule = true,
enforce_int_power_levels = true,
implicit_room_creator = false,
@ -2542,6 +2646,13 @@ binary_to_room_version(<<"10">>) ->
};
binary_to_room_version(<<"11">>) ->
#room_version{id = <<"11">>,
enforce_key_validity = true,
special_case_aliases_auth = false,
strict_canonicaljson = true,
limit_notifications_power_levels = true,
knock_join_rule = true,
restricted_join_rule = true,
restricted_join_rule_fix = true,
knock_restricted_join_rule = true,
enforce_int_power_levels = true,
implicit_room_creator = true,
@ -2550,6 +2661,10 @@ binary_to_room_version(<<"11">>) ->
binary_to_room_version(_) ->
false.
supported_versions() ->
[<<"4">>, <<"5">>, <<"6">>, <<"7">>, <<"8">>, <<"9">>,
<<"10">>, <<"11">>].
json_to_event(#{<<"type">> := Type,
<<"room_id">> := RoomID,
<<"depth">> := Depth,
@ -2562,6 +2677,17 @@ json_to_event(#{<<"type">> := Type,
is_list(AuthEvents) ->
StateKey = maps:get(<<"state_key">>, JSON, undefined),
EventID = mod_matrix_gw:get_event_id(JSON, RoomVersion),
case RoomVersion#room_version.strict_canonicaljson of
true ->
case mod_matrix_gw:is_canonical_json(JSON) of
true ->
ok;
false ->
throw(non_canonical_json)
end;
false ->
ok
end,
#event{id = EventID,
room_version = RoomVersion,
room_id = RoomID,
@ -3162,12 +3288,13 @@ new_room_id() ->
MatrixServer = mod_matrix_gw_opt:matrix_domain(Host),
<<$!, S/binary, $:, MatrixServer/binary>>.
compute_event_auth_keys(#{<<"type">> := ?ROOM_CREATE}) ->
compute_event_auth_keys(#{<<"type">> := ?ROOM_CREATE}, _RoomVersion) ->
[];
compute_event_auth_keys(#{<<"type">> := ?ROOM_MEMBER,
<<"sender">> := Sender,
<<"content">> := #{<<"membership">> := Membership} = Content,
<<"state_key">> := StateKey}) ->
<<"state_key">> := StateKey},
RoomVersion) ->
Common = [{?ROOM_CREATE, <<"">>},
{?ROOM_POWER_LEVELS, <<"">>},
{?ROOM_MEMBER, Sender},
@ -3175,7 +3302,8 @@ compute_event_auth_keys(#{<<"type">> := ?ROOM_MEMBER,
case Membership of
<<"join">> ->
case Content of
#{<<"join_authorised_via_users_server">> := AuthUser} ->
#{<<"join_authorised_via_users_server">> := AuthUser}
when RoomVersion#room_version.restricted_join_rule ->
[{?ROOM_MEMBER, AuthUser}, {?ROOM_JOIN_RULES, <<"">>} | Common];
_ ->
[{?ROOM_JOIN_RULES, <<"">>} | Common]
@ -3192,7 +3320,7 @@ compute_event_auth_keys(#{<<"type">> := ?ROOM_MEMBER,
_ ->
Common
end;
compute_event_auth_keys(#{<<"type">> := _, <<"sender">> := Sender}) ->
compute_event_auth_keys(#{<<"type">> := _, <<"sender">> := Sender}, _RoomVersion) ->
[{?ROOM_CREATE, <<"">>},
{?ROOM_POWER_LEVELS, <<"">>},
{?ROOM_MEMBER, Sender}].

View file

@ -28,7 +28,7 @@
%% API
-export([start_link/2, supervisor/1, create_db/0,
get_connection/2, check_auth/5, check_signature/2,
get_connection/2, check_auth/5, check_signature/3,
get_matrix_host_port/2]).
%% gen_statem callbacks
@ -38,6 +38,7 @@
-include("logger.hrl").
-include("ejabberd_http.hrl").
-include_lib("kernel/include/inet.hrl").
-include("mod_matrix_gw.hrl").
-record(matrix_s2s,
{to :: binary(),
@ -169,18 +170,21 @@ check_auth(Host, MatrixServer, AuthParams, Content, Request) ->
false
end.
check_signature(Host, JSON) ->
check_signature(Host, JSON, RoomVersion) ->
case JSON of
#{<<"sender">> := Sender,
<<"signatures">> := Sigs} ->
<<"signatures">> := Sigs,
<<"origin_server_ts">> := OriginServerTS} ->
MatrixServer = mod_matrix_gw:get_id_domain_exn(Sender),
case Sigs of
#{MatrixServer := #{} = KeySig} ->
case maps:next(maps:iterator(KeySig)) of
{KeyID, _Sig, _} ->
case catch get_key(Host, MatrixServer, KeyID) of
{ok, VerifyKey, _ValidUntil} ->
%% TODO: check ValidUntil
{ok, VerifyKey, ValidUntil} ->
if
not RoomVersion#room_version.enforce_key_validity or
OriginServerTS =< ValidUntil ->
case check_signature(JSON, MatrixServer, KeyID, VerifyKey) of
true ->
true;
@ -188,6 +192,9 @@ check_signature(Host, JSON) ->
?WARNING_MSG("Failed authentication: ~p", [JSON]),
false
end;
true ->
false
end;
_ ->
false
end;