From 90263cf0e7a095a619ca962282caf4a920af71dc Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 7 May 2009 23:35:59 +0000 Subject: [PATCH] Allow to get subscriptions on a given node (EJAB-712), and presence based fixes SVN Revision: 2066 --- ChangeLog | 8 ++ src/mod_pubsub/mod_pubsub.erl | 159 +++++++++++++++++++++++----------- 2 files changed, 118 insertions(+), 49 deletions(-) diff --git a/ChangeLog b/ChangeLog index 723c20281..d5565b2d5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2009-05-08 Christophe Romain + + * src/mod_pubsub/mod_pubsub.erl: Allow to get subscriptions on a given + node (EJAB-712); Improve XML schema validation on publish item; + Remove subscriptions of users whose authorization has been removed on + nodes with presence access model; Do not allow unsubscribed user to + subscribe node with presence access model. + 2009-05-08 Mickael Remond * src/ejabberd_app.erl: Better support for profiling. diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index c90190859..5a4981da5 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -54,6 +54,7 @@ %% exports for hooks -export([presence_probe/3, + in_subscription/6, out_subscription/4, remove_user/2, disco_local_identity/5, @@ -177,6 +178,7 @@ init([ServerHost, Opts]) -> ejabberd_hooks:add(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75), ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75), ejabberd_hooks:add(presence_probe_hook, ServerHost, ?MODULE, presence_probe, 50), + ejabberd_hooks:add(roster_in_subscription, ServerHost, ?MODULE, in_subscription, 50), ejabberd_hooks:add(roster_out_subscription, ServerHost, ?MODULE, out_subscription, 50), ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, remove_user, 50), ejabberd_hooks:add(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50), @@ -632,7 +634,13 @@ out_subscription(User, Server, JID, subscribed) -> end, Proc = gen_mod:get_module_proc(Server, ?PROCNAME), gen_server:cast(Proc, {presence, PUser, PServer, PResources, Owner}); -out_subscription(_, _, _, _) -> +out_subscription(_,_,_,_) -> + ok. +in_subscription(_, User, Server, Subscriber, unsubscribed, _) -> + Owner = jlib:make_jid(User, Server, ""), + Proc = gen_mod:get_module_proc(Server, ?PROCNAME), + gen_server:cast(Proc, {unsubscribe, Subscriber, Owner}); +in_subscription(_, _, _, _, _, _) -> ok. %% ------- @@ -703,6 +711,33 @@ handle_cast({remove_user, LUser, LServer}, State) -> delete_node(Host, ["home", LServer, LUser], Owner), {noreply, State}; +handle_cast({unsubscribe, Subscriber, Owner}, State) -> + Host = State#state.host, + BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), + lists:foreach(fun(PType) -> + {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Subscriber]), + lists:foreach(fun + ({Node, subscribed, JID}) -> + Action = fun(#pubsub_node{options = Options, owners = Owners, type = Type, id = NodeId}) -> + case get_option(Options, access_model) of + presence -> + case lists:member(BJID, Owners) of + true -> + node_call(Type, unsubscribe_node, [NodeId, Subscriber, JID, all]); + false -> + ok + end; + _ -> + ok + end + end, + transaction(Host, Node, Action, sync_dirty); + (_) -> + ok + end, Subscriptions) + end, State#state.plugins), + {noreply, State}; + handle_cast(_Msg, State) -> {noreply, State}. @@ -753,6 +788,7 @@ terminate(_Reason, #state{host = Host, ejabberd_hooks:delete(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75), ejabberd_hooks:delete(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75), ejabberd_hooks:delete(presence_probe_hook, ServerHost, ?MODULE, presence_probe, 50), + ejabberd_hooks:delete(roster_in_subscription, ServerHost, ?MODULE, in_subscription, 50), ejabberd_hooks:delete(roster_out_subscription, ServerHost, ?MODULE, out_subscription, 50), ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, remove_user, 50), ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50), @@ -1121,7 +1157,7 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, _Lang, Access, Plugins) -> end, [], xml:remove_cdata(Els)), get_items(Host, Node, From, SubId, MaxItems, ItemIDs); {get, "subscriptions"} -> - get_subscriptions(Host, From, Plugins); + get_subscriptions(Host, Node, From, Plugins); {get, "affiliations"} -> get_affiliations(Host, From, Plugins); {get, "options"} -> @@ -1542,39 +1578,42 @@ subscribe_node(Host, Node, From, JID) -> J -> jlib:jid_tolower(J) end, SubId = uniqid(), - Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - SubscribeFeature = lists:member("subscribe", Features), - SubscribeConfig = get_option(Options, subscribe), - AccessModel = get_option(Options, access_model), - SendLast = get_option(Options, send_last_published_item), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - {PresenceSubscription, RosterGroup} = - case Host of - {OUser, OServer, _} -> - get_roster_info(OUser, OServer, - Subscriber, AllowedGroups); - _ -> - case Subscriber of - {"", "", ""} -> {false, false}; - {U, S, _} -> get_roster_info(U, S, Subscriber, - AllowedGroups) - end - end, - if - not SubscribeFeature -> - %% Node does not support subscriptions - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")}; - not SubscribeConfig -> - %% Node does not support subscriptions - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")}; - true -> - node_call(Type, subscribe_node, - [NodeId, From, Subscriber, + Action = fun(#pubsub_node{options = Options, owners = [Owner|_], type = Type, id = NodeId}) -> + Features = features(Type), + SubscribeFeature = lists:member("subscribe", Features), + SubscribeConfig = get_option(Options, subscribe), + AccessModel = get_option(Options, access_model), + SendLast = get_option(Options, send_last_published_item), + AllowedGroups = get_option(Options, roster_groups_allowed, []), + {PresenceSubscription, RosterGroup} = + case Host of + {OUser, OServer, _} -> + get_roster_info(OUser, OServer, + Subscriber, AllowedGroups); + _ -> + case Subscriber of + {"", "", ""} -> + {false, false}; + _ -> + {OU, OS, _} = Owner, + get_roster_info(OU, OS, + Subscriber, AllowedGroups) + end + end, + if + not SubscribeFeature -> + %% Node does not support subscriptions + {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")}; + not SubscribeConfig -> + %% Node does not support subscriptions + {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")}; + true -> + node_call(Type, subscribe_node, + [NodeId, From, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup]) - end - end, + end + end, Reply = fun(Subscription) -> %% TODO, this is subscription-notification, should depends on node features Fields = @@ -1685,10 +1724,10 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> PayloadSize > PayloadMaxSize -> %% Entity attempts to publish very large payload {error, extended_error(?ERR_NOT_ACCEPTABLE, "payload-too-big")}; - PayloadCount == 0 -> + (PayloadCount == 0) and (Payload == []) -> %% Publisher attempts to publish to payload node with no payload {error, extended_error(?ERR_BAD_REQUEST, "payload-required")}; - PayloadCount > 1 -> + (PayloadCount > 1) or (PayloadCount == 0) -> %% Entity attempts to publish item with multiple payload elements {error, extended_error(?ERR_BAD_REQUEST, "invalid-payload")}; (DeliverPayloads == 0) and (PersistItems == 0) and (PayloadSize > 0) -> @@ -2009,12 +2048,12 @@ get_affiliations(Host, Node, JID) -> Action = fun(#pubsub_node{type = Type, id = NodeId}) -> Features = features(Type), RetrieveFeature = lists:member("modify-affiliations", Features), - Affiliation = node_call(Type, get_affiliation, [NodeId, JID]), + {result, Affiliation} = node_call(Type, get_affiliation, [NodeId, JID]), if not RetrieveFeature -> %% Service does not support modify affiliations {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "modify-affiliations")}; - Affiliation /= {result, owner} -> + Affiliation /= owner -> %% Entity is not an owner {error, ?ERR_FORBIDDEN}; true -> @@ -2105,14 +2144,15 @@ set_affiliations(Host, Node, From, EntitiesEls) -> end. -%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response} +%% @spec (Host, Node, JID, Plugins) -> {error, Reason} | {result, Response} %% Host = host() +%% Node = pubsubNode() %% JID = jid() %% Plugins = [Plugin::string()] %% Reason = stanzaError() %% Response = [pubsubIQResponse()] %% @doc

