25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-20 16:15:59 +01:00

Add more tests for privacy lists and blocking command

This commit is contained in:
Evgeniy Khramtsov 2016-10-22 13:01:45 +03:00
parent 67720c7713
commit f6236d456d
11 changed files with 1508 additions and 595 deletions

View File

@ -881,6 +881,8 @@ decode_element(#xmlel{} = El, StateName, StateData) ->
end
catch error:{xmpp_codec, Why} ->
NS = xmpp:get_ns(El),
fsm_next_state(
StateName,
case xmpp:is_stanza(El) of
true ->
Lang = xmpp:get_lang(El),
@ -888,11 +890,11 @@ decode_element(#xmlel{} = El, StateName, StateData) ->
send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
false when NS == ?NS_STREAM_MGMT_2; NS == ?NS_STREAM_MGMT_3 ->
Err = #sm_failed{reason = 'bad-request', xmlns = NS},
send_element(StateData, Err);
send_element(StateData, Err),
StateData;
false ->
ok
end,
fsm_next_state(StateName, StateData)
StateData
end)
end.
wait_for_bind({xmlstreamelement, El}, StateData) ->
@ -957,13 +959,14 @@ wait_for_bind(closed, StateData) ->
wait_for_bind(stop, StateData) ->
{stop, normal, StateData};
wait_for_bind(Pkt, StateData) ->
fsm_next_state(
wait_for_bind,
case xmpp:is_stanza(Pkt) of
true ->
send_error(StateData, Pkt, xmpp:err_not_acceptable());
false ->
ok
end,
fsm_next_state(wait_for_bind, StateData).
StateData
end).
-spec open_session(state()) -> {ok, state()} | {error, stanza_error()}.
open_session(StateData) ->
@ -1315,27 +1318,23 @@ handle_info({route, From, To, Packet}, StateName, StateData) when ?is_stanza(Pac
allow ->
{true, StateData};
deny ->
Err = xmpp:make_error(
Packet,
ejabberd_router:route_error(
To, From, Packet,
xmpp:err_service_unavailable()),
ejabberd_router:route(To, From, Err),
{false, StateData}
end;
_ ->
Err = xmpp:make_error(Packet, xmpp:err_forbidden()),
ejabberd_router:route(To, From, Err),
ejabberd_router:route_error(
To, From, Packet, xmpp:err_forbidden()),
{false, StateData}
end;
_ ->
case privacy_check_packet(StateData, From, To, Packet, in) of
allow ->
{true, StateData};
deny when T == get; T == set ->
Err = xmpp:make_error(
Packet, xmpp:err_service_unavailable()),
ejabberd_router:route(To, From, Err),
{false, StateData};
deny ->
ejabberd_router:route_error(
To, From, Packet, xmpp:err_service_unavailable()),
{false, StateData}
end
end;
@ -1345,13 +1344,11 @@ handle_info({route, From, To, Packet}, StateName, StateData) when ?is_stanza(Pac
{true, StateData};
deny ->
case T of
error -> ok;
groupchat -> ok;
headline -> ok;
_ ->
Err = xmpp:make_error(
Packet, xmpp:err_service_unavailable()),
ejabberd_router:route(To, From, Err)
ejabberd_router:route_error(
To, From, Packet, xmpp:err_service_unavailable())
end,
{false, StateData}
end
@ -1572,14 +1569,14 @@ send_element(StateData, #xmlel{} = El) ->
send_element(StateData, Pkt) ->
send_element(StateData, xmpp:encode(Pkt, ?NS_CLIENT)).
-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
-spec send_error(state(), xmlel() | stanza(), stanza_error()) -> state().
send_error(StateData, Stanza, Error) ->
Type = xmpp:get_type(Stanza),
if Type == error; Type == result;
Type == <<"error">>; Type == <<"result">> ->
ok;
StateData;
true ->
send_element(StateData, xmpp:make_error(Stanza, Error))
send_stanza(StateData, xmpp:make_error(Stanza, Error))
end.
-spec send_stanza(state(), xmpp_element()) -> state().
@ -1754,47 +1751,56 @@ presence_track(From, To, Packet, StateData) ->
LTo = jid:tolower(To),
User = StateData#state.user,
Server = StateData#state.server,
case Type of
Lang = StateData#state.lang,
case privacy_check_packet(StateData, From, To, Packet, out) of
deny ->
ErrText = <<"Your active privacy list has denied "
"the routing of this stanza.">>,
Err = xmpp:err_not_acceptable(ErrText, Lang),
send_error(StateData, xmpp:set_from_to(Packet, From, To), Err);
allow when Type == subscribe; Type == subscribed;
Type == unsubscribe; Type == unsubscribed ->
Access = gen_mod:get_module_opt(Server, mod_roster, access,
fun(A) when is_atom(A) -> A end,
all),
MyBareJID = jid:make(User, Server, <<"">>),
case acl:match_rule(Server, Access, MyBareJID) of
deny ->
ErrText = <<"Denied by ACL">>,
Err = xmpp:err_forbidden(ErrText, Lang),
send_error(StateData, xmpp:set_from_to(Packet, From, To), Err);
allow ->
ejabberd_hooks:run(roster_out_subscription,
Server,
[User, Server, To, Type]),
ejabberd_router:route(jid:remove_resource(From), To, Packet),
StateData
end;
allow when Type == error; Type == probe ->
ejabberd_router:route(From, To, Packet),
StateData;
allow ->
ejabberd_router:route(From, To, Packet),
A = case Type of
available ->
?SETS:add_element(LTo, StateData#state.pres_a);
unavailable ->
A = ?SETS:del_element(LTo, StateData#state.pres_a),
check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet);
subscribe ->
try_roster_subscribe(subscribe, User, Server, From, To, Packet, StateData);
subscribed ->
ejabberd_hooks:run(roster_out_subscription, Server,
[User, Server, To, subscribed]),
check_privacy_route(From, StateData,
jid:remove_resource(From), To, Packet);
unsubscribe ->
try_roster_subscribe(unsubscribe, User, Server, From, To, Packet, StateData);
unsubscribed ->
ejabberd_hooks:run(roster_out_subscription, Server,
[User, Server, To, unsubscribed]),
check_privacy_route(From, StateData,
jid:remove_resource(From), To, Packet);
error ->
check_privacy_route(From, StateData, From, To, Packet);
probe ->
check_privacy_route(From, StateData, From, To, Packet);
_ ->
A = (?SETS):add_element(LTo, StateData#state.pres_a),
check_privacy_route(From, StateData#state{pres_a = A}, From, To, Packet)
?SETS:del_element(LTo, StateData#state.pres_a)
end,
StateData#state{pres_a = A}
end.
-spec check_privacy_route(jid(), state(), jid(), jid(), stanza()) -> state().
check_privacy_route(From, StateData, FromRoute, To,
Packet) ->
case privacy_check_packet(StateData, From, To, Packet,
out)
of
out) of
deny ->
Lang = StateData#state.lang,
ErrText = <<"Your active privacy list has denied "
"the routing of this stanza.">>,
Err = xmpp:make_error(
xmpp:set_from_to(Packet, From, To),
xmpp:err_not_acceptable(ErrText, Lang)),
send_stanza(StateData, Err);
Err = xmpp:err_not_acceptable(ErrText, Lang),
send_error(StateData, xmpp:set_from_to(Packet, From, To), Err);
allow ->
ejabberd_router:route(FromRoute, To, Packet),
StateData
@ -1815,24 +1821,6 @@ is_privacy_allow(StateData, From, To, Packet, Dir) ->
allow ==
privacy_check_packet(StateData, From, To, Packet, Dir).
%%% Check ACL before allowing to send a subscription stanza
-spec try_roster_subscribe(subscribe | unsubscribe, binary(), binary(),
jid(), jid(), presence(), state()) -> state().
try_roster_subscribe(Type, User, Server, From, To, Packet, StateData) ->
JID1 = jid:make(User, Server, <<"">>),
Access = gen_mod:get_module_opt(Server, mod_roster, access, fun(A) when is_atom(A) -> A end, all),
case acl:match_rule(Server, Access, JID1) of
deny ->
%% Silently drop this (un)subscription request
StateData;
allow ->
ejabberd_hooks:run(roster_out_subscription,
Server,
[User, Server, To, Type]),
check_privacy_route(From, StateData, jid:remove_resource(From),
To, Packet)
end.
%% Send presence when disconnecting
-spec presence_broadcast(state(), jid(), ?SETS:set(), presence()) -> ok.
presence_broadcast(StateData, From, JIDSet, Packet) ->
@ -1980,7 +1968,7 @@ process_privacy_iq(#iq{from = From, to = To,
privacy_iq_set,
StateData#state.server,
{error, xmpp:err_feature_not_implemented(Txt, Lang)},
[IQ])
[IQ, StateData#state.privacy_list])
of
{result, R, NewPrivList} ->
{{result, R},
@ -2522,9 +2510,8 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
false ->
fun(From, To, El, _Time) ->
Txt = <<"User session terminated">>,
Err = xmpp:make_error(
El, xmpp:err_service_unavailable(Txt, Lang)),
ejabberd_router:route(To, From, Err)
ejabberd_router:route_error(
To, From, El, xmpp:err_service_unavailable(Txt, Lang))
end
end,
F = fun(From, _To, #presence{}, _Time) ->
@ -2532,9 +2519,8 @@ handle_unacked_stanzas(#state{mgmt_state = MgmtState} = StateData)
[jid:to_string(From)]);
(From, To, #iq{} = El, _Time) ->
Txt = <<"User session terminated">>,
Err = xmpp:make_error(
El, xmpp:err_service_unavailable(Txt, Lang)),
ejabberd_router:route(To, From, Err);
ejabberd_router:route_error(
To, From, El, xmpp:err_service_unavailable(Txt, Lang));
(From, To, El, Time) ->
%% We'll drop the stanza if it was <forwarded/> by some
%% encapsulating protocol as per XEP-0297. One such protocol is

View File

@ -486,7 +486,7 @@ process_privacy(#privacy_query{lists = Lists,
from = JID, to = JID, sub_els = [PrivacyQuery]},
Txt = <<"No module is handling this query">>,
Error = {error, xmpp:err_feature_not_implemented(Txt, ?MYLANG)},
case mod_privacy:process_iq_set(Error, IQ) of
case mod_privacy:process_iq_set(Error, IQ, #userlist{}) of
{error, #stanza_error{reason = Reason}} = Err ->
if Reason == 'item-not-found', Lists == [],
Active == undefined, Default /= undefined ->

View File

@ -31,6 +31,7 @@
-export([start/4,
connect/3,
connect/4,
connect/5,
starttls/2,
starttls/3,
compress/1,
@ -125,19 +126,21 @@ start(Module, SockMod, Socket, Opts) ->
end.
connect(Addr, Port, Opts) ->
connect(Addr, Port, Opts, infinity).
connect(Addr, Port, Opts, infinity, self()).
connect(Addr, Port, Opts, Timeout) ->
connect(Addr, Port, Opts, Timeout, self()).
connect(Addr, Port, Opts, Timeout, Owner) ->
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
{ok, Socket} ->
Receiver = ejabberd_receiver:start(Socket, gen_tcp,
none),
SocketData = #socket_state{sockmod = gen_tcp,
socket = Socket, receiver = Receiver},
Pid = self(),
case gen_tcp:controlling_process(Socket, Receiver) of
ok ->
ejabberd_receiver:become_controller(Receiver, Pid),
ejabberd_receiver:become_controller(Receiver, Owner),
{ok, SocketData};
{error, _Reason} = Error -> gen_tcp:close(Socket), Error
end;

View File

@ -53,6 +53,7 @@
-include("ejabberd.hrl").
-include("ejabberd_commands.hrl").
-include("mod_roster.hrl").
-include("mod_privacy.hrl").
-include("ejabberd_sm.hrl").
-include("xmpp.hrl").
@ -1380,11 +1381,12 @@ privacy_set(Username, Host, QueryS) ->
To = jid:make(Host),
QueryEl = fxml_stream:parse_element(QueryS),
SubEl = xmpp:decode(QueryEl),
IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl]},
IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl],
from = From, to = To},
ejabberd_hooks:run_fold(privacy_iq_set,
Host,
{error, xmpp:err_feature_not_implemented()},
[From, To, IQ]),
[IQ, #userlist{}]),
ok.
%%%

View File

@ -30,7 +30,7 @@
-protocol({xep, 191, '1.2'}).
-export([start/2, stop/1, process_iq/1,
process_iq_set/2, process_iq_get/3, mod_opt_type/1, depends/2]).
process_iq_set/3, process_iq_get/3, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@ -85,10 +85,11 @@ process_iq_get(Acc, _, _) -> Acc.
-spec process_iq_set({error, stanza_error()} |
{result, xmpp_element() | undefined} |
{result, xmpp_element() | undefined, userlist()},
iq()) -> {error, stanza_error()} |
iq(), userlist()) ->
{error, stanza_error()} |
{result, xmpp_element() | undefined} |
{result, xmpp_element() | undefined, userlist()}.
process_iq_set(Acc, #iq{from = From, lang = Lang, sub_els = [SubEl]}) ->
process_iq_set(Acc, #iq{from = From, lang = Lang, sub_els = [SubEl]}, _) ->
#jid{luser = LUser, lserver = LServer} = From,
case SubEl of
#block{items = []} ->
@ -105,7 +106,7 @@ process_iq_set(Acc, #iq{from = From, lang = Lang, sub_els = [SubEl]}) ->
_ ->
Acc
end;
process_iq_set(Acc, _) -> Acc.
process_iq_set(Acc, _, _) -> Acc.
-spec list_to_blocklist_jids([listitem()], [ljid()]) -> [ljid()].
list_to_blocklist_jids([], JIDs) -> JIDs;

View File

@ -32,7 +32,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, process_iq/1, export/1, import/1,
process_iq_set/2, process_iq_get/3, get_user_list/3,
process_iq_set/3, process_iq_get/3, get_user_list/3,
check_packet/6, remove_user/2, encode_list_item/1,
is_list_needdb/1, updated_list/3,
item_to_xml/1, get_user_lists/2, import/3,
@ -103,6 +103,12 @@ process_iq(IQ) ->
-spec process_iq_get({error, stanza_error()} | {result, xmpp_element() | undefined},
iq(), userlist()) -> {error, stanza_error()} |
{result, xmpp_element() | undefined}.
process_iq_get(_, #iq{lang = Lang,
sub_els = [#privacy_query{default = Default,
active = Active}]},
_) when Default /= undefined; Active /= undefined ->
Txt = <<"Only <list/> element is allowed in this query">>,
{error, xmpp:err_bad_request(Txt, Lang)};
process_iq_get(_, #iq{from = From, lang = Lang,
sub_els = [#privacy_query{lists = Lists}]},
#userlist{name = Active}) ->
@ -205,7 +211,7 @@ encode_value(Type, Val) ->
listitem_value().
decode_value(Type, Value) ->
case Type of
jid -> jid:from_string(Value);
jid -> jid:tolower(jid:from_string(Value));
subscription ->
case Value of
<<"from">> -> from;
@ -213,35 +219,37 @@ decode_value(Type, Value) ->
<<"both">> -> both;
<<"none">> -> none
end;
group -> Value;
group when Value /= <<"">> -> Value;
undefined -> none
end.
-spec process_iq_set({error, stanza_error()} |
{result, xmpp_element() | undefined} |
{result, xmpp_element() | undefined, userlist()},
iq()) -> {error, stanza_error()} |
iq(), #userlist{}) ->
{error, stanza_error()} |
{result, xmpp_element() | undefined} |
{result, xmpp_element() | undefined, userlist()}.
process_iq_set(_, #iq{from = From, lang = Lang,
sub_els = [#privacy_query{default = Default,
active = Active,
lists = Lists}]}) ->
lists = Lists}]},
#userlist{} = UserList) ->
#jid{luser = LUser, lserver = LServer} = From,
case Lists of
[#privacy_list{items = Items, name = ListName}]
when Default == undefined, Active == undefined ->
process_lists_set(LUser, LServer, ListName, Items, Lang);
process_lists_set(LUser, LServer, ListName, Items, UserList, Lang);
[] when Default == undefined, Active /= undefined ->
process_active_set(LUser, LServer, Active, Lang);
[] when Active == undefined, Default /= undefined ->
process_default_set(LUser, LServer, Default, Lang);
_ ->
Txt = <<"There should be exactly one element in this query: "
"<list/>, <active/> or <default/>">>,
Txt = <<"The stanza MUST contain only one <active/> element, "
"one <default/> element, or one <list/> element">>,
{error, xmpp:err_bad_request(Txt, Lang)}
end;
process_iq_set(Acc, _) ->
process_iq_set(Acc, _, _) ->
Acc.
-spec process_default_set(binary(), binary(), none | binary(),
@ -286,13 +294,20 @@ set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
Mod:set_privacy_list(Privacy).
-spec process_lists_set(binary(), binary(), binary(), [privacy_item()],
binary()) -> {error, stanza_error()} | {result, undefined}.
process_lists_set(LUser, LServer, Name, [], Lang) ->
#userlist{}, binary()) -> {error, stanza_error()} |
{result, undefined}.
process_lists_set(_LUser, _LServer, Name, [], #userlist{name = Name}, Lang) ->
Txt = <<"Cannot remove active list">>,
{error, xmpp:err_conflict(Txt, Lang)};
process_lists_set(LUser, LServer, Name, [], _UserList, Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:remove_privacy_list(LUser, LServer, Name) of
{atomic, conflict} ->
Txt = <<"Cannot remove default list">>,
{error, xmpp:err_conflict(Txt, Lang)};
{atomic, not_found} ->
Txt = <<"No privacy list with this name found">>,
{error, xmpp:err_item_not_found(Txt, Lang)};
{atomic, ok} ->
ejabberd_sm:route(jid:make(LUser, LServer,
<<"">>),
@ -308,7 +323,7 @@ process_lists_set(LUser, LServer, Name, [], Lang) ->
Txt = <<"Database failure">>,
{error, xmpp:err_internal_server_error(Txt, Lang)}
end;
process_lists_set(LUser, LServer, Name, Items, Lang) ->
process_lists_set(LUser, LServer, Name, Items, _UserList, Lang) ->
case catch lists:map(fun decode_item/1, Items) of
{error, Why} ->
Txt = xmpp:format_error(Why),
@ -358,9 +373,7 @@ decode_item(#privacy_item{order = Order,
action = Action,
type = Type,
value = Value},
if MatchMessage and MatchIQ and MatchPresenceIn and MatchPresenceOut ->
ListItem#listitem{match_all = true};
not (MatchMessage or MatchIQ or MatchPresenceIn or MatchPresenceOut) ->
if not (MatchMessage or MatchIQ or MatchPresenceIn or MatchPresenceOut) ->
ListItem#listitem{match_all = true};
true ->
ListItem#listitem{match_iq = MatchIQ,
@ -468,16 +481,10 @@ check_packet_aux([Item | List], PType, JID,
Item,
case is_ptype_match(Item, PType) of
true ->
case Type of
none -> Action;
_ ->
case is_type_match(Type, Value, JID, Subscription,
Groups)
of
case is_type_match(Type, Value, JID, Subscription, Groups) of
true -> Action;
false ->
check_packet_aux(List, PType, JID, Subscription, Groups)
end
end;
false ->
check_packet_aux(List, PType, JID, Subscription, Groups)
@ -499,8 +506,10 @@ is_ptype_match(Item, PType) ->
end
end.
-spec is_type_match(jid | subscription | group, listitem_value(),
-spec is_type_match(none | jid | subscription | group, listitem_value(),
ljid(), none | both | from | to, [binary()]) -> boolean().
is_type_match(none, _Value, _JID, _Subscription, _Groups) ->
true;
is_type_match(Type, Value, JID, Subscription, Groups) ->
case Type of
jid ->

View File

@ -49,7 +49,7 @@
get_jid_info/4, encode_item/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2,
roster_versioning_enabled/1, roster_version/2,
mod_opt_type/1, set_roster/1, depends/2]).
mod_opt_type/1, set_roster/1, del_roster/3, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@ -297,6 +297,13 @@ set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
roster_subscribe_t(LUser, LServer, LJID, Item)
end).
del_roster(LUser, LServer, LJID) ->
transaction(
LServer,
fun() ->
del_roster_t(LUser, LServer, LJID)
end).
encode_item(Item) ->
#roster_item{jid = jid:make(Item#roster.jid),
name = Item#roster.name,

File diff suppressed because it is too large Load Diff

View File

@ -425,6 +425,7 @@ muc_history_master(Config) ->
ServerHost = ?config(server_host, Config),
MyNick = ?config(nick, Config),
MyNickJID = jid:replace_resource(Room, MyNick),
PeerNickJID = peer_muc_jid(Config),
Size = gen_mod:get_module_opt(ServerHost, mod_muc, history_size,
fun(I) when is_integer(I), I>=0 -> I end,
20),
@ -439,9 +440,14 @@ muc_history_master(Config) ->
#message{type = groupchat, from = MyNickJID,
body = Body} = recv_message(Config)
end, lists:seq(0, Size)),
wait_for_slave(Config),
wait_for_slave(Config),
flush(Config),
put_event(Config, join),
lists:foreach(
fun(Type) ->
recv_muc_presence(Config, PeerNickJID, Type)
end, [available, unavailable,
available, unavailable,
available, unavailable,
available, unavailable]),
ok = muc_leave(Config),
disconnect(Config).
@ -453,7 +459,9 @@ muc_history_slave(Config) ->
Size = gen_mod:get_module_opt(ServerHost, mod_muc, history_size,
fun(I) when is_integer(I), I>=0 -> I end,
20),
{History, _, _} = muc_slave_join(Config),
ct:comment("Waiting for 'join' command from the master"),
join = get_event(Config),
{History, _, _} = muc_join(Config),
ct:comment("Checking ordering of history events"),
BodyList = [binary_to_integer(xmpp:get_text(Body))
|| #message{type = groupchat, from = From,
@ -486,7 +494,6 @@ muc_history_slave(Config) ->
From == PeerNickJID],
BodyListWithoutFirst = lists:nthtail(1, lists:seq(1, Size)),
ok = muc_leave(Config),
wait_for_master(Config),
disconnect(Config).
muc_invite_master(Config) ->
@ -663,8 +670,7 @@ muc_change_role_master(Config) ->
nick = PeerNick}|_] = muc_get_role(Config, Role)
end, [visitor, participant, moderator]),
put_event(Config, disconnect),
wait_for_slave(Config),
flush(Config),
recv_muc_presence(Config, PeerNickJID, unavailable),
ok = muc_leave(Config),
disconnect(Config).
@ -687,7 +693,6 @@ muc_change_role_slave(Config, {Role, Reason}) ->
muc_change_role_slave(Config, get_event(Config));
muc_change_role_slave(Config, disconnect) ->
ok = muc_leave(Config),
wait_for_master(Config),
disconnect(Config).
muc_change_affiliation_master(Config) ->
@ -1522,7 +1527,7 @@ muc_join_new(Config, Room) ->
items = [#muc_item{role = moderator,
jid = MyJID,
affiliation = owner}]} =
xmpp:get_subtag(?recv1(#presence{from = MyNickJID}), #muc_user{}),
recv_muc_presence(Config, MyNickJID, available),
ct:comment("Checking if codes '110' (self-presence) and "
"'201' (new room) is set"),
true = lists:member(110, Codes),
@ -1623,8 +1628,7 @@ muc_leave(Config, Room) ->
#muc_user{
status_codes = Codes,
items = [#muc_item{role = none, jid = MyJID}]} =
xmpp:get_subtag(?recv1(#presence{from = MyNickJID,
type = unavailable}), #muc_user{}),
recv_muc_presence(Config, MyNickJID, unavailable),
ct:comment("Checking if code '110' (self-presence) is set"),
true = lists:member(110, Codes),
ok.

821
test/privacy_tests.erl Normal file
View File

@ -0,0 +1,821 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 18 Oct 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(privacy_tests).
%% API
-compile(export_all).
-import(suite, [disconnect/1, send_recv/2, get_event/1, put_event/2,
recv_iq/1, recv_presence/1, recv_message/1, recv/1,
send/2, my_jid/1, server_jid/1, get_features/1,
set_roster/3, del_roster/1]).
-include("suite.hrl").
-include("mod_roster.hrl").
%%%===================================================================
%%% API
%%%===================================================================
%%%===================================================================
%%% Single cases
%%%===================================================================
single_cases() ->
{privacy_single, [sequence],
[single_test(feature_enabled),
single_test(set_get_list),
single_test(get_list_non_existent),
single_test(set_default),
single_test(del_default),
single_test(set_default_non_existent),
single_test(set_active),
single_test(del_active),
single_test(set_active_non_existent),
single_test(remove_list),
single_test(remove_default_list),
single_test(remove_active_list),
%% TODO: this should be fixed
%% single_test(remove_list_non_existent),
single_test(allow_local_server),
single_test(malformed_iq_query),
single_test(malformed_get),
single_test(malformed_set),
single_test(malformed_type_value),
single_test(set_get_block)]}.
feature_enabled(Config) ->
Features = get_features(Config),
true = lists:member(?NS_PRIVACY, Features),
true = lists:member(?NS_BLOCKING, Features),
disconnect(Config).
set_get_list(Config) ->
ListName = <<"set-get-list">>,
Items = [#privacy_item{order = 0, action = deny,
type = jid, value = <<"user@jabber.org">>,
iq = true},
#privacy_item{order = 1, action = allow,
type = group, value = <<"group">>,
message = true},
#privacy_item{order = 2, action = allow,
type = subscription, value = <<"both">>,
presence_in = true},
#privacy_item{order = 3, action = deny,
type = subscription, value = <<"from">>,
presence_out = true},
#privacy_item{order = 4, action = deny,
type = subscription, value = <<"to">>,
iq = true, message = true},
#privacy_item{order = 5, action = deny,
type = subscription, value = <<"none">>,
_ = true},
#privacy_item{order = 6, action = deny}],
ok = set_items(Config, ListName, Items),
#privacy_list{name = ListName, items = Items1} = get_list(Config, ListName),
Items = lists:keysort(#privacy_item.order, Items1),
del_privacy(disconnect(Config)).
get_list_non_existent(Config) ->
ListName = <<"get-list-non-existent">>,
#stanza_error{reason = 'item-not-found'} = get_list(Config, ListName),
disconnect(Config).
set_default(Config) ->
ListName = <<"set-default">>,
Item = #privacy_item{order = 0, action = deny},
ok = set_items(Config, ListName, [Item]),
ok = set_default(Config, ListName),
#privacy_query{default = ListName} = get_lists(Config),
del_privacy(disconnect(Config)).
del_default(Config) ->
ListName = <<"del-default">>,
Item = #privacy_item{order = 0, action = deny},
ok = set_items(Config, ListName, [Item]),
ok = set_default(Config, ListName),
#privacy_query{default = ListName} = get_lists(Config),
ok = set_default(Config, none),
#privacy_query{default = none} = get_lists(Config),
del_privacy(disconnect(Config)).
set_default_non_existent(Config) ->
ListName = <<"set-default-non-existent">>,
#stanza_error{reason = 'item-not-found'} = set_default(Config, ListName),
disconnect(Config).
set_active(Config) ->
ListName = <<"set-active">>,
Item = #privacy_item{order = 0, action = deny},
ok = set_items(Config, ListName, [Item]),
ok = set_active(Config, ListName),
#privacy_query{active = ListName} = get_lists(Config),
del_privacy(disconnect(Config)).
del_active(Config) ->
ListName = <<"del-active">>,
Item = #privacy_item{order = 0, action = deny},
ok = set_items(Config, ListName, [Item]),
ok = set_active(Config, ListName),
#privacy_query{active = ListName} = get_lists(Config),
ok = set_active(Config, none),
#privacy_query{active = none} = get_lists(Config),
del_privacy(disconnect(Config)).
set_active_non_existent(Config) ->
ListName = <<"set-active-non-existent">>,
#stanza_error{reason = 'item-not-found'} = set_active(Config, ListName),
disconnect(Config).
remove_list(Config) ->
ListName = <<"remove-list">>,
Item = #privacy_item{order = 0, action = deny},
ok = set_items(Config, ListName, [Item]),
ok = del_list(Config, ListName),
#privacy_query{lists = []} = get_lists(Config),
del_privacy(disconnect(Config)).
remove_active_list(Config) ->
ListName = <<"remove-active-list">>,
Item = #privacy_item{order = 0, action = deny},
ok = set_items(Config, ListName, [Item]),
ok = set_active(Config, ListName),
#stanza_error{reason = 'conflict'} = del_list(Config, ListName),
del_privacy(disconnect(Config)).
remove_default_list(Config) ->
ListName = <<"remove-default-list">>,
Item = #privacy_item{order = 0, action = deny},
ok = set_items(Config, ListName, [Item]),
ok = set_default(Config, ListName),
#stanza_error{reason = 'conflict'} = del_list(Config, ListName),
del_privacy(disconnect(Config)).
remove_list_non_existent(Config) ->
ListName = <<"remove-list-non-existent">>,
#stanza_error{reason = 'item-not-found'} = del_list(Config, ListName),
disconnect(Config).
allow_local_server(Config) ->
ListName = <<"allow-local-server">>,
Item = #privacy_item{order = 0, action = deny},
ok = set_items(Config, ListName, [Item]),
ok = set_active(Config, ListName),
%% Whatever privacy rules are set, we should always communicate
%% with our home server
server_send_iqs(Config),
server_recv_iqs(Config),
send_stanzas_to_server_resource(Config),
del_privacy(disconnect(Config)).
malformed_iq_query(Config) ->
lists:foreach(
fun(Type) ->
#iq{type = error} =
send_recv(Config,
#iq{type = Type,
sub_els = [#privacy_list{name = <<"foo">>}]})
end, [get, set]),
disconnect(Config).
malformed_get(Config) ->
JID = jid:make(randoms:get_string()),
lists:foreach(
fun(SubEl) ->
#iq{type = error} =
send_recv(Config, #iq{type = get, sub_els = [SubEl]})
end, [#privacy_query{active = none},
#privacy_query{default = none},
#privacy_query{lists = [#privacy_list{name = <<"1">>},
#privacy_list{name = <<"2">>}]},
#block{items = [JID]}, #unblock{items = [JID]},
#block{}, #unblock{}]),
disconnect(Config).
malformed_set(Config) ->
lists:foreach(
fun(SubEl) ->
#iq{type = error} =
send_recv(Config, #iq{type = set, sub_els = [SubEl]})
end, [#privacy_query{active = none, default = none},
#privacy_query{lists = [#privacy_list{name = <<"1">>},
#privacy_list{name = <<"2">>}]},
#block{},
#block_list{},
#block_list{items = [jid:make(randoms:get_string())]}]).
malformed_type_value(Config) ->
Item = #privacy_item{order = 0, action = deny},
#stanza_error{reason = 'bad-request'} =
set_items(Config, <<"malformed-jid">>,
[Item#privacy_item{type = jid, value = <<"@bad">>}]),
#stanza_error{reason = 'bad-request'} =
set_items(Config, <<"malformed-group">>,
[Item#privacy_item{type = group, value = <<"">>}]),
#stanza_error{reason = 'bad-request'} =
set_items(Config, <<"malformed-subscription">>,
[Item#privacy_item{type = subscription, value = <<"bad">>}]),
disconnect(Config).
set_get_block(Config) ->
J1 = jid:make(randoms:get_string(), randoms:get_string()),
J2 = jid:make(randoms:get_string(), randoms:get_string()),
{ok, ListName} = set_block(Config, [J1, J2]),
JIDs = get_block(Config),
JIDs = lists:sort([J1, J2]),
{ok, ListName} = set_unblock(Config, [J2, J1]),
[] = get_block(Config),
del_privacy(disconnect(Config)).
%%%===================================================================
%%% Master-slave cases
%%%===================================================================
master_slave_cases() ->
{privacy_master_slave, [parallel],
[master_slave_test(deny_bare_jid),
master_slave_test(deny_full_jid),
master_slave_test(deny_server_jid),
master_slave_test(deny_group),
master_slave_test(deny_sub_both),
master_slave_test(deny_sub_from),
master_slave_test(deny_sub_to),
master_slave_test(deny_sub_none),
master_slave_test(deny_all),
master_slave_test(deny_offline),
master_slave_test(block),
master_slave_test(unblock),
master_slave_test(unblock_all)]}.
deny_bare_jid_master(Config) ->
PeerJID = ?config(peer, Config),
PeerBareJID = jid:remove_resource(PeerJID),
deny_master(Config, {jid, jid:to_string(PeerBareJID)}).
deny_bare_jid_slave(Config) ->
deny_slave(Config).
deny_full_jid_master(Config) ->
PeerJID = ?config(peer, Config),
deny_master(Config, {jid, jid:to_string(PeerJID)}).
deny_full_jid_slave(Config) ->
deny_slave(Config).
deny_server_jid_master(Config) ->
{_, Server, _} = jid:tolower(?config(peer, Config)),
deny_master(Config, {jid, Server}).
deny_server_jid_slave(Config) ->
deny_slave(Config).
deny_group_master(Config) ->
Group = randoms:get_string(),
deny_master(Config, {group, Group}).
deny_group_slave(Config) ->
deny_slave(Config).
deny_sub_both_master(Config) ->
deny_master(Config, {subscription, <<"both">>}).
deny_sub_both_slave(Config) ->
deny_slave(Config).
deny_sub_from_master(Config) ->
deny_master(Config, {subscription, <<"from">>}).
deny_sub_from_slave(Config) ->
deny_slave(Config).
deny_sub_to_master(Config) ->
deny_master(Config, {subscription, <<"to">>}).
deny_sub_to_slave(Config) ->
deny_slave(Config).
deny_sub_none_master(Config) ->
deny_master(Config, {subscription, <<"none">>}).
deny_sub_none_slave(Config) ->
deny_slave(Config).
deny_all_master(Config) ->
deny_master(Config, {undefined, <<"">>}).
deny_all_slave(Config) ->
deny_slave(Config).
deny_master(Config, {Type, Value}) ->
Sub = if Type == subscription ->
erlang:binary_to_atom(Value, utf8);
true ->
both
end,
Groups = if Type == group -> [Value];
true -> []
end,
set_roster(Config, Sub, Groups),
lists:foreach(
fun(Opts) ->
ListName = str:format("deny-~s-~s-~p", [Type, Value, Opts]),
Item = #privacy_item{order = 0,
action = deny,
iq = proplists:get_bool(iq, Opts),
message = proplists:get_bool(message, Opts),
presence_in = proplists:get_bool(presence_in, Opts),
presence_out = proplists:get_bool(presence_out, Opts),
type = Type,
value = Value},
ok = set_items(Config, ListName, [Item]),
ok = set_active(Config, ListName),
put_event(Config, Opts),
case is_presence_in_blocked(Opts) of
true -> ok;
false -> recv_presences(Config)
end,
case is_iq_in_blocked(Opts) of
true -> ok;
false -> recv_iqs(Config)
end,
case is_message_in_blocked(Opts) of
true -> ok;
false -> recv_messages(Config)
end,
ct:comment("Waiting for 'send' command from the slave"),
send = get_event(Config),
case is_presence_out_blocked(Opts) of
true -> check_presence_blocked(Config, 'not-acceptable');
false -> ok
end,
case is_iq_out_blocked(Opts) of
true -> check_iq_blocked(Config, 'not-acceptable');
false -> send_iqs(Config)
end,
case is_message_out_blocked(Opts) of
true -> check_message_blocked(Config, 'not-acceptable');
false -> send_messages(Config)
end,
case is_other_blocked(Opts) of
true -> check_other_blocked(Config, 'not-acceptable');
false -> ok
end,
ct:comment("Waiting for slave to finish processing our stanzas"),
done = get_event(Config)
end,
[[iq], [message], [presence_in], [presence_out],
[iq, message, presence_in, presence_out], []]),
put_event(Config, disconnect),
clean_up(disconnect(Config)).
deny_slave(Config) ->
set_roster(Config, both, []),
deny_slave(Config, get_event(Config)).
deny_slave(Config, disconnect) ->
clean_up(disconnect(Config));
deny_slave(Config, Opts) ->
send_presences(Config),
case is_iq_in_blocked(Opts) of
true -> check_iq_blocked(Config, 'service-unavailable');
false -> send_iqs(Config)
end,
case is_message_in_blocked(Opts) of
true -> check_message_blocked(Config, 'service-unavailable');
false -> send_messages(Config)
end,
put_event(Config, send),
case is_iq_out_blocked(Opts) of
true -> ok;
false -> recv_iqs(Config)
end,
case is_message_out_blocked(Opts) of
true -> ok;
false -> recv_messages(Config)
end,
put_event(Config, done),
deny_slave(Config, get_event(Config)).
deny_offline_master(Config) ->
set_roster(Config, both, []),
ListName = <<"deny-offline">>,
Item = #privacy_item{order = 0, action = deny},
ok = set_items(Config, ListName, [Item]),
ok = set_default(Config, ListName),
NewConfig = disconnect(Config),
put_event(NewConfig, send),
ct:comment("Waiting for the slave to finish"),
done = get_event(NewConfig),
clean_up(NewConfig).
deny_offline_slave(Config) ->
set_roster(Config, both, []),
ct:comment("Waiting for 'send' command from the master"),
send = get_event(Config),
send_presences(Config),
check_iq_blocked(Config, 'service-unavailable'),
check_message_blocked(Config, 'service-unavailable'),
put_event(Config, done),
clean_up(disconnect(Config)).
block_master(Config) ->
PeerJID = ?config(peer, Config),
set_roster(Config, both, []),
{ok, _} = set_block(Config, [PeerJID]),
check_presence_blocked(Config, 'not-acceptable'),
check_iq_blocked(Config, 'not-acceptable'),
check_message_blocked(Config, 'not-acceptable'),
check_other_blocked(Config, 'not-acceptable'),
%% We should always be able to communicate with our home server
server_send_iqs(Config),
server_recv_iqs(Config),
send_stanzas_to_server_resource(Config),
put_event(Config, send),
done = get_event(Config),
clean_up(disconnect(Config)).
block_slave(Config) ->
set_roster(Config, both, []),
ct:comment("Waiting for 'send' command from master"),
send = get_event(Config),
send_presences(Config),
check_iq_blocked(Config, 'service-unavailable'),
check_message_blocked(Config, 'service-unavailable'),
put_event(Config, done),
clean_up(disconnect(Config)).
unblock_master(Config) ->
PeerJID = ?config(peer, Config),
set_roster(Config, both, []),
{ok, ListName} = set_block(Config, [PeerJID]),
{ok, ListName} = set_unblock(Config, [PeerJID]),
put_event(Config, send),
recv_presences(Config),
recv_iqs(Config),
recv_messages(Config),
clean_up(disconnect(Config)).
unblock_slave(Config) ->
set_roster(Config, both, []),
ct:comment("Waiting for 'send' command from master"),
send = get_event(Config),
send_presences(Config),
send_iqs(Config),
send_messages(Config),
clean_up(disconnect(Config)).
unblock_all_master(Config) ->
PeerJID = ?config(peer, Config),
set_roster(Config, both, []),
{ok, ListName} = set_block(Config, [PeerJID]),
{ok, ListName} = set_unblock(Config, []),
put_event(Config, send),
recv_presences(Config),
recv_iqs(Config),
recv_messages(Config),
clean_up(disconnect(Config)).
unblock_all_slave(Config) ->
set_roster(Config, both, []),
ct:comment("Waiting for 'send' command from master"),
send = get_event(Config),
send_presences(Config),
send_iqs(Config),
send_messages(Config),
clean_up(disconnect(Config)).
%%%===================================================================
%%% Internal functions
%%%===================================================================
single_test(T) ->
list_to_atom("privacy_" ++ atom_to_list(T)).
master_slave_test(T) ->
{list_to_atom("privacy_" ++ atom_to_list(T)), [parallel],
[list_to_atom("privacy_" ++ atom_to_list(T) ++ "_master"),
list_to_atom("privacy_" ++ atom_to_list(T) ++ "_slave")]}.
set_items(Config, Name, Items) ->
ct:comment("Setting privacy list ~s with items = ~p", [Name, Items]),
case send_recv(
Config,
#iq{type = set, sub_els = [#privacy_query{
lists = [#privacy_list{
name = Name,
items = Items}]}]}) of
#iq{type = result, sub_els = []} ->
ct:comment("Receiving privacy list push"),
#iq{type = set, id = ID,
sub_els = [#privacy_query{lists = [#privacy_list{
name = Name}]}]} =
recv_iq(Config),
send(Config, #iq{type = result, id = ID}),
ok;
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
get_list(Config, Name) ->
ct:comment("Requesting privacy list ~s", [Name]),
case send_recv(Config,
#iq{type = get,
sub_els = [#privacy_query{
lists = [#privacy_list{name = Name}]}]}) of
#iq{type = result, sub_els = [#privacy_query{lists = [List]}]} ->
List;
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
get_lists(Config) ->
ct:comment("Requesting privacy lists"),
case send_recv(Config, #iq{type = get, sub_els = [#privacy_query{}]}) of
#iq{type = result, sub_els = [SubEl]} ->
SubEl;
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
del_list(Config, Name) ->
case send_recv(
Config,
#iq{type = set, sub_els = [#privacy_query{
lists = [#privacy_list{
name = Name}]}]}) of
#iq{type = result, sub_els = []} ->
ct:comment("Receiving privacy list push"),
#iq{type = set, id = ID,
sub_els = [#privacy_query{lists = [#privacy_list{
name = Name}]}]} =
recv_iq(Config),
send(Config, #iq{type = result, id = ID}),
ok;
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
set_active(Config, Name) ->
ct:comment("Setting active privacy list ~s", [Name]),
case send_recv(
Config,
#iq{type = set, sub_els = [#privacy_query{active = Name}]}) of
#iq{type = result, sub_els = []} ->
ok;
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
set_default(Config, Name) ->
ct:comment("Setting default privacy list ~s", [Name]),
case send_recv(
Config,
#iq{type = set, sub_els = [#privacy_query{default = Name}]}) of
#iq{type = result, sub_els = []} ->
ok;
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
get_block(Config) ->
case send_recv(Config, #iq{type = get, sub_els = [#block_list{}]}) of
#iq{type = result, sub_els = [#block_list{items = JIDs}]} ->
lists:sort(JIDs);
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
set_block(Config, JIDs) ->
case send_recv(Config, #iq{type = set,
sub_els = [#block{items = JIDs}]}) of
#iq{type = result, sub_els = []} ->
{#iq{id = I1, sub_els = [#block{items = Items}]},
#iq{id = I2, sub_els = [#privacy_query{lists = Lists}]}} =
?recv2(#iq{type = set, sub_els = [#block{}]},
#iq{type = set, sub_els = [#privacy_query{}]}),
send(Config, #iq{type = result, id = I1}),
send(Config, #iq{type = result, id = I2}),
ct:comment("Checking if all JIDs present in the push"),
true = lists:sort(JIDs) == lists:sort(Items),
ct:comment("Getting name of the corresponding privacy list"),
[#privacy_list{name = Name}] = Lists,
{ok, Name};
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
set_unblock(Config, JIDs) ->
ct:comment("Unblocking ~p", [JIDs]),
case send_recv(Config, #iq{type = set,
sub_els = [#unblock{items = JIDs}]}) of
#iq{type = result, sub_els = []} ->
{#iq{id = I1, sub_els = [#unblock{items = Items}]},
#iq{id = I2, sub_els = [#privacy_query{lists = Lists}]}} =
?recv2(#iq{type = set, sub_els = [#unblock{}]},
#iq{type = set, sub_els = [#privacy_query{}]}),
send(Config, #iq{type = result, id = I1}),
send(Config, #iq{type = result, id = I2}),
ct:comment("Checking if all JIDs present in the push"),
true = lists:sort(JIDs) == lists:sort(Items),
ct:comment("Getting name of the corresponding privacy list"),
[#privacy_list{name = Name}] = Lists,
{ok, Name};
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
del_privacy(Config) ->
{U, S, _} = jid:tolower(my_jid(Config)),
ct:comment("Removing all privacy data"),
mod_privacy:remove_user(U, S),
Config.
clean_up(Config) ->
del_privacy(del_roster(Config)).
check_iq_blocked(Config, Reason) ->
PeerJID = ?config(peer, Config),
ct:comment("Checking if all IQs are blocked"),
lists:foreach(
fun(Type) ->
send(Config, #iq{type = Type, to = PeerJID})
end, [error, result]),
lists:foreach(
fun(Type) ->
#iq{type = error} = Err =
send_recv(Config, #iq{type = Type, to = PeerJID,
sub_els = [#ping{}]}),
#stanza_error{reason = Reason} = xmpp:get_error(Err)
end, [set, get]).
check_message_blocked(Config, Reason) ->
PeerJID = ?config(peer, Config),
ct:comment("Checking if all messages are blocked"),
%% TODO: do something with headline and groupchat.
%% The hack from 64d96778b452aad72349b21d2ac94e744617b07a
%% screws this up.
lists:foreach(
fun(Type) ->
send(Config, #message{type = Type, to = PeerJID})
end, [error]),
lists:foreach(
fun(Type) ->
#message{type = error} = Err =
send_recv(Config, #message{type = Type, to = PeerJID}),
#stanza_error{reason = Reason} = xmpp:get_error(Err)
end, [chat, normal]).
check_presence_blocked(Config, Reason) ->
PeerJID = ?config(peer, Config),
ct:comment("Checking if all presences are blocked"),
lists:foreach(
fun(Type) ->
#presence{type = error} = Err =
send_recv(Config, #presence{type = Type, to = PeerJID}),
#stanza_error{reason = Reason} = xmpp:get_error(Err)
end, [available, unavailable]).
check_other_blocked(Config, Reason) ->
PeerJID = ?config(peer, Config),
ct:comment("Checking if subscriptions and presence-errors are blocked"),
send(Config, #presence{type = error, to = PeerJID}),
lists:foreach(
fun(Type) ->
#presence{type = error} = Err =
send_recv(Config, #presence{type = Type, to = PeerJID}),
#stanza_error{reason = Reason} = xmpp:get_error(Err)
end, [subscribe, subscribed, unsubscribe, unsubscribed]).
send_presences(Config) ->
PeerJID = ?config(peer, Config),
ct:comment("Sending all types of presences to the peer"),
lists:foreach(
fun(Type) ->
send(Config, #presence{type = Type, to = PeerJID})
end, [available, unavailable]).
send_iqs(Config) ->
PeerJID = ?config(peer, Config),
ct:comment("Sending all types of IQs to the peer"),
lists:foreach(
fun(Type) ->
send(Config, #iq{type = Type, to = PeerJID})
end, [set, get, error, result]).
send_messages(Config) ->
PeerJID = ?config(peer, Config),
ct:comment("Sending all types of messages to the peer"),
lists:foreach(
fun(Type) ->
send(Config, #message{type = Type, to = PeerJID})
end, [chat, error, groupchat, headline, normal]).
recv_presences(Config) ->
PeerJID = ?config(peer, Config),
lists:foreach(
fun(Type) ->
#presence{type = Type, from = PeerJID} =
recv_presence(Config)
end, [available, unavailable]).
recv_iqs(Config) ->
PeerJID = ?config(peer, Config),
lists:foreach(
fun(Type) ->
#iq{type = Type, from = PeerJID} = recv_iq(Config)
end, [set, get, error, result]).
recv_messages(Config) ->
PeerJID = ?config(peer, Config),
lists:foreach(
fun(Type) ->
#message{type = Type, from = PeerJID} = recv_message(Config)
end, [chat, error, groupchat, headline, normal]).
match_all(Opts) ->
IQ = proplists:get_bool(iq, Opts),
Message = proplists:get_bool(message, Opts),
PresenceIn = proplists:get_bool(presence_in, Opts),
PresenceOut = proplists:get_bool(presence_out, Opts),
not (IQ or Message or PresenceIn or PresenceOut).
is_message_in_blocked(Opts) ->
proplists:get_bool(message, Opts) or match_all(Opts).
is_message_out_blocked(Opts) ->
match_all(Opts).
is_iq_in_blocked(Opts) ->
proplists:get_bool(iq, Opts) or match_all(Opts).
is_iq_out_blocked(Opts) ->
match_all(Opts).
is_presence_in_blocked(Opts) ->
proplists:get_bool(presence_in, Opts) or match_all(Opts).
is_presence_out_blocked(Opts) ->
proplists:get_bool(presence_out, Opts) or match_all(Opts).
is_other_blocked(Opts) ->
%% 'other' means subscriptions and presence-errors
match_all(Opts).
server_send_iqs(Config) ->
ServerJID = server_jid(Config),
MyJID = my_jid(Config),
ct:comment("Sending IQs from ~s to ~s",
[jid:to_string(ServerJID), jid:to_string(MyJID)]),
lists:foreach(
fun(Type) ->
ejabberd_router:route(
ServerJID, MyJID, #iq{type = Type})
end, [error, result]),
lists:foreach(
fun(Type) ->
ejabberd_local:route_iq(
ServerJID, MyJID, #iq{type = Type},
fun(#iq{type = result, sub_els = []}) -> ok;
(IQ) -> ct:fail({unexpected_iq_result, IQ})
end)
end, [set, get]).
server_recv_iqs(Config) ->
ServerJID = server_jid(Config),
ct:comment("Receiving IQs from ~s", [jid:to_string(ServerJID)]),
lists:foreach(
fun(Type) ->
#iq{type = Type, from = ServerJID} = recv_iq(Config)
end, [error, result]),
lists:foreach(
fun(Type) ->
#iq{type = Type, from = ServerJID, id = I} = recv_iq(Config),
send(Config, #iq{to = ServerJID, type = result, id = I})
end, [set, get]).
send_stanzas_to_server_resource(Config) ->
ServerJID = server_jid(Config),
ServerJIDResource = jid:replace_resource(ServerJID, <<"resource">>),
%% All stanzas sent should be handled by local_send_to_resource_hook
%% and should be bounced with item-not-found error
ct:comment("Sending IQs to ~s", [jid:to_string(ServerJIDResource)]),
lists:foreach(
fun(Type) ->
#iq{type = error} = Err =
send_recv(Config, #iq{type = Type, to = ServerJIDResource}),
#stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
end, [set, get]),
ct:comment("Sending messages to ~s", [jid:to_string(ServerJIDResource)]),
lists:foreach(
fun(Type) ->
#message{type = error} = Err =
send_recv(Config, #message{type = Type, to = ServerJIDResource}),
#stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
end, [normal, chat, groupchat, headline]),
ct:comment("Sending presences to ~s", [jid:to_string(ServerJIDResource)]),
lists:foreach(
fun(Type) ->
#presence{type = error} = Err =
send_recv(Config, #presence{type = Type, to = ServerJIDResource}),
#stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
end, [available, unavailable]).

View File

@ -13,6 +13,7 @@
-include("suite.hrl").
-include_lib("kernel/include/file.hrl").
-include("mod_roster.hrl").
%%%===================================================================
%%% API
@ -171,10 +172,18 @@ connect(Config) ->
tcp_connect(Config) ->
case ?config(socket, Config) of
undefined ->
Owner = self(),
NS = case ?config(type, Config) of
client -> ?NS_CLIENT;
server -> ?NS_SERVER;
component -> ?NS_COMPONENT
end,
ReceiverPid = spawn(fun() -> receiver(NS, Owner) end),
{ok, Sock} = ejabberd_socket:connect(
?config(server_host, Config),
?config(server_port, Config),
[binary, {packet, 0}, {active, false}]),
[binary, {packet, 0}, {active, false}],
infinity, ReceiverPid),
set_opt(socket, Sock, Config);
_ ->
Config
@ -219,9 +228,11 @@ disconnect(Config) ->
catch exit:normal ->
ok
end,
{xmlstreamend, <<"stream:stream">>} = recv(Config),
receive {xmlstreamend, <<"stream:stream">>} -> ok end,
flush(Config),
ejabberd_socket:close(Socket),
Config.
ct:comment("Disconnected"),
set_opt(socket, undefined, Config).
close_socket(Config) ->
Socket = ?config(socket, Config),
@ -435,40 +446,25 @@ match_failure(Received, [Match]) when is_list(Match)->
match_failure(Received, Matches) ->
ct:fail("Received input:~n~n~p~n~ndon't match expected patterns:~n~n~p", [Received, Matches]).
recv(Config) ->
recv(_Config) ->
receive
{'$gen_event', {xmlstreamelement, El}} ->
decode_stream_element(Config, El);
{'$gen_event', {xmlstreamstart, Name, Attrs}} ->
decode(#xmlel{name = Name, attrs = Attrs}, <<>>, []);
{'$gen_event', Event} ->
{fail, El, Why} ->
ct:fail("recv failed: ~p->~n~s",
[El, xmpp:format_error(Why)]);
Event ->
Event
end.
recv_iq(Config) ->
receive
{'$gen_event', {xmlstreamelement, #xmlel{name = <<"iq">>} = El}} ->
decode_stream_element(Config, El)
end.
recv_iq(_Config) ->
receive #iq{} = IQ -> IQ end.
recv_presence(Config) ->
receive
{'$gen_event', {xmlstreamelement, #xmlel{name = <<"presence">>} = El}} ->
decode_stream_element(Config, El)
end.
recv_presence(_Config) ->
receive #presence{} = Pres -> Pres end.
recv_message(Config) ->
receive
{'$gen_event', {xmlstreamelement, #xmlel{name = <<"message">>} = El}} ->
decode_stream_element(Config, El)
end.
recv_message(_Config) ->
receive #message{} = Msg -> Msg end.
decode_stream_element(Config, El) ->
NS = case ?config(type, Config) of
client -> ?NS_CLIENT;
server -> ?NS_SERVER;
component -> ?NS_COMPONENT
end,
decode_stream_element(NS, El) ->
decode(El, NS, []).
format_element(El) ->
@ -517,13 +513,13 @@ send(State, Pkt) ->
send_recv(State, #message{} = Msg) ->
ID = send(State, Msg),
#message{id = ID} = recv_message(State);
receive #message{id = ID} = Result -> Result end;
send_recv(State, #presence{} = Pres) ->
ID = send(State, Pres),
#presence{id = ID} = recv_presence(State);
receive #presence{id = ID} = Result -> Result end;
send_recv(State, #iq{} = IQ) ->
ID = send(State, IQ),
#iq{id = ID} = recv_iq(State).
receive #iq{id = ID} = Result -> Result end.
sasl_new(<<"PLAIN">>, User, Server, Password) ->
{<<User/binary, $@, Server/binary, 0, User/binary, 0, Password/binary>>,
@ -698,6 +694,50 @@ wait_for_slave(Config) ->
make_iq_result(#iq{from = From} = IQ) ->
IQ#iq{type = result, to = From, from = undefined, sub_els = []}.
set_roster(Config, Subscription, Groups) ->
MyJID = my_jid(Config),
{U, S, _} = jid:tolower(MyJID),
PeerJID = ?config(peer, Config),
PeerBareJID = jid:remove_resource(PeerJID),
PeerLJID = jid:tolower(PeerBareJID),
ct:comment("Adding ~s to roster with subscription '~s' in groups ~p",
[jid:to_string(PeerBareJID), Subscription, Groups]),
{atomic, _} = mod_roster:set_roster(#roster{usj = {U, S, PeerLJID},
us = {U, S},
jid = PeerLJID,
subscription = Subscription,
groups = Groups}),
Config.
del_roster(Config) ->
MyJID = my_jid(Config),
{U, S, _} = jid:tolower(MyJID),
PeerJID = ?config(peer, Config),
PeerBareJID = jid:remove_resource(PeerJID),
PeerLJID = jid:tolower(PeerBareJID),
ct:comment("Removing ~s from roster", [jid:to_string(PeerBareJID)]),
{atomic, _} = mod_roster:del_roster(U, S, PeerLJID),
Config.
receiver(NS, Owner) ->
MRef = erlang:monitor(process, Owner),
receiver(NS, Owner, MRef).
receiver(NS, Owner, MRef) ->
receive
{'$gen_event', {xmlstreamelement, El}} ->
Owner ! decode_stream_element(NS, El),
receiver(NS, Owner, MRef);
{'$gen_event', {xmlstreamstart, Name, Attrs}} ->
Owner ! decode(#xmlel{name = Name, attrs = Attrs}, <<>>, []),
receiver(NS, Owner, MRef);
{'$gen_event', Event} ->
Owner ! Event,
receiver(NS, Owner, MRef);
{'DOWN', MRef, process, Owner, _} ->
ok
end.
%%%===================================================================
%%% Clients puts and gets events via this relay.
%%%===================================================================
@ -730,12 +770,17 @@ event_relay(Events, Subscribers) ->
end, Subscribers),
event_relay([Event|Events], Subscribers);
{'DOWN', _MRef, process, Pid, _Info} ->
case lists:member(Pid, Subscribers) of
true ->
NewSubscribers = lists:delete(Pid, Subscribers),
lists:foreach(
fun(Subscriber) ->
Subscriber ! {event, peer_down, self()}
end, NewSubscribers),
event_relay(Events, NewSubscribers)
event_relay(Events, NewSubscribers);
false ->
event_relay(Events, Subscribers)
end
end.
subscribe_to_events(Config) ->
@ -762,8 +807,10 @@ get_event(Config) ->
end.
flush(Config) ->
flush(Config, []).
flush(Config, Msgs) ->
receive Msg -> flush(Config, [Msg|Msgs])
after 1000 -> lists:reverse(Msgs) end.
receive
{event, peer_down, _} -> flush(Config);
closed -> flush(Config);
Msg -> ct:fail({unexpected_msg, Msg})
after 0 ->
ok
end.