summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog24
-rw-r--r--doc/guide.tex8
-rw-r--r--src/acl.erl4
-rw-r--r--src/ejabberd_auth.erl42
-rw-r--r--src/ejabberd_auth_internal.erl9
-rw-r--r--src/ejabberd_auth_ldap.erl1
-rw-r--r--src/ejabberd_auth_odbc.erl11
-rw-r--r--src/ejabberd_auth_pam.erl2
-rw-r--r--src/mod_register.erl2
-rw-r--r--src/mod_shared_roster.erl109
10 files changed, 181 insertions, 31 deletions
diff --git a/ChangeLog b/ChangeLog
index f92551ad..14ecfa56 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,29 @@
2008-12-23 Badlop <badlop@process-one.net>
+ * 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 0ca65220..9501c0cc 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, <groupname>\}} Matches any member of a Shared Roster Group with name \term{<groupname>} in the virtual host. Example:
+\begin{verbatim}
+{acl, techgroupmembers, {shared_group, "techteam"}}.
+\end{verbatim}
+\titem{\{shared\_group, <groupname>, <server>\}} Matches any member of a Shared Roster Group with name \term{<groupname>} in the virtual host \term{<server>}. Example:
+\begin{verbatim}
+{acl, techgroupmembers, {shared_group, "techteam", "example.org"}}.
+\end{verbatim}
\titem{\{user\_regexp, <regexp>\}} Matches any local user with a name that
matches \term{<regexp>} on local virtual hosts. Example:
\begin{verbatim}
diff --git a/src/acl.erl b/src/acl.erl
index c135c254..4088c856 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 db37e26a..c69dda60 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 7d6a8e8f..c7982c38 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 961faec5..df21f62d 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 6f73e6a9..ee7603fe 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 f26296d3..5f67bedb 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 7a4eca79..09867fdc 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 cbce3102..d62124d1 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.
+
+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).
-user_registered(User, Server) ->
+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