From 7808dc11afaf43bd058d585f62f263a46b0f0b82 Mon Sep 17 00:00:00 2001 From: Badlop Date: Tue, 23 Dec 2008 19:15:33 +0000 Subject: [PATCH] * src/acl.erl: New ACL: shared_group (thanks to Maxim Ryazanov) * doc/guide.tex: Likewise * src/mod_shared_roster.erl: Push new group members when registered or manually added to group: EJAB-730 EJAB-731 EJAB-732 EJAB-767 EJAB-794. When user is added to group, push it to other members, and other members to it. When user is removed from group, push deletion to other members, and other members to it. When user is registered, push him to members of group @all@. When user is deleted, push deletion to members of group @all@. Document several functions in mod_shared_roster. * src/ejabberd_auth.erl: Rename hook user_registered to register_user, for name consistency with the widely used hook remove_user. Run hook register_user in ejabberd_auth, so it's run when account is created with any method. Run hook remove_user in ejabberd_auth, so it's run when account is deleted with any method. * src/ejabberd_auth_internal.erl: Likewise * src/ejabberd_auth_ldap.erl: Likewise * src/ejabberd_auth_odbc.erl: Likewise * src/ejabberd_auth_pam.erl: Likewise * src/mod_register.erl: Likewise SVN Revision: 1752 --- ChangeLog | 24 ++++++++ doc/guide.tex | 8 +++ src/acl.erl | 4 ++ src/ejabberd_auth.erl | 42 ++++++++++--- src/ejabberd_auth_internal.erl | 9 ++- src/ejabberd_auth_ldap.erl | 1 + src/ejabberd_auth_odbc.erl | 11 ++-- src/ejabberd_auth_pam.erl | 2 +- src/mod_register.erl | 2 - src/mod_shared_roster.erl | 109 ++++++++++++++++++++++++++++----- 10 files changed, 181 insertions(+), 31 deletions(-) diff --git a/ChangeLog b/ChangeLog index f92551ad1..14ecfa565 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,29 @@ 2008-12-23 Badlop + * src/acl.erl: New ACL: shared_group (thanks to Maxim Ryazanov) + * doc/guide.tex: Likewise + + * src/mod_shared_roster.erl: Push new group members when + registered or manually added to group: EJAB-730 EJAB-731 EJAB-732 + EJAB-767 EJAB-794. When user is added to group, push it to other + members, and other members to it. When user is removed from group, + push deletion to other members, and other members to it. When user + is registered, push him to members of group @all@. When user is + deleted, push deletion to members of group @all@. Document several + functions in mod_shared_roster. + + * src/ejabberd_auth.erl: Rename hook user_registered to + register_user, for name consistency with the widely used hook + remove_user. Run hook register_user in ejabberd_auth, so it's run + when account is created with any method. Run hook remove_user in + ejabberd_auth, so it's run when account is deleted with any + method. + * src/ejabberd_auth_internal.erl: Likewise + * src/ejabberd_auth_ldap.erl: Likewise + * src/ejabberd_auth_odbc.erl: Likewise + * src/ejabberd_auth_pam.erl: Likewise + * src/mod_register.erl: Likewise + * src/jlib.erl: Implementation of XEP-0059 Result Set Management (thanks to Eric Cestari)(EJAB-807) * src/jlib.hrl: Likewise diff --git a/doc/guide.tex b/doc/guide.tex index 0ca65220b..9501c0cc5 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -1239,6 +1239,14 @@ declarations of ACLs in the configuration file have the following syntax: \begin{verbatim} {acl, mucklres, {resource, "muckl"}}. \end{verbatim} +\titem{\{shared\_group, \}} Matches any member of a Shared Roster Group with name \term{} in the virtual host. Example: +\begin{verbatim} +{acl, techgroupmembers, {shared_group, "techteam"}}. +\end{verbatim} +\titem{\{shared\_group, , \}} Matches any member of a Shared Roster Group with name \term{} in the virtual host \term{}. Example: +\begin{verbatim} +{acl, techgroupmembers, {shared_group, "techteam", "example.org"}}. +\end{verbatim} \titem{\{user\_regexp, \}} Matches any local user with a name that matches \term{} on local virtual hosts. Example: \begin{verbatim} diff --git a/src/acl.erl b/src/acl.erl index c135c254b..4088c856d 100644 --- a/src/acl.erl +++ b/src/acl.erl @@ -180,6 +180,10 @@ match_acl(ACL, JID, Host) -> ((Host == global) andalso lists:member(Server, ?MYHOSTS))) andalso is_regexp_match(User, UR); + {shared_group, G} -> + mod_shared_roster:is_user_in_group({User, Server}, G, Host); + {shared_group, G, H} -> + mod_shared_roster:is_user_in_group({User, Server}, G, H); {user_regexp, UR, S} -> (S == Server) andalso is_regexp_match(User, UR); diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index db37e26ac..c69dda602 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -139,6 +139,7 @@ set_password(User, Server, Password) -> Res end, {error, not_allowed}, auth_modules(Server)). +%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed} try_register(_User, _Server, "") -> %% We do not allow empty password {error, not_allowed}; @@ -149,12 +150,19 @@ try_register(User, Server, Password) -> false -> case lists:member(jlib:nameprep(Server), ?MYHOSTS) of true -> - lists:foldl( + Res = lists:foldl( fun(_M, {atomic, ok} = Res) -> Res; (M, _) -> M:try_register(User, Server, Password) - end, {error, not_allowed}, auth_modules(Server)); + end, {error, not_allowed}, auth_modules(Server)), + case Res of + {atomic, ok} -> + ejabberd_hooks:run(register_user, Server, + [User, Server]), + {atomic, ok}; + _ -> Res + end; false -> {error, not_allowed} end @@ -251,17 +259,37 @@ is_user_exists_in_other_modules(Module, User, Server) -> M:is_user_exists(User, Server) end, auth_modules(Server)--[Module]). +%% @spec (User, Server) -> ok | error | {error, not_allowed} +%% Remove user. +%% Note: it may return ok even if there was some problem removing the user. remove_user(User, Server) -> - lists:foreach( + R = lists:foreach( fun(M) -> M:remove_user(User, Server) - end, auth_modules(Server)). + end, auth_modules(Server)), + case R of + ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]); + _ -> none + end, + R. +%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error +%% Try to remove user if the provided password is correct. +%% The removal is attempted in each auth method provided: +%% when one returns 'ok' the loop stops; +%% if no method returns 'ok' then it returns the error message indicated by the last method attempted. remove_user(User, Server, Password) -> - lists:foreach( - fun(M) -> + R = lists:foldl( + fun(_M, ok = Res) -> + Res; + (M, _) -> M:remove_user(User, Server, Password) - end, auth_modules(Server)). + end, error, auth_modules(Server)), + case R of + ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]); + _ -> none + end, + R. %%%---------------------------------------------------------------------- diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl index 7d6a8e8f8..c7982c382 100644 --- a/src/ejabberd_auth_internal.erl +++ b/src/ejabberd_auth_internal.erl @@ -112,6 +112,7 @@ set_password(User, Server, Password) -> ok end. +%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason} try_register(User, Server, Password) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), @@ -237,6 +238,9 @@ is_user_exists(User, Server) -> false end. +%% @spec (User, Server) -> ok +%% Remove user. +%% Note: it returns ok even if there was some problem removing the user. remove_user(User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), @@ -245,8 +249,10 @@ remove_user(User, Server) -> mnesia:delete({passwd, US}) end, mnesia:transaction(F), - ejabberd_hooks:run(remove_user, LServer, [User, Server]). + ok. +%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request +%% Remove user if the provided password is correct. remove_user(User, Server, Password) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), @@ -264,7 +270,6 @@ remove_user(User, Server, Password) -> end, case mnesia:transaction(F) of {atomic, ok} -> - ejabberd_hooks:run(remove_user, LServer, [User, Server]), ok; {atomic, Res} -> Res; diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 961faec56..df21f62d0 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -153,6 +153,7 @@ check_password(User, Server, Password, _StreamID, _Digest) -> set_password(_User, _Server, _Password) -> {error, not_allowed}. +%% @spec (User, Server, Password) -> {error, not_allowed} try_register(_User, _Server, _Password) -> {error, not_allowed}. diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl index 6f73e6a9c..ee7603fef 100644 --- a/src/ejabberd_auth_odbc.erl +++ b/src/ejabberd_auth_odbc.erl @@ -114,6 +114,7 @@ set_password(User, Server, Password) -> end. +%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} try_register(User, Server, Password) -> case jlib:nodeprep(User) of error -> @@ -218,6 +219,9 @@ is_user_exists(User, Server) -> end end. +%% @spec (User, Server) -> ok | error +%% Remove user. +%% Note: it may return ok even if there was some problem removing the user. remove_user(User, Server) -> case jlib:nodeprep(User) of error -> @@ -226,10 +230,11 @@ remove_user(User, Server) -> Username = ejabberd_odbc:escape(LUser), LServer = jlib:nameprep(Server), catch odbc_queries:del_user(LServer, Username), - ejabberd_hooks:run(remove_user, jlib:nameprep(Server), - [User, Server]) + ok end. +%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed +%% Remove user if the provided password is correct. remove_user(User, Server, Password) -> case jlib:nodeprep(User) of error -> @@ -243,8 +248,6 @@ remove_user(User, Server, Password) -> LServer, Username, Pass), case Result of {selected, ["password"], [{Password}]} -> - ejabberd_hooks:run(remove_user, jlib:nameprep(Server), - [User, Server]), ok; {selected, ["password"], []} -> not_exists; diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl index f26296d33..5f67bedba 100644 --- a/src/ejabberd_auth_pam.erl +++ b/src/ejabberd_auth_pam.erl @@ -91,7 +91,7 @@ remove_user(_User, _Server) -> {error, not_allowed}. remove_user(_User, _Server, _Password) -> - {error, not_allowed}. + not_allowed. plain_password_required() -> true. diff --git a/src/mod_register.erl b/src/mod_register.erl index 7a4eca79f..09867fdc7 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -223,8 +223,6 @@ try_register(User, Server, Password, Source, Lang) -> true -> case ejabberd_auth:try_register(User, Server, Password) of {atomic, ok} -> - ejabberd_hooks:run(user_registered, Server, - [User, Server]), send_welcome_message(JID), send_registration_notifications(JID), ok; diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index cbce3102a..d62124d1e 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -37,7 +37,8 @@ process_item/2, in_subscription/6, out_subscription/4, - user_registered/2, + register_user/2, + remove_user/2, list_groups/1, create_group/2, create_group/3, @@ -46,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]). @@ -83,8 +85,10 @@ start(Host, _Opts) -> ?MODULE, get_jid_info, 70), ejabberd_hooks:add(roster_process_item, Host, ?MODULE, process_item, 50), - ejabberd_hooks:add(user_registered, Host, - ?MODULE, user_registered, 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), @@ -105,8 +109,10 @@ stop(Host) -> ?MODULE, get_jid_info, 70), ejabberd_hooks:delete(roster_process_item, Host, ?MODULE, process_item, 50), - ejabberd_hooks:delete(user_registered, Host, - ?MODULE, user_registered, 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), @@ -424,6 +430,7 @@ get_group_users(_User, Host, Group, GroupOpts) -> [] end ++ get_group_explicit_users(Host, Group). +%% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}] get_group_explicit_users(Host, Group) -> Read = (catch mnesia:dirty_index_read( sr_user, @@ -439,6 +446,7 @@ get_group_explicit_users(Host, Group) -> get_group_name(Host, Group) -> get_group_opt(Host, Group, name, Group). +%% Get list of names of groups that have @all@ in the memberlist get_special_users_groups(Host) -> lists:filter( fun(Group) -> @@ -446,6 +454,8 @@ get_special_users_groups(Host) -> 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( @@ -457,6 +467,9 @@ displayed_groups(GroupsOpts, 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}) -> @@ -464,6 +477,9 @@ get_special_displayed_groups(GroupsOpts) -> 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) -> @@ -474,6 +490,7 @@ get_user_displayed_groups(LUser, LServer, GroupsOpts) -> 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 = @@ -496,22 +513,60 @@ is_user_in_group({_U, S} = US, Group, Host) -> _ -> true end. + +%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok} add_user_to_group(Host, US, Group) -> + {LUser, LServer} = US, + %% 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). +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). + Result = mnesia:transaction(F), + {LUser, LServer} = US, + %% 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. -user_registered(User, Server) -> +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), @@ -523,17 +578,31 @@ user_registered(User, Server) -> GroupName = proplists:get_value(name, GroupOpts, Group), lists:foreach( fun({U, S}) -> - Item = #roster{usj = {U, S, {LUser, LServer, ""}}, - us = {U, S}, - jid = {LUser, LServer, ""}, - name = "", - subscription = both, - ask = none, - groups = [GroupName]}, - push_item(U, S, jlib:make_jid("", S, ""), Item) + 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) -> @@ -558,6 +627,16 @@ push_item(User, Server, From, Item) -> 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