1
0
Fork 0
mirror of https://github.com/processone/ejabberd synced 2025-10-03 09:49:18 +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, -record(room_version,
{id :: binary(), {id :: binary(),
%% use the same field names as in Synapse %% 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(), knock_restricted_join_rule :: boolean(),
enforce_int_power_levels :: boolean(), enforce_int_power_levels :: boolean(),
implicit_room_creator :: boolean(), implicit_room_creator :: boolean(),

View file

@ -41,6 +41,7 @@
handle_info/2, terminate/2, code_change/3, handle_info/2, terminate/2, code_change/3,
depends/2, mod_opt_type/1, mod_options/1, mod_doc/0]). depends/2, mod_opt_type/1, mod_options/1, mod_doc/0]).
-export([parse_auth/1, encode_canonical_json/1, -export([parse_auth/1, encode_canonical_json/1,
is_canonical_json/1,
get_id_domain_exn/1, get_id_domain_exn/1,
base64_decode/1, base64_encode/1, base64_decode/1, base64_encode/1,
prune_event/2, get_event_id/2, content_hash/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 %% TODO: check type and userid
Host = ejabberd_config:get_myname(), Host = ejabberd_config:get_myname(),
PrunedEvent = prune_event(Event, RoomVersion), 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)}]), %?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) of case mod_matrix_gw_s2s:check_signature(Host, PrunedEvent, RoomVersion) of
true -> true ->
case get_pruned_event_id(PrunedEvent) of case get_pruned_event_id(PrunedEvent) of
EventID -> EventID ->
@ -629,9 +630,15 @@ prune_event(#{<<"type">> := Type, <<"content">> := Content} = Event,
Content2 = Content2 =
case Type of case Type of
<<"m.room.member">> -> <<"m.room.member">> ->
C3 = maps:with([<<"membership">>, C3 =
<<"join_authorised_via_users_server">>], case RoomVersion#room_version.restricted_join_rule_fix of
Content), true ->
maps:with([<<"membership">>,
<<"join_authorised_via_users_server">>],
Content);
false ->
maps:with([<<"membership">>], Content)
end,
case RoomVersion#room_version.updated_redaction_rules of case RoomVersion#room_version.updated_redaction_rules of
false -> false ->
C3; C3;
@ -653,7 +660,12 @@ prune_event(#{<<"type">> := Type, <<"content">> := Content} = Event,
Content Content
end; end;
<<"m.room.join_rules">> -> <<"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">> -> <<"m.room.power_levels">> ->
case RoomVersion#room_version.updated_redaction_rules of case RoomVersion#room_version.updated_redaction_rules of
false -> false ->
@ -677,6 +689,8 @@ prune_event(#{<<"type">> := Type, <<"content">> := Content} = Event,
true -> true ->
maps:with([<<"redacts">>], Content) maps:with([<<"redacts">>], Content)
end; end;
<<"m.room.aliases">> when RoomVersion#room_version.special_case_aliases_auth ->
maps:with([<<"aliases">>], Content);
_ -> #{} _ -> #{}
end, end,
Event2#{<<"content">> := Content2}. Event2#{<<"content">> := Content2}.
@ -716,6 +730,27 @@ sort_json(List) when is_list(List) ->
sort_json(JSON) -> sort_json(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) -> base64_decode(B) ->
Fixed = Fixed =
case size(B) rem 4 of case size(B) rem 4 of

View file

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

View file

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