Return the list of subscriptions as an XMPP response.

-get_subscriptions(Host, JID, Plugins) when is_list(Plugins) -> +get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> Result = lists:foldl( fun(Type, {Status, Acc}) -> Features = features(Type), @@ -2130,36 +2170,57 @@ get_subscriptions(Host, JID, Plugins) when is_list(Plugins) -> case Result of {ok, Subscriptions} -> Entities = lists:flatmap( - fun({_, none}) -> []; - ({Node, Subscription}) -> + fun({_, none}) -> + []; + ({SubsNode, Subscription}) -> + case Node of + [] -> [{xmlelement, "subscription", - [{"node", node_to_string(Node)}, + [{"node", node_to_string(SubsNode)}, {"subscription", subscription_to_string(Subscription)}], []}]; - ({_, none, _}) -> []; - ({Node, Subscription, SubJID}) -> + SubsNode -> [{xmlelement, "subscription", - [{"node", node_to_string(Node)}, + [{"subscription", subscription_to_string(Subscription)}], + []}]; + _ -> + [] + end; + ({_, none, _}) -> + []; + ({SubsNode, Subscription, SubJID}) -> + case Node of + [] -> + [{xmlelement, "subscription", + [{"node", node_to_string(SubsNode)}, {"jid", jlib:jid_to_string(SubJID)}, {"subscription", subscription_to_string(Subscription)}], - []}] + []}]; + SubsNode -> + [{xmlelement, "subscription", + [{"jid", jlib:jid_to_string(SubJID)}, + {"subscription", subscription_to_string(Subscription)}], + []}]; + _ -> + [] + end end, lists:usort(lists:flatten(Subscriptions))), {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [{xmlelement, "subscriptions", [], Entities}]}]}; {Error, _} -> Error - end; + end. get_subscriptions(Host, Node, JID) -> Action = fun(#pubsub_node{type = Type, id = NodeId}) -> Features = features(Type), RetrieveFeature = lists:member("manage-subscriptions", Features), - Affiliation = node_call(Type, get_affiliation, [NodeId, JID]), + {result, Affiliation} = node_call(Type, get_affiliation, [NodeId, JID]), if not RetrieveFeature -> %% Service does not support manage subscriptions {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "manage-subscriptions")}; - Affiliation /= {result, owner} -> + Affiliation /= owner -> %% Entity is not an owner {error, ?ERR_FORBIDDEN}; true ->