add support for roster pre-approval
Implementation of RFC 6121 section 3.4: Pre-Approving a Subscription Request https://datatracker.ietf.org/doc/html/rfc6121#section-3.4
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
binary(),
|
||||
[binary()],
|
||||
both | from | to | none,
|
||||
boolean(),
|
||||
subscribe | unsubscribe | both | in | out | none,
|
||||
binary()}]
|
||||
}).
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
jid = {<<>>, <<>>, <<>>} :: jid:ljid(),
|
||||
name = <<>> :: binary() | '_',
|
||||
subscription = none :: subscription() | '_',
|
||||
approved = false :: boolean() | '_',
|
||||
ask = none :: ask() | '_',
|
||||
groups = [] :: [binary()] | '_',
|
||||
askmessage = <<"">> :: binary() | '_',
|
||||
|
||||
@@ -44,6 +44,7 @@ CREATE TABLE rosterusers (
|
||||
jid text NOT NULL,
|
||||
nick text NOT NULL,
|
||||
subscription character(1) NOT NULL,
|
||||
approved boolean NOT NULL,
|
||||
ask character(1) NOT NULL,
|
||||
askmessage text NOT NULL,
|
||||
server character(1) NOT NULL,
|
||||
|
||||
@@ -40,6 +40,7 @@ CREATE TABLE rosterusers (
|
||||
jid text NOT NULL,
|
||||
nick text NOT NULL,
|
||||
subscription character(1) NOT NULL,
|
||||
approved boolean NOT NULL,
|
||||
ask character(1) NOT NULL,
|
||||
askmessage text NOT NULL,
|
||||
server character(1) NOT NULL,
|
||||
|
||||
@@ -324,6 +324,7 @@ CREATE TABLE [dbo].[rosterusers] (
|
||||
[jid] [varchar] (250) NOT NULL,
|
||||
[nick] [text] NOT NULL,
|
||||
[subscription] [char] (1) NOT NULL,
|
||||
[approved] [smallint] NOT NULL,
|
||||
[ask] [char] (1) NOT NULL,
|
||||
[askmessage] [text] NOT NULL,
|
||||
[server] [char] (1) NOT NULL,
|
||||
|
||||
@@ -305,6 +305,7 @@ CREATE TABLE [dbo].[rosterusers] (
|
||||
[jid] [varchar] (250) NOT NULL,
|
||||
[nick] [text] NOT NULL,
|
||||
[subscription] [char] (1) NOT NULL,
|
||||
[approved] [smallint] NOT NULL,
|
||||
[ask] [char] (1) NOT NULL,
|
||||
[askmessage] [text] NOT NULL,
|
||||
[server] [char] (1) NOT NULL,
|
||||
|
||||
@@ -48,6 +48,7 @@ CREATE TABLE rosterusers (
|
||||
jid varchar(191) NOT NULL,
|
||||
nick text NOT NULL,
|
||||
subscription character(1) NOT NULL,
|
||||
approved boolean NOT NULL,
|
||||
ask character(1) NOT NULL,
|
||||
askmessage text NOT NULL,
|
||||
server character(1) NOT NULL,
|
||||
|
||||
@@ -44,6 +44,7 @@ CREATE TABLE rosterusers (
|
||||
jid varchar(191) NOT NULL,
|
||||
nick text NOT NULL,
|
||||
subscription character(1) NOT NULL,
|
||||
approved boolean NOT NULL,
|
||||
ask character(1) NOT NULL,
|
||||
askmessage text NOT NULL,
|
||||
server character(1) NOT NULL,
|
||||
|
||||
@@ -201,6 +201,7 @@ CREATE TABLE rosterusers (
|
||||
jid text NOT NULL,
|
||||
nick text NOT NULL,
|
||||
subscription character(1) NOT NULL,
|
||||
approved boolean NOT NULL,
|
||||
ask character(1) NOT NULL,
|
||||
askmessage text NOT NULL,
|
||||
server character(1) NOT NULL,
|
||||
|
||||
@@ -44,6 +44,7 @@ CREATE TABLE rosterusers (
|
||||
jid text NOT NULL,
|
||||
nick text NOT NULL,
|
||||
subscription character(1) NOT NULL,
|
||||
approved boolean NOT NULL,
|
||||
ask character(1) NOT NULL,
|
||||
askmessage text NOT NULL,
|
||||
server character(1) NOT NULL,
|
||||
|
||||
+114
-22
@@ -44,9 +44,11 @@
|
||||
import_info/0, process_local_iq/1, get_user_roster_items/2,
|
||||
import/5, get_roster/2, push_item/3,
|
||||
import_start/2, import_stop/2, is_subscribed/2,
|
||||
user_send_packet/1,
|
||||
c2s_self_presence/1, in_subscription/2,
|
||||
out_subscription/1, set_items/3, remove_user/2,
|
||||
get_jid_info/4, encode_item/1, get_versioning_feature/2,
|
||||
get_jid_info/4, encode_item/1,
|
||||
get_versioning_feature/2, pre_approval_stream_feature/2,
|
||||
roster_version/2, mod_doc/0,
|
||||
mod_opt_type/1, mod_options/1, set_roster/1, del_roster/3,
|
||||
process_rosteritems/5,
|
||||
@@ -101,6 +103,8 @@ start(Host, Opts) ->
|
||||
{hook, remove_user, remove_user, 50},
|
||||
{hook, c2s_self_presence, c2s_self_presence, 50},
|
||||
{hook, c2s_post_auth_features, get_versioning_feature, 50},
|
||||
{hook, c2s_post_auth_features, pre_approval_stream_feature, 50},
|
||||
{hook, user_send_packet, user_send_packet, 50},
|
||||
{hook, webadmin_menu_hostuser, webadmin_menu_hostuser, 50},
|
||||
{hook, webadmin_page_hostuser, webadmin_page_hostuser, 50},
|
||||
{hook, webadmin_user, webadmin_user, 50},
|
||||
@@ -205,6 +209,16 @@ get_versioning_feature(Acc, Host) ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
%% Indicate support for pre-approval as of RFC6121 section 3.4
|
||||
-spec pre_approval_stream_feature([xmpp_element()], binary()) -> [xmpp_element()].
|
||||
pre_approval_stream_feature(Acc, Host) ->
|
||||
case gen_mod:is_loaded(Host, ?MODULE) of
|
||||
true ->
|
||||
[#feature_pre_approval{} | Acc];
|
||||
false ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
-spec roster_version(binary(), binary()) -> undefined | binary().
|
||||
roster_version(LServer, LUser) ->
|
||||
case mod_roster_opt:store_current_id(LServer) of
|
||||
@@ -406,6 +420,7 @@ encode_item(Item) ->
|
||||
both -> subscribe;
|
||||
_ -> undefined
|
||||
end,
|
||||
approved = Item#roster.approved,
|
||||
groups = Item#roster.groups}.
|
||||
|
||||
-spec decode_item(roster_item(), #roster{}, boolean()) -> #roster{}.
|
||||
@@ -416,6 +431,7 @@ decode_item(#roster_item{subscription = remove} = Item, R, _) ->
|
||||
ask = none,
|
||||
groups = [],
|
||||
askmessage = <<"">>,
|
||||
approved = false,
|
||||
xs = []};
|
||||
decode_item(Item, R, Managed) ->
|
||||
R#roster{jid = jid:tolower(Item#roster_item.jid),
|
||||
@@ -424,6 +440,7 @@ decode_item(Item, R, Managed) ->
|
||||
Sub when Managed -> Sub;
|
||||
_ -> R#roster.subscription
|
||||
end,
|
||||
approved = Item#roster_item.approved,
|
||||
groups = Item#roster_item.groups}.
|
||||
|
||||
-spec process_iq_set(iq()) -> iq().
|
||||
@@ -574,41 +591,58 @@ process_subscription(Direction, User, Server, JID1,
|
||||
Item = get_roster_item(LUser, LServer, LJID),
|
||||
NewState = case Direction of
|
||||
out ->
|
||||
out_state_change(Item#roster.subscription,
|
||||
Item#roster.ask, Type);
|
||||
out_state_change(Item#roster.subscription,
|
||||
Item#roster.ask, Type);
|
||||
in ->
|
||||
in_state_change(Item#roster.subscription,
|
||||
Item#roster.ask, Type)
|
||||
case {Type, Item#roster.approved} of
|
||||
{subscribe, true} ->
|
||||
{TSub, TAsk} = in_state_change(Item#roster.subscription,
|
||||
Item#roster.ask, Type),
|
||||
out_state_change(TSub, TAsk, subscribed);
|
||||
_ ->
|
||||
in_state_change(Item#roster.subscription,
|
||||
Item#roster.ask, Type)
|
||||
end
|
||||
end,
|
||||
AutoReply = case Direction of
|
||||
out -> none;
|
||||
in ->
|
||||
in_auto_reply(Item#roster.subscription,
|
||||
Item#roster.ask, Type)
|
||||
case {Type, Item#roster.approved} of
|
||||
{subscribe, true} ->
|
||||
subscribed;
|
||||
_ ->
|
||||
in_auto_reply(Item#roster.subscription,
|
||||
Item#roster.ask, Type)
|
||||
end
|
||||
end,
|
||||
AskMessage = case NewState of
|
||||
{_, both} -> Reason;
|
||||
{_, in} -> Reason;
|
||||
_ -> <<"">>
|
||||
end,
|
||||
{Unapproved, Approved} = case {Direction, Type, Item#roster.approved} of
|
||||
{out, unsubscribed, true} ->
|
||||
{true, false};
|
||||
{_, _, Approved0} ->
|
||||
{false, Approved0}
|
||||
end,
|
||||
case NewState of
|
||||
none ->
|
||||
{none, AutoReply};
|
||||
NewItem = update_item(Item, Item#roster.subscription, Approved, Item#roster.ask, SubEls, AskMessage),
|
||||
{maybe_push_item(Unapproved, LUser, LServer, LJID, Item, NewItem), AutoReply};
|
||||
{none, none} when Item#roster.subscription == none,
|
||||
Item#roster.ask == in ->
|
||||
del_roster_t(LUser, LServer, LJID), {none, AutoReply};
|
||||
del_roster_t(LUser, LServer, LJID),
|
||||
case Unapproved of
|
||||
true ->
|
||||
NewItem = update_item(Item, none, Approved, none, SubEls, AskMessage),
|
||||
{{push, Item, NewItem}, AutoReply};
|
||||
false ->
|
||||
{none, AutoReply}
|
||||
end;
|
||||
{Subscription, Pending} ->
|
||||
NewItem = Item#roster{subscription = Subscription,
|
||||
ask = Pending,
|
||||
name = get_nick_subels(SubEls, Item#roster.name),
|
||||
xs = SubEls,
|
||||
askmessage = AskMessage},
|
||||
roster_subscribe_t(LUser, LServer, LJID, NewItem),
|
||||
case mod_roster_opt:store_current_id(LServer) of
|
||||
true -> write_roster_version_t(LUser, LServer);
|
||||
false -> ok
|
||||
end,
|
||||
{{push, Item, NewItem}, AutoReply}
|
||||
NewItem = update_item(Item, Subscription, Approved, Pending, SubEls, AskMessage),
|
||||
{prepare_push_item(LUser, LServer, LJID, Item, NewItem), AutoReply}
|
||||
end
|
||||
end,
|
||||
case transaction(LUser, LServer, [LJID], F) of
|
||||
@@ -631,7 +665,7 @@ process_subscription(Direction, User, Server, JID1,
|
||||
encode_item(OldItem),
|
||||
encode_item(NewItem))
|
||||
end,
|
||||
true;
|
||||
not (Type == subscribe andalso Direction == in andalso NewItem#roster.approved);
|
||||
none ->
|
||||
false
|
||||
end;
|
||||
@@ -639,12 +673,33 @@ process_subscription(Direction, User, Server, JID1,
|
||||
false
|
||||
end.
|
||||
|
||||
update_item(Item, Subscription, Approved, Pending, SubEls, AskMessage) ->
|
||||
Item#roster{subscription = Subscription,
|
||||
approved = Approved,
|
||||
ask = Pending,
|
||||
name = get_nick_subels(SubEls, Item#roster.name),
|
||||
xs = SubEls,
|
||||
askmessage = AskMessage}.
|
||||
|
||||
get_nick_subels(SubEls, Default) ->
|
||||
case xmpp:get_subtag(#presence{sub_els = SubEls}, #nick{}) of
|
||||
{nick, N} -> N;
|
||||
_ -> Default
|
||||
end.
|
||||
|
||||
maybe_push_item(true, LUser, LServer, LJID, OldItem, NewItem) ->
|
||||
prepare_push_item(LUser, LServer, LJID, OldItem, NewItem);
|
||||
maybe_push_item(false, _LUser, _LServer, _LJID, _OldItem, _NewItem) ->
|
||||
none.
|
||||
|
||||
prepare_push_item(LUser, LServer, LJID, OldItem, NewItem) ->
|
||||
roster_subscribe_t(LUser, LServer, LJID, NewItem),
|
||||
case mod_roster_opt:store_current_id(LServer) of
|
||||
true -> write_roster_version_t(LUser, LServer);
|
||||
false -> ok
|
||||
end,
|
||||
{push, OldItem, NewItem}.
|
||||
|
||||
%% in_state_change(Subscription, Pending, Type) -> NewState
|
||||
%% NewState = none | {NewSubscription, NewPending}
|
||||
-ifdef(ROSTER_GATEWAY_WORKAROUND).
|
||||
@@ -946,6 +1001,43 @@ process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) ->
|
||||
end;
|
||||
process_item_set_t(_LUser, _LServer, _) -> ok.
|
||||
|
||||
-spec user_send_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
|
||||
user_send_packet({#presence{type = subscribed, to = To} = Presence,
|
||||
#{jid := #jid{luser = LUser, lserver = LServer} = Jid} = C2SState}) ->
|
||||
LJID = jid:tolower(To),
|
||||
{atomic, Item} = transaction(
|
||||
LUser, LServer, [LJID],
|
||||
fun() ->
|
||||
get_roster_item(LUser, LServer, LJID)
|
||||
end),
|
||||
case Item of
|
||||
#roster{subscription = Subscription, ask = Ask}
|
||||
when Subscription == both;
|
||||
Subscription == from, Ask == none;
|
||||
Subscription == from, Ask == out ->
|
||||
{drop, C2SState};
|
||||
#roster{subscription = Subscription, ask = Ask}
|
||||
when Subscription == to, Ask == in;
|
||||
Subscription == none, Ask == in;
|
||||
Subscription == none, Ask == both ->
|
||||
{Presence, C2SState};
|
||||
#roster{subscription = Subscription, ask = Ask}
|
||||
when Subscription == to;
|
||||
Subscription == none, Ask == none;
|
||||
Subscription == none, Ask == out ->
|
||||
transaction(
|
||||
LUser, LServer, [LJID],
|
||||
fun() ->
|
||||
update_roster_t(LUser, LServer, LJID, Item#roster{approved = true})
|
||||
end),
|
||||
OldItem = encode_item(Item),
|
||||
NewItem = OldItem#roster_item{approved = true},
|
||||
push_item(Jid, OldItem, NewItem),
|
||||
{drop, C2SState}
|
||||
end;
|
||||
user_send_packet(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec c2s_self_presence({presence(), c2s_state()}) -> {presence(), c2s_state()}.
|
||||
c2s_self_presence({_, #{pres_last := _}} = Acc) ->
|
||||
Acc;
|
||||
@@ -1179,7 +1271,7 @@ export(LServer) ->
|
||||
import_info() ->
|
||||
[{<<"roster_version">>, 2},
|
||||
{<<"rostergroups">>, 3},
|
||||
{<<"rosterusers">>, 10}].
|
||||
{<<"rosterusers">>, 11}].
|
||||
|
||||
import_start(LServer, DBType) ->
|
||||
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
||||
|
||||
@@ -130,9 +130,24 @@ need_transform({roster_version, {U, S}, Ver})
|
||||
when is_list(U) orelse is_list(S) orelse is_list(Ver) ->
|
||||
?INFO_MSG("Mnesia table 'roster_version' will be converted to binary", []),
|
||||
true;
|
||||
need_transform({roster, {U, S, _}, _, _, _, _, none, _, _, _, _}) ->
|
||||
?INFO_MSG("Mnesia table 'roster' will be converted to use new boolean() 'approved' attribute.", []),
|
||||
true;
|
||||
need_transform(_) ->
|
||||
false.
|
||||
|
||||
transform({roster, USJ, US, Jid, Name, Sub, none, Ask, Groups, AskMsg, XS}) ->
|
||||
#roster{
|
||||
us = US,
|
||||
usj = USJ,
|
||||
jid = Jid,
|
||||
name = Name,
|
||||
subscription = Sub,
|
||||
approved = false,
|
||||
ask = Ask,
|
||||
groups = Groups,
|
||||
askmessage = AskMsg,
|
||||
xs = XS};
|
||||
transform(#roster{usj = {U, S, {LU, LS, LR}},
|
||||
us = {U1, S1},
|
||||
jid = {U2, S2, R2},
|
||||
|
||||
+59
-11
@@ -53,6 +53,51 @@ init(Host, _Opts) ->
|
||||
|
||||
sql_schemas() ->
|
||||
[#sql_schema{
|
||||
version = 2,
|
||||
tables =
|
||||
[#sql_table{
|
||||
name = <<"rosterusers">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"jid">>, type = text},
|
||||
#sql_column{name = <<"nick">>, type = text},
|
||||
#sql_column{name = <<"subscription">>, type = {char, 1}},
|
||||
#sql_column{name = <<"approved">>, type = boolean},
|
||||
#sql_column{name = <<"ask">>, type = {char, 1}},
|
||||
#sql_column{name = <<"askmessage">>, type = text},
|
||||
#sql_column{name = <<"server">>, type = {char, 1}},
|
||||
#sql_column{name = <<"subscribe">>, type = text},
|
||||
#sql_column{name = <<"type">>, type = text},
|
||||
#sql_column{name = <<"created_at">>, type = timestamp,
|
||||
default = true}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>,
|
||||
<<"jid">>],
|
||||
unique = true},
|
||||
#sql_index{
|
||||
columns = [<<"server_host">>, <<"jid">>]}]},
|
||||
#sql_table{
|
||||
name = <<"rostergroups">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"jid">>, type = text},
|
||||
#sql_column{name = <<"grp">>, type = text}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>,
|
||||
<<"jid">>]}]},
|
||||
#sql_table{
|
||||
name = <<"roster_version">>,
|
||||
columns =
|
||||
[#sql_column{name = <<"username">>, type = text},
|
||||
#sql_column{name = <<"server_host">>, type = text},
|
||||
#sql_column{name = <<"version">>, type = text}],
|
||||
indices = [#sql_index{
|
||||
columns = [<<"server_host">>, <<"username">>],
|
||||
unique = true}]}],
|
||||
update = [{add_column, <<"rosterusers">>, <<"approved">>}]},
|
||||
#sql_schema{
|
||||
version = 1,
|
||||
tables =
|
||||
[#sql_table{
|
||||
@@ -118,7 +163,7 @@ write_roster_version(LUser, LServer, InTransaction, Ver) ->
|
||||
get_roster(LUser, LServer) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, "
|
||||
?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, @(approved)b, "
|
||||
"@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, "
|
||||
"@(type)s from rosterusers "
|
||||
"where username=%(LUser)s and %(LServer)H")) of
|
||||
@@ -281,7 +326,7 @@ get_roster_groups(LServer, LUser, SJID) ->
|
||||
?SQL("select @(grp)s from rostergroups"
|
||||
" where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
|
||||
|
||||
roster_subscribe({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage}) ->
|
||||
roster_subscribe({LUser, LServer, SJID, Name, SSubscription, BApproved, SAsk, AskMessage}) ->
|
||||
?SQL_UPSERT_T(
|
||||
"rosterusers",
|
||||
["!username=%(LUser)s",
|
||||
@@ -289,6 +334,7 @@ roster_subscribe({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage})
|
||||
"!jid=%(SJID)s",
|
||||
"nick=%(Name)s",
|
||||
"subscription=%(SSubscription)s",
|
||||
"approved=%(BApproved)b",
|
||||
"ask=%(SAsk)s",
|
||||
"askmessage=%(AskMessage)s",
|
||||
"server='N'",
|
||||
@@ -297,7 +343,7 @@ roster_subscribe({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage})
|
||||
|
||||
get_roster_by_jid(LServer, LUser, SJID) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s,"
|
||||
?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, @(approved)b, "
|
||||
" @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s,"
|
||||
" @(type)s from rosterusers"
|
||||
" where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
|
||||
@@ -314,7 +360,7 @@ get_subscription(LServer, LUser, SJID) ->
|
||||
?SQL("select @(subscription)s, @(ask)s from rosterusers "
|
||||
"where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
|
||||
|
||||
update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
|
||||
update_roster_sql({LUser, LServer, SJID, Name, SSubscription, BApproved, SAsk, AskMessage},
|
||||
ItemGroups) ->
|
||||
[?SQL("delete from rosterusers where"
|
||||
" username=%(LUser)s and %(LServer)H and jid=%(SJID)s;"),
|
||||
@@ -325,6 +371,7 @@ update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
|
||||
"jid=%(SJID)s",
|
||||
"nick=%(Name)s",
|
||||
"subscription=%(SSubscription)s",
|
||||
"approved=%(BApproved)b",
|
||||
"ask=%(SAsk)s",
|
||||
"askmessage=%(AskMessage)s",
|
||||
"server='N'",
|
||||
@@ -342,19 +389,19 @@ update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
|
||||
|| ItemGroup <- ItemGroups].
|
||||
|
||||
raw_to_record(LServer,
|
||||
[User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
[User, LServer, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
|
||||
SServer, SSubscribe, SType]) ->
|
||||
raw_to_record(LServer,
|
||||
{User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
{User, LServer, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
|
||||
SServer, SSubscribe, SType});
|
||||
raw_to_record(LServer,
|
||||
{User, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
{User, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
|
||||
SServer, SSubscribe, SType}) ->
|
||||
raw_to_record(LServer,
|
||||
{User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
{User, LServer, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
|
||||
SServer, SSubscribe, SType});
|
||||
raw_to_record(LServer,
|
||||
{User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
{User, LServer, SJID, Nick, SSubscription, BApproved, SAsk, SAskMessage,
|
||||
_SServer, _SSubscribe, _SType}) ->
|
||||
try jid:decode(SJID) of
|
||||
JID ->
|
||||
@@ -363,7 +410,7 @@ raw_to_record(LServer,
|
||||
Ask = decode_ask(User, LServer, SAsk),
|
||||
#roster{usj = {User, LServer, LJID},
|
||||
us = {User, LServer}, jid = LJID, name = Nick,
|
||||
subscription = Subscription, ask = Ask,
|
||||
subscription = Subscription, approved = BApproved, ask = Ask,
|
||||
askmessage = SAskMessage}
|
||||
catch _:{bad_jid, _} ->
|
||||
?ERROR_MSG("~ts", [format_row_error(User, LServer, {jid, SJID})]),
|
||||
@@ -373,13 +420,14 @@ raw_to_record(LServer,
|
||||
record_to_row(
|
||||
#roster{us = {LUser, LServer},
|
||||
jid = JID, name = Name, subscription = Subscription,
|
||||
ask = Ask, askmessage = AskMessage}) ->
|
||||
approved = Approved, ask = Ask, askmessage = AskMessage}) ->
|
||||
SJID = jid:encode(jid:tolower(JID)),
|
||||
{LUser,
|
||||
LServer,
|
||||
SJID,
|
||||
Name,
|
||||
encode_subscription(Subscription),
|
||||
Approved,
|
||||
encode_ask(Ask),
|
||||
AskMessage}.
|
||||
|
||||
|
||||
+2
-12
@@ -345,21 +345,11 @@ init_per_testcase(TestCase, OrigConfig) ->
|
||||
bind(auth(connect(Config)));
|
||||
"replaced" ++ _ ->
|
||||
auth(connect(Config));
|
||||
"antispam" ++ _ ->
|
||||
Password = ?config(password, Config),
|
||||
ejabberd_auth:try_register(User, Server, Password),
|
||||
open_session(bind(auth(connect(Config))));
|
||||
"invites_" ++ _ ->
|
||||
Password = ?config(password, Config),
|
||||
ejabberd_auth:try_register(User, Server, Password),
|
||||
open_session(bind(auth(connect(Config))));
|
||||
_ when IsMaster or IsSlave ->
|
||||
Password = ?config(password, Config),
|
||||
ejabberd_auth:try_register(User, Server, Password),
|
||||
open_session(bind(auth(connect(Config))));
|
||||
_ when TestGroup == s2s_tests ->
|
||||
auth(connect(starttls(connect(Config))));
|
||||
_ ->
|
||||
Password = ?config(password, Config),
|
||||
ejabberd_auth:try_register(User, Server, Password),
|
||||
open_session(bind(auth(connect(Config))))
|
||||
end.
|
||||
|
||||
|
||||
+125
-69
@@ -35,7 +35,8 @@
|
||||
-record(state, {subscription = none :: none | from | to | both,
|
||||
peer_available = false,
|
||||
pending_in = false :: boolean(),
|
||||
pending_out = false :: boolean()}).
|
||||
pending_out = false :: boolean(),
|
||||
approved = false :: boolean()}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -51,7 +52,7 @@ stop(_TestCase, Config) ->
|
||||
%%%===================================================================
|
||||
single_cases() ->
|
||||
{roster_single, [sequence],
|
||||
[single_test(feature_enabled),
|
||||
[single_test(features_enabled),
|
||||
single_test(iq_set_many_items),
|
||||
single_test(iq_set_duplicated_groups),
|
||||
single_test(iq_get_item),
|
||||
@@ -60,9 +61,10 @@ single_cases() ->
|
||||
single_test(set_item),
|
||||
single_test(version)]}.
|
||||
|
||||
feature_enabled(Config) ->
|
||||
ct:comment("Checking if roster versioning stream feature is set"),
|
||||
features_enabled(Config) ->
|
||||
ct:comment("Checking if roster stream feature (versioning and pre-approval) is set"),
|
||||
true = ?config(rosterver, Config),
|
||||
true = ?config(pre_approval, Config),
|
||||
disconnect(Config).
|
||||
|
||||
set_item(Config) ->
|
||||
@@ -160,6 +162,7 @@ subscribe_slave(Config) ->
|
||||
|
||||
process_subscriptions_master(Config, Actions) ->
|
||||
EnumeratedActions = lists:zip(lists:seq(1, length(Actions)), Actions),
|
||||
ct:pal("actions: ~p", [EnumeratedActions]),
|
||||
self_presence(Config, available),
|
||||
Peer = ?config(peer, Config),
|
||||
lists:foldl(
|
||||
@@ -265,10 +268,14 @@ recv_push(Config) ->
|
||||
{Ver, PushItem}.
|
||||
|
||||
recv_push(Config, Subscription, Ask) ->
|
||||
recv_push(Config, Subscription, Ask, false).
|
||||
|
||||
recv_push(Config, Subscription, Ask, Approved) ->
|
||||
PeerJID = ?config(peer, Config),
|
||||
PeerBareJID = jid:remove_resource(PeerJID),
|
||||
Match = #roster_item{jid = PeerBareJID,
|
||||
subscription = Subscription,
|
||||
approved = Approved,
|
||||
ask = Ask,
|
||||
groups = [],
|
||||
name = <<"">>},
|
||||
@@ -316,11 +323,12 @@ check_roster([], _Config, _State) ->
|
||||
ok;
|
||||
check_roster([Roster], _Config, State) ->
|
||||
case {Roster#roster.subscription == State#state.subscription,
|
||||
Roster#roster.approved == State#state.approved,
|
||||
Roster#roster.ask, State#state.pending_in, State#state.pending_out} of
|
||||
{true, both, true, true} -> ok;
|
||||
{true, in, true, false} -> ok;
|
||||
{true, out, false, true} -> ok;
|
||||
{true, none, false, false} -> ok;
|
||||
{true, true, both, true, true} -> ok;
|
||||
{true, true, in, true, false} -> ok;
|
||||
{true, true, out, false, true} -> ok;
|
||||
{true, true, none, false, false} -> ok;
|
||||
_ ->
|
||||
ct:fail({roster_mismatch, State, Roster})
|
||||
end.
|
||||
@@ -340,43 +348,50 @@ check_roster_item(Config, State) ->
|
||||
|
||||
%% RFC6121, A.2.1
|
||||
transition(Id, Config, out, subscribe,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
PeerJID = ?config(peer, Config),
|
||||
PeerBareJID = jid:remove_resource(PeerJID),
|
||||
{is_approved, PeerApproved} = get_event(Config),
|
||||
send(Config, #presence{id = Id, to = PeerBareJID, type = subscribe}),
|
||||
case {Sub, Out, In} of
|
||||
{none, false, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
case {Sub, Out, In, PeerApproved} of
|
||||
{none, false, _, true} ->
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
recv_push(Config, to, undefined, Approved),
|
||||
recv_subscription(Config, subscribed),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = to};
|
||||
{none, false, _, false} ->
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
State#state{pending_out = true};
|
||||
{none, true, false} ->
|
||||
{none, true, false, false} ->
|
||||
%% BUG: we should not receive roster push here
|
||||
recv_push(Config, none, subscribe),
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
State;
|
||||
{from, false, false} ->
|
||||
recv_push(Config, from, subscribe),
|
||||
{from, false, false, false} ->
|
||||
recv_push(Config, from, subscribe, Approved),
|
||||
State#state{pending_out = true};
|
||||
_ ->
|
||||
State
|
||||
end;
|
||||
%% RFC6121, A.2.2
|
||||
transition(Id, Config, out, unsubscribe,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
PeerJID = ?config(peer, Config),
|
||||
PeerBareJID = jid:remove_resource(PeerJID),
|
||||
send(Config, #presence{id = Id, to = PeerBareJID, type = unsubscribe}),
|
||||
case {Sub, Out, In} of
|
||||
{none, true, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
State#state{pending_out = false};
|
||||
{to, false, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_presence(Config, unavailable),
|
||||
State#state{subscription = none, peer_available = false};
|
||||
{from, true, false} ->
|
||||
recv_push(Config, from, undefined),
|
||||
recv_push(Config, from, undefined, Approved),
|
||||
State#state{pending_out = false};
|
||||
{both, false, false} ->
|
||||
recv_push(Config, from, undefined),
|
||||
recv_push(Config, from, undefined, Approved),
|
||||
recv_presence(Config, unavailable),
|
||||
State#state{subscription = from, peer_available = false};
|
||||
_ ->
|
||||
@@ -390,59 +405,94 @@ transition(Id, Config, out, subscribed,
|
||||
send(Config, #presence{id = Id, to = PeerBareJID, type = subscribed}),
|
||||
case {Sub, Out, In} of
|
||||
{none, false, true} ->
|
||||
put_event(Config, {is_approved, false}),
|
||||
recv_push(Config, from, undefined),
|
||||
State#state{subscription = from, pending_in = false};
|
||||
{none, true, true} ->
|
||||
put_event(Config, {is_approved, false}),
|
||||
recv_push(Config, from, subscribe),
|
||||
State#state{subscription = from, pending_in = false};
|
||||
{to, false, true} ->
|
||||
recv_push(Config, both, undefined),
|
||||
State#state{subscription = both, pending_in = false};
|
||||
{to, false, _} ->
|
||||
put_event(Config, {is_approved, false}),
|
||||
%% BUG: we should not transition to 'both' state
|
||||
recv_push(Config, both, undefined),
|
||||
State#state{subscription = both};
|
||||
State#state{subscription = both, pending_in = false};
|
||||
{to, false, false} ->
|
||||
put_event(Config, {is_approved, true}),
|
||||
recv_push(Config, to, undefined, true),
|
||||
State#state{approved = true};
|
||||
{none, true, false} ->
|
||||
put_event(Config, {is_approved, true}),
|
||||
recv_push(Config, none, subscribe, true),
|
||||
State#state{approved = true};
|
||||
{none, false, false} ->
|
||||
put_event(Config, {is_approved, true}),
|
||||
recv_push(Config, none, undefined, true),
|
||||
State#state{approved = true};
|
||||
_ ->
|
||||
put_event(Config, {is_approved, false}),
|
||||
State
|
||||
end;
|
||||
%% RFC6121, A.2.4
|
||||
transition(Id, Config, out, unsubscribed,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
PeerJID = ?config(peer, Config),
|
||||
PeerBareJID = jid:remove_resource(PeerJID),
|
||||
send(Config, #presence{id = Id, to = PeerBareJID, type = unsubscribed}),
|
||||
case {Sub, Out, In} of
|
||||
{none, false, true} ->
|
||||
State#state{subscription = none, pending_in = false};
|
||||
{none, true, true} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
State#state{subscription = none, pending_in = false};
|
||||
{to, _, true} ->
|
||||
State#state{pending_in = false};
|
||||
{from, false, _} ->
|
||||
case {Sub, Out, In, Approved} of
|
||||
{none, false, true, true} ->
|
||||
recv_push(Config, none, undefined),
|
||||
State#state{subscription = none};
|
||||
{from, true, _} ->
|
||||
State#state{subscription = none, pending_in = false, approved = false};
|
||||
{none, false, true, false} ->
|
||||
State#state{subscription = none, pending_in = false, approved = false};
|
||||
{none, true, true, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
State#state{subscription = none};
|
||||
{both, _, _} ->
|
||||
State#state{subscription = none, pending_in = false, approved = false};
|
||||
{to, _, true, true} ->
|
||||
recv_push(Config, to, undefined),
|
||||
State#state{subscription = to};
|
||||
State#state{pending_in = false, approved = false};
|
||||
{to, _, true, false} ->
|
||||
State#state{pending_in = false, approved = false};
|
||||
{from, false, _, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
State#state{subscription = none, approved = false};
|
||||
{from, true, _, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
State#state{subscription = none, approved = false};
|
||||
{both, _, _, _} ->
|
||||
recv_push(Config, to, undefined),
|
||||
State#state{subscription = to, approved = false};
|
||||
{_, true, _, true} ->
|
||||
recv_push(Config, Sub, subscribe),
|
||||
State#state{approved = false};
|
||||
{_, false, _, true} ->
|
||||
recv_push(Config, Sub, undefined),
|
||||
State#state{approved = false};
|
||||
_ ->
|
||||
State
|
||||
State#state{approved = false}
|
||||
end;
|
||||
%% RFC6121, A.3.1
|
||||
transition(_, Config, in, subscribe = Type,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
case {Sub, Out, In} of
|
||||
{none, false, false} ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
put_event(Config, {is_approved, Approved}),
|
||||
case {Sub, Out, In, Approved} of
|
||||
{to, false, false, true} ->
|
||||
recv_push(Config, both, undefined, true),
|
||||
State#state{subscription = both};
|
||||
{none, true, false, true} ->
|
||||
recv_push(Config, from, subscribe, true),
|
||||
State#state{subscription = from};
|
||||
{none, false, false, true} ->
|
||||
recv_push(Config, from, undefined, true),
|
||||
State#state{subscription = from};
|
||||
{none, false, false, false} ->
|
||||
recv_subscription(Config, Type),
|
||||
State#state{pending_in = true};
|
||||
{none, true, false} ->
|
||||
{none, true, false, false} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{pending_in = true};
|
||||
{to, false, false} ->
|
||||
{to, false, false, false} ->
|
||||
%% BUG: we should not receive roster push in this state!
|
||||
recv_push(Config, to, undefined),
|
||||
recv_subscription(Config, Type),
|
||||
@@ -452,24 +502,24 @@ transition(_, Config, in, subscribe = Type,
|
||||
end;
|
||||
%% RFC6121, A.3.2
|
||||
transition(_, Config, in, unsubscribe = Type,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
case {Sub, Out, In} of
|
||||
{none, _, true} ->
|
||||
State#state{pending_in = false};
|
||||
{to, _, true} ->
|
||||
recv_push(Config, to, undefined),
|
||||
recv_push(Config, to, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{pending_in = false};
|
||||
{from, false, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none};
|
||||
{from, true, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none};
|
||||
{both, _, _} ->
|
||||
recv_push(Config, to, undefined),
|
||||
recv_push(Config, to, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = to};
|
||||
_ ->
|
||||
@@ -477,52 +527,58 @@ transition(_, Config, in, unsubscribe = Type,
|
||||
end;
|
||||
%% RFC6121, A.3.3
|
||||
transition(_, Config, in, subscribed = Type,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approve} = State) ->
|
||||
{is_approved, PeerApproved} = get_event(Config),
|
||||
case {Sub, Out, In} of
|
||||
{none, true, _} ->
|
||||
recv_push(Config, to, undefined),
|
||||
recv_push(Config, to, undefined, Approve),
|
||||
recv_subscription(Config, Type),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = to, pending_out = false, peer_available = true};
|
||||
{from, true, _} ->
|
||||
recv_push(Config, both, undefined),
|
||||
recv_push(Config, both, undefined, Approve),
|
||||
recv_subscription(Config, Type),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = both, pending_out = false, peer_available = true};
|
||||
{from, false, _} ->
|
||||
%% BUG: we should not transition to 'both' in this state
|
||||
recv_push(Config, both, undefined),
|
||||
recv_subscription(Config, Type),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = both, pending_out = false, peer_available = true};
|
||||
case PeerApproved of
|
||||
false ->
|
||||
%% BUG: we should not transition to 'both' in this state
|
||||
recv_push(Config, both, undefined, Approve),
|
||||
recv_subscription(Config, Type),
|
||||
recv_presence(Config, available),
|
||||
State#state{subscription = both, pending_out = false, peer_available = true};
|
||||
true ->
|
||||
State
|
||||
end;
|
||||
_ ->
|
||||
State
|
||||
end;
|
||||
%% RFC6121, A.3.4
|
||||
transition(_, Config, in, unsubscribed = Type,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
case {Sub, Out, In} of
|
||||
{none, true, true} ->
|
||||
%% BUG: we should receive roster push in this state!
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none, pending_out = false};
|
||||
{none, true, false} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none, pending_out = false};
|
||||
{none, false, false} ->
|
||||
State;
|
||||
{to, false, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_presence(Config, unavailable),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = none, peer_available = false};
|
||||
{from, true, false} ->
|
||||
recv_push(Config, from, undefined),
|
||||
recv_push(Config, from, undefined, Approved),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = from, pending_out = false};
|
||||
{both, _, _} ->
|
||||
recv_push(Config, from, undefined),
|
||||
recv_push(Config, from, undefined, Approved),
|
||||
recv_presence(Config, unavailable),
|
||||
recv_subscription(Config, Type),
|
||||
State#state{subscription = from, peer_available = false};
|
||||
@@ -550,15 +606,15 @@ transition(Id, Config, out, remove,
|
||||
#state{};
|
||||
%% Incoming roster remove
|
||||
transition(_, Config, in, remove,
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
|
||||
#state{subscription = Sub, pending_in = In, pending_out = Out, approved = Approved} = State) ->
|
||||
case {Sub, Out, In} of
|
||||
{none, true, _} ->
|
||||
ok;
|
||||
{from, false, _} ->
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_subscription(Config, unsubscribe);
|
||||
{from, true, _} ->
|
||||
recv_push(Config, none, subscribe),
|
||||
recv_push(Config, none, subscribe, Approved),
|
||||
recv_subscription(Config, unsubscribe);
|
||||
{to, false, _} ->
|
||||
%% BUG: we should receive push here
|
||||
@@ -567,9 +623,9 @@ transition(_, Config, in, remove,
|
||||
recv_subscription(Config, unsubscribed);
|
||||
{both, _, _} ->
|
||||
recv_presence(Config, unavailable),
|
||||
recv_push(Config, to, undefined),
|
||||
recv_push(Config, to, undefined, Approved),
|
||||
recv_subscription(Config, unsubscribe),
|
||||
recv_push(Config, none, undefined),
|
||||
recv_push(Config, none, undefined, Approved),
|
||||
recv_subscription(Config, unsubscribed);
|
||||
_ ->
|
||||
ok
|
||||
|
||||
@@ -130,6 +130,7 @@ init_config(Config) ->
|
||||
{db_xmlns, <<"">>},
|
||||
{mechs, []},
|
||||
{rosterver, false},
|
||||
{pre_approval, false},
|
||||
{lang, <<"en">>},
|
||||
{base_dir, BaseDir},
|
||||
{receiver, undefined},
|
||||
@@ -531,6 +532,8 @@ wait_auth_SASL_result(Config, ShouldFail) ->
|
||||
set_opt(csi, true, ConfigAcc);
|
||||
(#rosterver_feature{}, ConfigAcc) ->
|
||||
set_opt(rosterver, true, ConfigAcc);
|
||||
(#feature_pre_approval{}, ConfigAcc) ->
|
||||
set_opt(pre_approval, true, ConfigAcc);
|
||||
(#compression{methods = Ms}, ConfigAcc) ->
|
||||
set_opt(compression, Ms, ConfigAcc);
|
||||
(_, ConfigAcc) ->
|
||||
|
||||
Reference in New Issue
Block a user