]> granicus.if.org Git - ejabberd/commitdiff
Support XEP-0321: Remote Roster Management (EJAB-1381)
authorBadlop <badlop@process-one.net>
Wed, 26 Feb 2014 17:01:47 +0000 (18:01 +0100)
committerBadlop <badlop@process-one.net>
Wed, 26 Feb 2014 17:02:37 +0000 (18:02 +0100)
doc/guide.tex
src/mod_roster.erl

index 46563561e077aa86aeee91fe1e69fe752bfdd098..5d5bf2fdcf6a5201f47860b1efeb3ba7a6ce3975 100644 (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}
 
index 7415aa3ded6351fd4c7c8a3087ea430965ec03e0..4851b8fb5a9d858b41c82fa1c51b109a0d5a0090 100644 (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)