mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix: tighten edge cases across services (#3158)
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
%% SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
-module(guild_voice_access_revoke_tests).
|
||||
-typing([eqwalizer]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-define(GUILD, 42).
|
||||
-define(USER, 10).
|
||||
-define(CHANNEL, 500).
|
||||
-define(ROLE, 999).
|
||||
|
||||
overwrite_deny_view_connect_disconnects_connected_user_test() ->
|
||||
State = guild_state:update_state(channel_update, deny_overwrite_update(), base_state(true)),
|
||||
Messages = collect_sync_messages(),
|
||||
?assert(disconnected(Messages)),
|
||||
?assertNot(granted_permissions(Messages)),
|
||||
?assert(guild_virtual_channel_access:has_virtual_access(?USER, ?CHANNEL, State)),
|
||||
?assertNot(guild_virtual_channel_access:has_voice_access(?USER, ?CHANNEL, State)).
|
||||
|
||||
overwrite_deny_connect_only_disconnects_connected_user_test() ->
|
||||
Update = channel_update_with_user_deny(constants:connect_permission()),
|
||||
_ = guild_state:update_state(channel_update, Update, base_state(true)),
|
||||
?assert(disconnected(collect_sync_messages())).
|
||||
|
||||
overwrite_deny_view_connect_disconnects_user_without_session_test() ->
|
||||
_ = guild_state:update_state(channel_update, deny_overwrite_update(), base_state(false)),
|
||||
?assert(disconnected(collect_sync_messages())).
|
||||
|
||||
role_removal_disconnects_connected_user_test() ->
|
||||
Update = #{<<"user">> => #{<<"id">> => integer_to_binary(?USER)}, <<"roles">> => []},
|
||||
_ = guild_state:update_state(guild_member_update, Update, base_state(true)),
|
||||
Messages = collect_sync_messages(),
|
||||
?assert(disconnected(Messages)),
|
||||
?assertNot(granted_permissions(Messages)).
|
||||
|
||||
moderator_granted_access_survives_revoke_test() ->
|
||||
State0 = guild_virtual_channel_access:add_virtual_access(?USER, ?CHANNEL, base_state(true)),
|
||||
State = guild_state:update_state(channel_update, deny_overwrite_update(), State0),
|
||||
Messages = collect_sync_messages(),
|
||||
?assertNot(disconnected(Messages)),
|
||||
?assert(guild_virtual_channel_access:has_voice_access(?USER, ?CHANNEL, State)).
|
||||
|
||||
view_only_access_grants_no_publish_rights_test() ->
|
||||
ViewConnect = constants:view_channel_permission() bor constants:connect_permission(),
|
||||
State0 = with_role_permissions(ViewConnect, base_state(true)),
|
||||
State1 = guild_virtual_channel_access:add_view_only_access(?USER, ?CHANNEL, State0),
|
||||
State = with_overwrites([user_deny(ViewConnect)], State1),
|
||||
?assertEqual(
|
||||
#{can_speak => false, can_stream => false, can_video => false},
|
||||
voice_utils:compute_voice_permissions(?USER, ?CHANNEL, State)
|
||||
).
|
||||
|
||||
explicit_access_replaces_view_only_mark_test() ->
|
||||
State0 = guild_virtual_channel_access:add_view_only_access(?USER, ?CHANNEL, #{}),
|
||||
?assert(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL, State0)),
|
||||
State1 = guild_virtual_channel_access:add_virtual_access(?USER, ?CHANNEL, State0),
|
||||
?assertNot(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL, State1)),
|
||||
?assert(guild_virtual_channel_access:has_voice_access(?USER, ?CHANNEL, State1)).
|
||||
|
||||
removing_access_clears_view_only_mark_test() ->
|
||||
State0 = guild_virtual_channel_access:add_view_only_access(?USER, ?CHANNEL, #{}),
|
||||
State1 = guild_virtual_channel_access:add_view_only_access(?USER, ?CHANNEL + 1, State0),
|
||||
State2 = guild_virtual_channel_access:remove_virtual_access(?USER, ?CHANNEL, State1),
|
||||
?assertNot(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL, State2)),
|
||||
?assert(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL + 1, State2)),
|
||||
State3 = guild_virtual_channel_access:remove_virtual_access(?USER, ?CHANNEL + 1, State2),
|
||||
?assertNot(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL + 1, State3)).
|
||||
|
||||
channel_delete_disconnects_everyone_in_channel_test() ->
|
||||
Self = self(),
|
||||
VoicePid = spawn(fun() ->
|
||||
receive
|
||||
Message -> Self ! {voice_server_got, Message}
|
||||
end
|
||||
end),
|
||||
State0 = (base_state(true))#{voice_server_pid => VoicePid},
|
||||
_ = guild_state:update_state(
|
||||
channel_delete,
|
||||
#{<<"id">> => integer_to_binary(?CHANNEL), <<"type">> => 2},
|
||||
State0
|
||||
),
|
||||
receive
|
||||
{voice_server_got, {'$gen_cast', {disconnect_all_voice_users_in_channel, Request}}} ->
|
||||
?assertEqual(#{channel_id => ?CHANNEL}, Request)
|
||||
after 500 ->
|
||||
exit(VoicePid, kill),
|
||||
?assert(false)
|
||||
end.
|
||||
|
||||
voice_server_disconnects_all_users_in_channel_test() ->
|
||||
Self = self(),
|
||||
ForceFun = fun(GId, ChId, UId, ConnId) ->
|
||||
Self ! {force_disconnect, GId, ChId, UId, ConnId},
|
||||
{ok, #{success => true}}
|
||||
end,
|
||||
GuildPid = spawn(fun() -> guild_state_reply_loop(ForceFun) end),
|
||||
State = #{
|
||||
guild_id => ?GUILD,
|
||||
guild_pid => GuildPid,
|
||||
voice_states => #{
|
||||
<<"conn-a">> => voice_state(<<"conn-a">>, 5, ?CHANNEL),
|
||||
<<"conn-b">> => voice_state(<<"conn-b">>, 6, ?CHANNEL),
|
||||
<<"conn-c">> => voice_state(<<"conn-c">>, 7, ?CHANNEL + 1)
|
||||
},
|
||||
pending_voice_connections => #{},
|
||||
recently_disconnected_voice_states => #{},
|
||||
e2ee_room_keys => #{}
|
||||
},
|
||||
try
|
||||
{noreply, NewState} = guild_voice_server:handle_cast(
|
||||
{disconnect_all_voice_users_in_channel, #{channel_id => ?CHANNEL}}, State
|
||||
),
|
||||
?assertEqual([<<"conn-c">>], maps:keys(maps:get(voice_states, NewState))),
|
||||
Disconnected = lists:sort([receive_force_disconnect(), receive_force_disconnect()]),
|
||||
?assertEqual(
|
||||
[{?GUILD, ?CHANNEL, 5, <<"conn-a">>}, {?GUILD, ?CHANNEL, 6, <<"conn-b">>}],
|
||||
Disconnected
|
||||
)
|
||||
after
|
||||
exit(GuildPid, kill)
|
||||
end.
|
||||
|
||||
receive_force_disconnect() ->
|
||||
receive
|
||||
{force_disconnect, GId, ChId, UId, ConnId} -> {GId, ChId, UId, ConnId}
|
||||
after 500 -> erlang:error(missing_force_disconnect)
|
||||
end.
|
||||
|
||||
guild_state_reply_loop(ForceFun) ->
|
||||
GuildState = #{
|
||||
id => ?GUILD,
|
||||
data => #{<<"guild">> => #{<<"owner_id">> => <<"1">>}},
|
||||
sessions => #{},
|
||||
test_force_disconnect_fun => ForceFun
|
||||
},
|
||||
receive
|
||||
{'$gen_call', From, {get_voice_guild_state}} ->
|
||||
gen_server:reply(From, GuildState),
|
||||
guild_state_reply_loop(ForceFun);
|
||||
_ ->
|
||||
guild_state_reply_loop(ForceFun)
|
||||
end.
|
||||
|
||||
voice_state(ConnId, UserId, ChannelId) ->
|
||||
#{
|
||||
<<"guild_id">> => integer_to_binary(?GUILD),
|
||||
<<"user_id">> => integer_to_binary(UserId),
|
||||
<<"channel_id">> => integer_to_binary(ChannelId),
|
||||
<<"connection_id">> => ConnId
|
||||
}.
|
||||
|
||||
base_state(WithSession) ->
|
||||
ok = drain(),
|
||||
Self = self(),
|
||||
SyncFun = fun(GId, ChId, UId, ConnId, Perms) ->
|
||||
Self ! {synced, GId, ChId, UId, ConnId, Perms}
|
||||
end,
|
||||
Sessions =
|
||||
case WithSession of
|
||||
true ->
|
||||
#{
|
||||
<<"sess">> => #{
|
||||
session_id => <<"sess">>,
|
||||
user_id => ?USER,
|
||||
pid => spawn(fun sink/0),
|
||||
viewable_channels => #{?CHANNEL => true}
|
||||
}
|
||||
};
|
||||
false ->
|
||||
#{}
|
||||
end,
|
||||
RolePerms =
|
||||
constants:view_channel_permission() bor constants:connect_permission() bor
|
||||
constants:speak_permission(),
|
||||
#{
|
||||
id => ?GUILD,
|
||||
sessions => Sessions,
|
||||
voice_states => #{
|
||||
<<"conn">> => (voice_state(<<"conn">>, ?USER, ?CHANNEL))#{<<"deaf">> => false}
|
||||
},
|
||||
member_list_subscriptions => guild_member_list_subs:new(),
|
||||
test_permission_sync_fun => SyncFun,
|
||||
data => #{
|
||||
<<"guild">> => #{<<"owner_id">> => <<"1">>},
|
||||
<<"roles">> => roles(RolePerms),
|
||||
<<"members">> => [
|
||||
#{
|
||||
<<"user">> => #{<<"id">> => integer_to_binary(?USER)},
|
||||
<<"roles">> => [integer_to_binary(?ROLE)]
|
||||
}
|
||||
],
|
||||
<<"channels">> => [voice_channel([])]
|
||||
}
|
||||
}.
|
||||
|
||||
voice_channel(Overwrites) ->
|
||||
#{
|
||||
<<"id">> => integer_to_binary(?CHANNEL),
|
||||
<<"type">> => 2,
|
||||
<<"permission_overwrites">> => Overwrites
|
||||
}.
|
||||
|
||||
with_overwrites(Overwrites, State) ->
|
||||
Data = maps:get(data, State),
|
||||
State#{data => Data#{<<"channels">> => [voice_channel(Overwrites)]}}.
|
||||
|
||||
with_role_permissions(Permissions, State) ->
|
||||
Data = maps:get(data, State),
|
||||
State#{data => Data#{<<"roles">> => roles(Permissions)}}.
|
||||
|
||||
roles(Permissions) ->
|
||||
[
|
||||
#{
|
||||
<<"id">> => integer_to_binary(?ROLE),
|
||||
<<"permissions">> => integer_to_binary(Permissions)
|
||||
},
|
||||
#{<<"id">> => integer_to_binary(?GUILD), <<"permissions">> => <<"0">>}
|
||||
].
|
||||
|
||||
user_deny(Deny) ->
|
||||
#{
|
||||
<<"id">> => integer_to_binary(?USER),
|
||||
<<"type">> => 1,
|
||||
<<"allow">> => <<"0">>,
|
||||
<<"deny">> => integer_to_binary(Deny)
|
||||
}.
|
||||
|
||||
channel_update_with_user_deny(Deny) ->
|
||||
voice_channel([user_deny(Deny)]).
|
||||
|
||||
deny_overwrite_update() ->
|
||||
channel_update_with_user_deny(
|
||||
constants:view_channel_permission() bor constants:connect_permission()
|
||||
).
|
||||
|
||||
sink() ->
|
||||
receive
|
||||
stop -> ok;
|
||||
_ -> sink()
|
||||
end.
|
||||
|
||||
drain() ->
|
||||
receive
|
||||
_ -> drain()
|
||||
after 0 -> ok
|
||||
end.
|
||||
|
||||
collect_sync_messages() ->
|
||||
collect_sync_messages([]).
|
||||
|
||||
collect_sync_messages(Acc) ->
|
||||
receive
|
||||
{synced, _, _, _, _, _} = Message -> collect_sync_messages([Message | Acc])
|
||||
after 300 -> lists:reverse(Acc)
|
||||
end.
|
||||
|
||||
disconnected(Messages) ->
|
||||
lists:any(
|
||||
fun({synced, _, _, _, _, Perms}) -> maps:get(disconnected, Perms, false) =:= true end,
|
||||
Messages
|
||||
).
|
||||
|
||||
granted_permissions(Messages) ->
|
||||
lists:any(
|
||||
fun({synced, _, _, _, _, Perms}) -> maps:is_key(can_speak, Perms) end,
|
||||
Messages
|
||||
).
|
||||
Reference in New Issue
Block a user