Support XEP-0321: Remote Roster Management (EJAB-1381)

This commit is contained in:
Badlop 2014-02-26 18:01:47 +01:00
parent 46b2d91105
commit e211bf522e
2 changed files with 109 additions and 6 deletions

View File

@ -4052,15 +4052,28 @@ Options:
not add/remove/modify contacts,
or subscribe/unsubscribe presence.
By default there aren't restrictions.
\titem{managers} \ind{options!managers}
List of remote entities that can manage users rosters using Remote Roster Management
(\xepref{0321}).
The protocol sections implemented are:
\term{4.2. The remote entity requests current user's roster}.
\term{4.3. The user updates roster}.
\term{4.4. The remote entity updates the user's roster}.
A remote entity cab only get or modify roster items that have the same domain as the entity.
Default value is: \term{[]}.
\end{description}
This example configuration enables Roster Versioning with storage of current id:
This example configuration enables Roster Versioning with storage of current id.
The ICQ and MSN transports can get ICQ and MSN contacts, add them, or remove them for any local account:
\begin{verbatim}
modules:
...
mod_roster:
versioning: true
store_current_id: true
managers:
- "icq.example.org"
- "msn.example.org"
...
\end{verbatim}

View File

@ -130,6 +130,9 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_ROSTER).
process_iq(From, To, IQ) when ((From#jid.luser == <<"">>) andalso (From#jid.resource == <<"">>)) ->
process_iq_manager(From, To, IQ);
process_iq(From, To, IQ) ->
#iq{sub_el = SubEl} = IQ,
#jid{lserver = LServer} = From,
@ -465,15 +468,16 @@ try_process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
process_iq_set(From, To, IQ)
end.
process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) ->
#xmlel{children = Els} = SubEl,
lists:foreach(fun (El) -> process_item_set(From, To, El)
Managed = is_managed_from_id(Id),
lists:foreach(fun (El) -> process_item_set(From, To, El, Managed)
end,
Els),
IQ#iq{type = result, sub_el = []}.
process_item_set(From, To,
#xmlel{attrs = Attrs, children = Els}) ->
#xmlel{attrs = Attrs, children = Els}, Managed) ->
JID1 = jlib:string_to_jid(xml:get_attr_s(<<"jid">>,
Attrs)),
#jid{user = User, luser = LUser, lserver = LServer} =
@ -484,12 +488,13 @@ process_item_set(From, To,
LJID = jlib:jid_tolower(JID1),
F = fun () ->
Item = get_roster_by_jid_t(LUser, LServer, LJID),
Item1 = process_item_attrs(Item, Attrs),
Item1 = process_item_attrs_managed(Item, Attrs, Managed),
Item2 = process_item_els(Item1, Els),
case Item2#roster.subscription of
remove -> del_roster_t(LUser, LServer, LJID);
_ -> update_roster_t(LUser, LServer, LJID, Item2)
end,
send_itemset_to_managers(From, Item2, Managed),
Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2,
[LServer]),
@ -511,7 +516,7 @@ process_item_set(From, To,
?DEBUG("ROSTER: roster item set error: ~p~n", [E]), ok
end
end;
process_item_set(_From, _To, _) -> ok.
process_item_set(_From, _To, _, _Managed) -> ok.
process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
case Attr of
@ -1554,6 +1559,91 @@ webadmin_user(Acc, _User, _Server, Lang) ->
Acc ++
[?XE(<<"h3">>, [?ACT(<<"roster/">>, <<"Roster">>)])].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Implement XEP-0321 Remote Roster Management
process_iq_manager(From, To, IQ) ->
%% Check what access is allowed for From to To
MatchDomain = From#jid.lserver,
case is_domain_managed(MatchDomain, To#jid.lserver) of
true ->
process_iq_manager2(MatchDomain, To, IQ);
false ->
#iq{sub_el = SubEl} = IQ,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
end.
process_iq_manager2(MatchDomain, To, IQ) ->
%% If IQ is SET, filter the input IQ
IQFiltered = maybe_filter_request(MatchDomain, IQ),
%% Call the standard function with reversed JIDs
IdInitial = IQFiltered#iq.id,
ResIQ = process_iq(To, To, IQFiltered#iq{id = <<"roster-remotely-managed">>}),
%% Filter the output IQ
filter_stanza(MatchDomain, ResIQ#iq{id = IdInitial}).
is_domain_managed(ContactHost, UserHost) ->
Managers = gen_mod:get_module_opt(UserHost, ?MODULE, managers,
fun(B) when is_list(B) -> B end,
[]),
lists:member(ContactHost, Managers).
maybe_filter_request(MatchDomain, IQ) when IQ#iq.type == set ->
filter_stanza(MatchDomain, IQ);
maybe_filter_request(_MatchDomain, IQ) ->
IQ.
filter_stanza(_MatchDomain, #iq{sub_el = []} = IQ) ->
IQ;
filter_stanza(MatchDomain, #iq{sub_el = [SubEl | _]} = IQ) ->
#iq{sub_el = SubElFiltered} = IQRes =
filter_stanza(MatchDomain, IQ#iq{sub_el = SubEl}),
IQRes#iq{sub_el = [SubElFiltered]};
filter_stanza(MatchDomain, #iq{sub_el = SubEl} = IQ) ->
#xmlel{name = Type, attrs = Attrs, children = Items} = SubEl,
ItemsFiltered = lists:filter(
fun(Item) ->
is_item_of_domain(MatchDomain, Item) end, Items),
SubElFiltered = #xmlel{name=Type, attrs = Attrs, children = ItemsFiltered},
IQ#iq{sub_el = SubElFiltered}.
is_item_of_domain(MatchDomain, #xmlel{} = El) ->
lists:any(fun(Attr) -> is_jid_of_domain(MatchDomain, Attr) end, El#xmlel.attrs);
is_item_of_domain(_MatchDomain, {xmlcdata, _}) ->
false.
is_jid_of_domain(MatchDomain, {<<"jid">>, JIDString}) ->
case jlib:string_to_jid(JIDString) of
JID when JID#jid.lserver == MatchDomain -> true;
_ -> false
end;
is_jid_of_domain(_, _) ->
false.
process_item_attrs_managed(Item, Attrs, true) ->
process_item_attrs_ws(Item, Attrs);
process_item_attrs_managed(Item, _Attrs, false) ->
process_item_attrs(Item, _Attrs).
send_itemset_to_managers(_From, _Item, true) ->
ok;
send_itemset_to_managers(From, Item, false) ->
{_, UserHost} = Item#roster.us,
{_ContactUser, ContactHost, _ContactResource} = Item#roster.jid,
%% Check if the component is an allowed manager
IsManager = is_domain_managed(ContactHost, UserHost),
case IsManager of
true -> push_item(<<"">>, ContactHost, <<"">>, From, Item);
false -> ok
end.
is_managed_from_id(<<"roster-remotely-managed">>) ->
true;
is_managed_from_id(_Id) ->
false.
export(_Server) ->
[{roster,
fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)