Files @ 42dcd95799f8
Branch filter:

Location: portage-overlay.git/net-im/ejabberd/files/e211bf522ecfa0f8177f20c5e7ff21a1a47f940b.patch

chain
commit old change
From e211bf522ecfa0f8177f20c5e7ff21a1a47f940b Mon Sep 17 00:00:00 2001
From: Badlop <badlop@process-one.net>
Date: Wed, 26 Feb 2014 18:01:47 +0100
Subject: [PATCH] Support XEP-0321: Remote Roster Management (EJAB-1381)

---
 doc/guide.tex      |  15 +++++++-
 src/mod_roster.erl | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 109 insertions(+), 6 deletions(-)

diff --git a/doc/guide.tex b/doc/guide.tex
index 4656356..5d5bf2f 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -4052,15 +4052,28 @@
    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}
 
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index 7415aa3..4851b8f 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -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)
-- 
1.8.5.5