diff options
Diffstat (limited to 'src/mod_shared_roster.erl')
-rw-r--r-- | src/mod_shared_roster.erl | 452 |
1 files changed, 402 insertions, 50 deletions
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index 89c4777cb..4d323b7ee 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -5,7 +5,7 @@ %%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@process-one.net> %%% %%% -%%% ejabberd, Copyright (C) 2002-2008 Process-one +%%% ejabberd, Copyright (C) 2002-2009 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -16,7 +16,7 @@ %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %%% General Public License for more details. -%%% +%%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA @@ -37,6 +37,8 @@ process_item/2, in_subscription/6, out_subscription/4, + register_user/2, + remove_user/2, list_groups/1, create_group/2, create_group/3, @@ -45,6 +47,7 @@ set_group_opts/3, get_group_users/2, get_group_explicit_users/2, + is_user_in_group/3, add_user_to_group/3, remove_user_from_group/3]). @@ -81,7 +84,11 @@ start(Host, _Opts) -> ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE, get_jid_info, 70), ejabberd_hooks:add(roster_process_item, Host, - ?MODULE, process_item, 50). + ?MODULE, process_item, 50), + ejabberd_hooks:add(register_user, Host, + ?MODULE, register_user, 50), + ejabberd_hooks:add(remove_user, Host, + ?MODULE, remove_user, 50). %%ejabberd_hooks:add(remove_user, Host, %% ?MODULE, remove_user, 50), @@ -101,7 +108,11 @@ stop(Host) -> ejabberd_hooks:delete(roster_get_jid_info, Host, ?MODULE, get_jid_info, 70), ejabberd_hooks:delete(roster_process_item, Host, - ?MODULE, process_item, 50). + ?MODULE, process_item, 50), + ejabberd_hooks:delete(register_user, Host, + ?MODULE, register_user, 50), + ejabberd_hooks:delete(remove_user, Host, + ?MODULE, remove_user, 50). %%ejabberd_hooks:delete(remove_user, Host, %% ?MODULE, remove_user, 50), @@ -150,12 +161,13 @@ get_user_roster(Items, US) -> {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)], SRItems ++ NewItems1. -%% This function in use to rewrite the roster entries when moving or renaming +%% This function rewrites the roster entries when moving or renaming %% them in the user contact list. process_item(RosterItem, Host) -> - USFrom = RosterItem#roster.us, - {User,Server,_Resource} = RosterItem#roster.jid, - USTo = {User,Server}, + USFrom = {UserFrom, ServerFrom} = RosterItem#roster.us, + {UserTo, ServerTo, ResourceTo} = RosterItem#roster.jid, + NameTo = RosterItem#roster.name, + USTo = {UserTo, ServerTo}, DisplayedGroups = get_user_displayed_groups(USFrom), CommonGroups = lists:filter(fun(Group) -> is_user_in_group(USTo, Group, Host) @@ -169,9 +181,96 @@ process_item(RosterItem, Host) -> end, CommonGroups), RosterItem#roster{subscription = both, ask = none, groups=[GroupNames]}; - _ -> RosterItem#roster{subscription = both, ask = none} + %% Both users have at least a common shared group, + %% So each user can see the other + _ -> + %% Check if the list of groups of the new roster item + %% include at least a new one + case lists:subtract(RosterItem#roster.groups, CommonGroups) of + %% If it doesn't, then remove this user from any + %% existing roster groups. + [] -> + %% Remove pending subscription by setting it + %% unsubscribed. + Mod = get_roster_mod(ServerFrom), + + %% Remove pending out subscription + Mod:out_subscription(UserTo, ServerTo, + jlib:make_jid(UserFrom, ServerFrom, ""), + unsubscribe), + + %% Remove pending in subscription + Mod:in_subscription(aaaa, UserFrom, ServerFrom, + jlib:make_jid(UserTo, ServerTo, ""), + unsubscribe, ""), + + %% But we're still subscribed, so respond as such. + RosterItem#roster{subscription = both, ask = none}; + %% If so, it means the user wants to add that contact + %% to his personal roster + PersonalGroups -> + %% Store roster items in From and To rosters + set_new_rosteritems(UserFrom, ServerFrom, + UserTo, ServerTo, ResourceTo, NameTo, + PersonalGroups) + end end. +build_roster_record(User1, Server1, User2, Server2, Name2, Groups) -> + USR2 = {User2, Server2, ""}, + #roster{usj = {User1, Server1, USR2}, + us = {User1, Server1}, + jid = USR2, + name = Name2, + subscription = both, + ask = none, + groups = Groups + }. + +set_new_rosteritems(UserFrom, ServerFrom, + UserTo, ServerTo, ResourceTo, NameTo, GroupsFrom) -> + Mod = get_roster_mod(ServerFrom), + + RIFrom = build_roster_record(UserFrom, ServerFrom, + UserTo, ServerTo, NameTo, GroupsFrom), + set_item(UserFrom, ServerFrom, ResourceTo, RIFrom), + JIDTo = jlib:make_jid(UserTo, ServerTo, ""), + + JIDFrom = jlib:make_jid(UserFrom, ServerFrom, ""), + RITo = build_roster_record(UserTo, ServerTo, + UserFrom, ServerFrom, UserFrom,[]), + set_item(UserTo, ServerTo, "", RITo), + + %% From requests + Mod:out_subscription(UserFrom, ServerFrom, JIDTo, subscribe), + Mod:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribe, ""), + + %% To accepts + Mod:out_subscription(UserTo, ServerTo, JIDFrom, subscribed), + Mod:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribed, ""), + + %% To requests + Mod:out_subscription(UserTo, ServerTo, JIDFrom, subscribe), + Mod:in_subscription(aaa, UserFrom, ServerFrom, JIDTo, subscribe, ""), + + %% From accepts + Mod:out_subscription(UserFrom, ServerFrom, JIDTo, subscribed), + Mod:in_subscription(aaa, UserTo, ServerTo, JIDFrom, subscribed, ""), + + RIFrom. + +set_item(User, Server, Resource, Item) -> + ResIQ = #iq{type = set, xmlns = ?NS_ROSTER, + id = "push" ++ randoms:get_string(), + sub_el = [{xmlelement, "query", + [{"xmlns", ?NS_ROSTER}], + [mod_roster:item_to_xml(Item)]}]}, + ejabberd_router:route( + jlib:make_jid(User, Server, Resource), + jlib:make_jid("", Server, ""), + jlib:iq_to_xml(ResIQ)). + + get_subscription_lists({F, T}, User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), @@ -216,6 +315,18 @@ get_jid_info({Subscription, Groups}, User, Server, JID) -> in_subscription(Acc, User, Server, JID, Type, _Reason) -> process_subscription(in, User, Server, JID, Type, Acc). +out_subscription(UserFrom, ServerFrom, JIDTo, unsubscribed) -> + Mod = get_roster_mod(ServerFrom), + + %% Remove pending out subscription + #jid{luser = UserTo, lserver = ServerTo} = JIDTo, + JIDFrom = jlib:make_jid(UserFrom, UserTo, ""), + Mod:out_subscription(UserTo, ServerTo, JIDFrom, unsubscribe), + + %% Remove pending in subscription + Mod:in_subscription(aaaa, UserFrom, ServerFrom, JIDTo, unsubscribe, ""), + + process_subscription(out, UserFrom, ServerFrom, JIDTo, unsubscribed, false); out_subscription(User, Server, JID, Type) -> process_subscription(out, User, Server, JID, Type, false). @@ -252,6 +363,14 @@ list_groups(Host) -> [{'==', '$2', Host}], ['$1']}]). +groups_with_opts(Host) -> + Gs = mnesia:dirty_select( + sr_group, + [{#sr_group{group_host={'$1', Host}, opts='$2', _='_'}, + [], + [['$1','$2']] }]), + lists:map(fun([G,O]) -> {G, O} end, Gs). + create_group(Host, Group) -> create_group(Host, Group, []). @@ -297,7 +416,7 @@ get_user_groups(US) -> [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host]; _ -> [] - end ++ get_all_users_groups(Host). + end ++ get_special_users_groups(Host). is_group_enabled(Host, Group) -> case catch mnesia:dirty_read(sr_group, {Group, Host}) of @@ -328,23 +447,75 @@ get_group_users(Host, Group) -> [] end ++ get_group_explicit_users(Host, Group). +get_group_users(_User, Host, Group, GroupOpts) -> + case proplists:get_value(all_users, GroupOpts, false) of + true -> + ejabberd_auth:get_vh_registered_users(Host); + false -> + [] + end ++ get_group_explicit_users(Host, Group). + +%% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}] get_group_explicit_users(Host, Group) -> - case catch mnesia:dirty_index_read( - sr_user, {Group, Host}, #sr_user.group_host) of - Rs when is_list(Rs) -> - [R#sr_user.us || R <- Rs]; - _ -> - [] - end. + Read = (catch mnesia:dirty_index_read( + sr_user, + {Group, Host}, + #sr_user.group_host)), + case Read of + Rs when is_list(Rs) -> + [R#sr_user.us || R <- Rs]; + _ -> + [] + end. get_group_name(Host, Group) -> get_group_opt(Host, Group, name, Group). -get_all_users_groups(Host) -> +%% Get list of names of groups that have @all@ in the memberlist +get_special_users_groups(Host) -> lists:filter( - fun(Group) -> get_group_opt(Host, Group, all_users, false) end, + fun(Group) -> + get_group_opt(Host, Group, all_users, false) + end, list_groups(Host)). +%% Given two lists of groupnames and their options, +%% return the list of displayed groups to the second list +displayed_groups(GroupsOpts, SelectedGroupsOpts) -> + DisplayedGroups = + lists:usort( + lists:flatmap( + fun({_Group, Opts}) -> + [G || G <- proplists:get_value(displayed_groups, Opts, []), + not lists:member(disabled, Opts)] + end, SelectedGroupsOpts)), + [G || G <- DisplayedGroups, + not lists:member(disabled, proplists:get_value(G, GroupsOpts, []))]. + +%% Given a list of group names with options, +%% for those that have @all@ in memberlist, +%% get the list of groups displayed +get_special_displayed_groups(GroupsOpts) -> + Groups = lists:filter( + fun({_Group, Opts}) -> + proplists:get_value(all_users, Opts, false) + end, GroupsOpts), + displayed_groups(GroupsOpts, Groups). + +%% Given a username and server, and a list of group names with options, +%% for the list of groups of that server that user is member +%% get the list of groups displayed +get_user_displayed_groups(LUser, LServer, GroupsOpts) -> + Groups = case catch mnesia:dirty_read(sr_user, {LUser, LServer}) of + Rs when is_list(Rs) -> + [{Group, proplists:get_value(Group, GroupsOpts, [])} || + #sr_user{group_host = {Group, H}} <- Rs, H == LServer]; + _ -> + [] + end, + displayed_groups(GroupsOpts, Groups). + +%% @doc Get the list of groups that are displayed to this user get_user_displayed_groups(US) -> Host = element(2, US), DisplayedGroups1 = @@ -360,27 +531,199 @@ get_user_displayed_groups(US) -> end, get_user_groups(US))), [Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)]. -is_user_in_group(US, Group, Host) -> +is_user_in_group({_U, S} = US, Group, Host) -> case catch mnesia:dirty_match_object( #sr_user{us=US, group_host={Group, Host}}) of - [] -> false; + [] -> lists:member(US, get_group_users(S, Group)); _ -> true end. + +%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok} add_user_to_group(Host, US, Group) -> - R = #sr_user{us = US, group_host = {Group, Host}}, - F = fun() -> - mnesia:write(R) - end, - mnesia:transaction(F). + {LUser, LServer} = US, + case regexp:match(LUser, "^@.+@$") of + {match,_,_} -> + GroupOpts = mod_shared_roster:get_group_opts(Host, Group), + AllUsersOpt = + case LUser == "@all@" of + true -> [{all_users, true}]; + false -> [] + end, + mod_shared_roster:set_group_opts( + Host, Group, + GroupOpts ++ AllUsersOpt); + nomatch -> + %% Push this new user to members of groups where this group is displayed + push_user_to_displayed(LUser, LServer, Group, both), + %% Push members of groups that are displayed to this group + push_displayed_to_user(LUser, LServer, Group, Host, both), + R = #sr_user{us = US, group_host = {Group, Host}}, + F = fun() -> + mnesia:write(R) + end, + mnesia:transaction(F) + end. + +push_displayed_to_user(LUser, LServer, Group, Host, Subscription) -> + GroupsOpts = groups_with_opts(LServer), + GroupOpts = proplists:get_value(Group, GroupsOpts, []), + DisplayedGroups = proplists:get_value(displayed_groups, GroupOpts, []), + [push_members_to_user(LUser, LServer, DGroup, Host, Subscription) || DGroup <- DisplayedGroups]. remove_user_from_group(Host, US, Group) -> GroupHost = {Group, Host}, - R = #sr_user{us = US, group_host = GroupHost}, - F = fun() -> - mnesia:delete_object(R) - end, - mnesia:transaction(F). + {LUser, LServer} = US, + case regexp:match(LUser, "^@.+@$") of + {match,_,_} -> + GroupOpts = mod_shared_roster:get_group_opts(Host, Group), + NewGroupOpts = + case LUser of + "@all@" -> + lists:filter(fun(X) -> X/={all_users,true} end, GroupOpts) + end, + mod_shared_roster:set_group_opts(Host, Group, NewGroupOpts); + nomatch -> + R = #sr_user{us = US, group_host = GroupHost}, + F = fun() -> + mnesia:delete_object(R) + end, + Result = mnesia:transaction(F), + %% Push removal of the old user to members of groups where the group that this user was members was displayed + push_user_to_displayed(LUser, LServer, Group, remove), + %% Push removal of members of groups that where displayed to the group which this user has left + push_displayed_to_user(LUser, LServer, Group, Host, remove), + Result + end. + + +push_members_to_user(LUser, LServer, Group, Host, Subscription) -> + GroupsOpts = groups_with_opts(LServer), + GroupOpts = proplists:get_value(Group, GroupsOpts, []), + GroupName = proplists:get_value(name, GroupOpts, Group), + Members = get_group_users(Host, Group), + lists:foreach( + fun({U, S}) -> + push_roster_item(LUser, LServer, U, S, GroupName, Subscription) + end, Members). + +register_user(User, Server) -> + %% Get list of groups where this user is member + Groups = get_user_groups({User, Server}), + %% Push this user to members of groups where is displayed a group which this user is member + [push_user_to_displayed(User, Server, Group, both) || Group <- Groups]. + +remove_user(User, Server) -> + push_user_to_members(User, Server, remove). + +push_user_to_members(User, Server, Subscription) -> + LUser = jlib:nodeprep(User), + LServer = jlib:nameprep(Server), + GroupsOpts = groups_with_opts(LServer), + SpecialGroups = get_special_displayed_groups(GroupsOpts), + UserGroups = get_user_displayed_groups(LUser, LServer, GroupsOpts), + lists:foreach( + fun(Group) -> + GroupOpts = proplists:get_value(Group, GroupsOpts, []), + GroupName = proplists:get_value(name, GroupOpts, Group), + lists:foreach( + fun({U, S}) -> + push_roster_item(U, S, LUser, LServer, GroupName, Subscription) + end, get_group_users(LUser, LServer, Group, GroupOpts)) + end, lists:usort(SpecialGroups++UserGroups)). + +push_user_to_displayed(LUser, LServer, Group, Subscription) -> + GroupsOpts = groups_with_opts(LServer), + GroupOpts = proplists:get_value(Group, GroupsOpts, []), + GroupName = proplists:get_value(name, GroupOpts, Group), + DisplayedToGroupsOpts = displayed_to_groups(Group, LServer), + [push_user_to_group(LUser, LServer, GroupD, GroupName, Subscription) || {GroupD, _Opts} <- DisplayedToGroupsOpts]. + +push_user_to_group(LUser, LServer, Group, GroupName, Subscription) -> + lists:foreach( + fun({U, S}) -> + push_roster_item(U, S, LUser, LServer, GroupName, Subscription) + end, get_group_users(LServer, Group)). + +%% Get list of groups to which this group is displayed +displayed_to_groups(GroupName, LServer) -> + GroupsOpts = groups_with_opts(LServer), + lists:filter( + fun({_Group, Opts}) -> + lists:member(GroupName, proplists:get_value(displayed_groups, Opts, [])) + end, GroupsOpts). + +push_item(_User, _Server, _From, none) -> + ok; +push_item(User, Server, From, Item) -> + %% It was + %% ejabberd_sm:route(jlib:make_jid("", "", ""), + %% jlib:make_jid(User, Server, "") + %% why? + ejabberd_sm:route(From, jlib:make_jid(User, Server, ""), + {xmlelement, "broadcast", [], + [{item, + Item#roster.jid, + Item#roster.subscription}]}), + Stanza = jlib:iq_to_xml( + #iq{type = set, xmlns = ?NS_ROSTER, + id = "push" ++ randoms:get_string(), + sub_el = [{xmlelement, "query", + [{"xmlns", ?NS_ROSTER}], + [item_to_xml(Item)]}]}), + lists:foreach( + fun(Resource) -> + JID = jlib:make_jid(User, Server, Resource), + ejabberd_router:route(JID, JID, Stanza) + end, ejabberd_sm:get_user_resources(User, Server)). + +push_roster_item(User, Server, ContactU, ContactS, GroupName, Subscription) -> + Item = #roster{usj = {User, Server, {ContactU, ContactS, ""}}, + us = {User, Server}, + jid = {ContactU, ContactS, ""}, + name = "", + subscription = Subscription, + ask = none, + groups = [GroupName]}, + push_item(User, Server, jlib:make_jid("", Server, ""), Item). + +item_to_xml(Item) -> + Attrs1 = [{"jid", jlib:jid_to_string(Item#roster.jid)}], + Attrs2 = case Item#roster.name of + "" -> + Attrs1; + Name -> + [{"name", Name} | Attrs1] + end, + Attrs3 = case Item#roster.subscription of + none -> + [{"subscription", "none"} | Attrs2]; + from -> + [{"subscription", "from"} | Attrs2]; + to -> + [{"subscription", "to"} | Attrs2]; + both -> + [{"subscription", "both"} | Attrs2]; + remove -> + [{"subscription", "remove"} | Attrs2] + end, + Attrs4 = case ask_to_pending(Item#roster.ask) of + out -> + [{"ask", "subscribe"} | Attrs3]; + both -> + [{"ask", "subscribe"} | Attrs3]; + _ -> + Attrs3 + end, + SubEls1 = lists:map(fun(G) -> + {xmlelement, "group", [], [{xmlcdata, G}]} + end, Item#roster.groups), + SubEls = SubEls1 ++ Item#roster.xs, + {xmlelement, "item", Attrs4, SubEls}. + +ask_to_pending(subscribe) -> out; +ask_to_pending(unsubscribe) -> none; +ask_to_pending(Ask) -> Ask. %%--------------------- @@ -430,10 +773,10 @@ list_shared_roster_groups(Host, Query, Lang) -> ] )] )]), - [?XC("h1", ?T("Shared Roster Groups"))] ++ + ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++ case Res of - ok -> [?CT("Submitted"), ?P]; - error -> [?CT("Bad format"), ?P]; + ok -> [?XREST("Submitted")]; + error -> [?XREST("Bad format")]; nothing -> [] end ++ [?XAE("form", [{"action", ""}, {"method", "post"}], @@ -496,8 +839,9 @@ shared_roster_group(Host, Group, Query, Lang) -> [] end ++ [[us_to_list(Member), $\n] || Member <- Members], FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups], + DescNL = length(element(2, regexp:split(Description, "\n"))), FGroup = - ?XAE("table", [], + ?XAE("table", [{"class", "withtextareas"}], [?XE("tbody", [?XE("tr", [?XCT("td", "Name:"), @@ -506,34 +850,34 @@ shared_roster_group(Host, Group, Query, Lang) -> ), ?XE("tr", [?XCT("td", "Description:"), - ?XE("td", [?XAC("textarea", [{"name", "description"}, - {"rows", "3"}, - {"cols", "20"}], - Description)]) + ?XE("td", [ + ?TEXTAREA("description", integer_to_list(lists:max([3, DescNL])), "20", Description) + ] + ) ] ), ?XE("tr", [?XCT("td", "Members:"), - ?XE("td", [?XAC("textarea", [{"name", "members"}, - {"rows", "3"}, - {"cols", "20"}], - FMembers)]) + ?XE("td", [ + ?TEXTAREA("members", integer_to_list(lists:max([3, length(FMembers)])), "20", FMembers) + ] + ) ] ), ?XE("tr", [?XCT("td", "Displayed Groups:"), - ?XE("td", [?XAC("textarea", [{"name", "dispgroups"}, - {"rows", "3"}, - {"cols", "20"}], - FDisplayedGroups)]) + ?XE("td", [ + ?TEXTAREA("dispgroups", integer_to_list(lists:max([3, length(FDisplayedGroups)])), "20", FDisplayedGroups) + ] + ) ] )] )]), - [?XC("h1", ?T("Shared Roster Groups"))] ++ + ?H1GL(?T("Shared Roster Groups"), "modsharedroster", "mod_shared_roster") ++ [?XC("h2", ?T("Group ") ++ Group)] ++ case Res of - ok -> [?CT("Submitted"), ?P]; - error -> [?CT("Bad format"), ?P]; + ok -> [?XREST("Submitted")]; + error -> [?XREST("Bad format")]; nothing -> [] end ++ [?XAE("form", [{"action", ""}, {"method", "post"}], @@ -617,6 +961,14 @@ shared_roster_group_parse_query(Host, Group, Query) -> nothing end. +%% Get the roster module for Server. +get_roster_mod(Server) -> + case lists:member(mod_roster_odbc, + gen_mod:loaded_modules(Server)) of + true -> mod_roster_odbc; + false -> mod_roster + end. + get_opt(Opts, Opt, Default) -> case lists:keysearch(Opt, 1, Opts) of {value, {_, Val}} -> |