aboutsummaryrefslogtreecommitdiff
path: root/src/mod_shared_roster_ldap.erl
diff options
context:
space:
mode:
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-02-26 17:27:12 +0300
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-02-26 17:27:12 +0300
commit6b126171da22dbf8b49fd10ac4248be588a4d0f2 (patch)
tree40d1994e034459baf3ffc6de3f5d13e7f7dfab62 /src/mod_shared_roster_ldap.erl
parentImplementation of pubsub#itemreply (EJAB-1347)(#928) (diff)
Improve LDAP shared roster support (EJAB-1480)
Diffstat (limited to 'src/mod_shared_roster_ldap.erl')
-rw-r--r--src/mod_shared_roster_ldap.erl1043
1 files changed, 667 insertions, 376 deletions
diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl
index a4ac65c10..3275394be 100644
--- a/src/mod_shared_roster_ldap.erl
+++ b/src/mod_shared_roster_ldap.erl
@@ -3,6 +3,7 @@
%%% Author : Realloc <realloc@realloc.spb.ru>
%%% Marcin Owsiany <marcin@owsiany.pl>
%%% Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% Contributor : Mike Kaganski <mikekaganski@hotmail.com>
%%% Description : LDAP shared roster management
%%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@process-one.net>
%%%
@@ -26,8 +27,6 @@
%%%-------------------------------------------------------------------
-module(mod_shared_roster_ldap).
--behaviour(ejabberd_config).
-
-behaviour(gen_server).
-behaviour(gen_mod).
@@ -44,19 +43,15 @@
out_subscription/4, mod_opt_type/1, opt_type/1]).
-include("ejabberd.hrl").
--include("logger.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
-
-include("eldap.hrl").
+-define(ERROR_MSG(Fmt, Args), error_logger:error_msg(Fmt, Args)).
--define(CACHE_SIZE, 1000).
-
--define(USER_CACHE_VALIDITY, 300).
-
--define(GROUP_CACHE_VALIDITY, 300).
-
--define(LDAP_SEARCH_TIMEOUT, 5).
+-define(CACHE_SIZE, 1).
+-define(CACHE_VALIDITY, 300). %% in seconds
+-define(LDAP_SEARCH_TIMEOUT, 5). %% Timeout for LDAP search queries in seconds
+-define(INVALID_SETTING_MSG, "~s is not properly set! ~s will not function.").
-record(state,
{host = <<"">> :: binary(),
@@ -74,7 +69,6 @@
group_attr = <<"">> :: binary(),
group_desc = <<"">> :: binary(),
user_desc = <<"">> :: binary(),
- user_uid = <<"">> :: binary(),
uid_format = <<"">> :: binary(),
uid_format_re = <<"">> :: binary(),
filter = <<"">> :: binary(),
@@ -82,25 +76,51 @@
rfilter = <<"">> :: binary(),
gfilter = <<"">> :: binary(),
auth_check = true :: boolean(),
- user_cache_size = ?CACHE_SIZE :: non_neg_integer(),
- group_cache_size = ?CACHE_SIZE :: non_neg_integer(),
- user_cache_validity = ?USER_CACHE_VALIDITY :: non_neg_integer(),
- group_cache_validity = ?GROUP_CACHE_VALIDITY :: non_neg_integer()}).
-
+ %% Group data parameters
+ group_base = <<"">> :: binary(),
+ %% - Subgroup of roster filter
+ %% This filter defines which groups are displayed in the shared roster
+ %% Valid values are 'all' or "LDAP filter string" or "LDAP filter string containing %g"
+ shgfilter = <<"">> :: binary(),
+ shg_attr = <<"">> :: binary(),
+ %% - Subgroup of group filter
+ group_is_dn = true :: boolean(),
+ member_attr = <<"">> :: binary(),
+ %% User data parameters
+ member_selection_mode = memberattr_dn :: memberattr_normal | memberattr_dn |
+ group_children,
+ %% Algorithm control parameters
+ subscribe_all = false :: binary(),
+ roster_cache_size = ?CACHE_SIZE :: non_neg_integer(),
+ roster_cache_validity = ?CACHE_VALIDITY :: non_neg_integer()}).
+
+%% If #state.member_selection_mode is memberattr_normal or memberattr_dn,
+%% then members is list of member_attr values;
+%% if #state.member_selection_mode is group_children,
+%% then members is dn of the group (to make it possible to search for its subtree)
-record(group_info, {desc, members}).
+-record(user_info, {us, name}).
+
+-record(shared_roster_item, {us, name, groups}).
+
+% Groups visible to this group
+% grp may be atom 'all' or a group name string.
+% shgrps is a list containing one or more grp
+-record(shg_data, {grp, shgrps}).
+
%%====================================================================
%% API
%%====================================================================
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE,
- [Host, Opts], []).
+ [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
- permanent, 1000, worker, [?MODULE]},
+ permanent, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -112,109 +132,92 @@ stop(Host) ->
%% Hooks
%%--------------------------------------------------------------------
get_user_roster(Items, {U, S} = US) ->
- SRUsers = get_user_to_groups_map(US, true),
- {NewItems1, SRUsersRest} = lists:mapfoldl(fun (Item,
- SRUsers1) ->
- {_, _, {U1, S1, _}} =
- Item#roster.usj,
- US1 = {U1, S1},
- case dict:find(US1,
- SRUsers1)
- of
- {ok, _GroupNames} ->
- {Item#roster{subscription
- =
- both,
- ask =
- none},
- dict:erase(US1,
- SRUsers1)};
- error ->
- {Item, SRUsers1}
- end
- end,
- SRUsers, Items),
+ {ok, State} = eldap_utils:get_state(S, ?MODULE),
+ SRUsers = get_shared_roster(State, US),
+ %%?ERROR_MSG("XXXXXX get_user_roster: SRUsers=~p", [SRUsers]),
+ %% If partially subscribed users are also in shared roster,
+ %% show them as totally subscribed:
+ {NewItems1, SRUsersRest} = lists:mapfoldl(
+ fun(Item, SRUsers1) ->
+ {_, _, {U1, S1, _}} = Item#roster.usj,
+ US1 = {U1, S1},
+ case lists:keytake(US1, #shared_roster_item.us, SRUsers1) of
+ %%case dict:find(US1, SRUsers1) of
+ {value, _, SRUsers2} -> {Item#roster{subscription = both, ask = none}, SRUsers2};
+ %%{ok, _GroupNames} -> {Item#roster{subscription = both, ask = none}, dict:erase(US1, SRUsers1)};
+ error -> {Item, SRUsers1}
+ end
+ end,
+ SRUsers, Items),
+ %% Export items in roster format:
SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}},
- us = US, jid = {U1, S1, <<"">>},
- name = get_user_name(U1, S1), subscription = both,
- ask = none, groups = GroupNames}
- || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
+ us = US,
+ jid = {U1, S1, <<"">>},
+ name = Name,
+ subscription = both,
+ ask = none,
+ groups = Groups} ||
+ #shared_roster_item{us = {U1, S1}, name = Name, groups = Groups} <- SRUsersRest],
+ %% SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}},
+ %% us = US, jid = {U1, S1, <<"">>},
+ %% name = get_user_name(U1, S1), subscription = both,
+ %% ask = none, groups = GroupNames}
+ %% || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
SRItems ++ NewItems1.
%% This function in use to rewrite 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},
- Map = get_user_to_groups_map(USFrom, false),
- case dict:find(USTo, Map) of
- error -> RosterItem;
- {ok, []} -> RosterItem;
- {ok, GroupNames}
- when RosterItem#roster.subscription == remove ->
- RosterItem#roster{subscription = both, ask = none,
- groups = GroupNames};
- _ -> RosterItem#roster{subscription = both, ask = none}
+ {ok, State} = eldap_utils:get_state(_Host, ?MODULE),
+ {User,Server,_Resource} = RosterItem#roster.jid,
+ USTo = {User,Server},
+ SR = get_shared_roster(State, RosterItem#roster.us),
+ case lists:keysearch(USTo, #shared_roster_item.us, SR) of
+ false ->
+ RosterItem;
+ {value, #shared_roster_item{groups = Groups}} when RosterItem#roster.subscription == remove ->
+ %% Roster item cannot be removed:
+ %% We simply reset the original groups:
+ RosterItem#roster{subscription = both, ask = none,
+ groups=Groups};
+ _ ->
+ RosterItem#roster{subscription = both, ask = none}
end.
get_subscription_lists({F, T}, User, Server) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
- US = {LUser, LServer},
- DisplayedGroups = get_user_displayed_groups(US),
- SRUsers = lists:usort(lists:flatmap(fun (Group) ->
- get_group_users(LServer, Group)
- end,
- DisplayedGroups)),
- SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers],
+ U = jlib:nodeprep(User),
+ S = jlib:nameprep(Server),
+ {ok, State} = eldap_utils:get_state(S, ?MODULE),
+ SRJIDs = get_presense_subscribers(State, {U, S}),
+%?INFO_MSG("SRJIDs: ~p", [SRJIDs]),
{lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
-get_jid_info({Subscription, Groups}, User, Server,
- JID) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
- US = {LUser, LServer},
- {U1, S1, _} = jid:tolower(JID),
+get_jid_info({Subscription, Groups}, User, Server, JID) ->
+ {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+ {U1, S1, _} = jlib:jid_tolower(JID),
US1 = {U1, S1},
- SRUsers = get_user_to_groups_map(US, false),
- case dict:find(US1, SRUsers) of
- {ok, GroupNames} ->
- NewGroups = if Groups == [] -> GroupNames;
- true -> Groups
- end,
- {both, NewGroups};
- error -> {Subscription, Groups}
+ SR = get_shared_roster(State, {User, Server}),
+ case lists:keysearch(US1, #shared_roster_item.us, SR) of
+ false -> {Subscription, Groups};
+ {value, #shared_roster_item{groups = GroupNames}} when Groups == [] -> {both, GroupNames};
+ _ -> {both, Groups}
end.
-in_subscription(Acc, User, Server, JID, Type,
- _Reason) ->
+in_subscription(Acc, User, Server, JID, Type, _Reason) ->
process_subscription(in, User, Server, JID, Type, Acc).
out_subscription(User, Server, JID, Type) ->
- process_subscription(out, User, Server, JID, Type,
- false).
-
-process_subscription(Direction, User, Server, JID,
- _Type, Acc) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
- US = {LUser, LServer},
- {U1, S1, _} =
- jid:tolower(jid:remove_resource(JID)),
+ process_subscription(out, User, Server, JID, Type, false).
+
+process_subscription(Direction, User, Server, JID, _Type, Acc) ->
+ {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+ {U1, S1, _} = jlib:jid_tolower(JID),
US1 = {U1, S1},
- DisplayedGroups = get_user_displayed_groups(US),
- SRUsers = lists:usort(lists:flatmap(fun (Group) ->
- get_group_users(LServer, Group)
- end,
- DisplayedGroups)),
- case lists:member(US1, SRUsers) of
- true ->
- case Direction of
- in -> {stop, false};
- out -> stop
- end;
- false -> Acc
+ SR = get_shared_roster(State, {User, Server}),
+ case lists:keysearch(US1, #shared_roster_item.us, SR) of
+ false -> Acc;
+ _ when Direction == in -> {stop, false};
+ _ -> stop
end.
%%====================================================================
@@ -222,267 +225,509 @@ process_subscription(Direction, User, Server, JID,
%%====================================================================
init([Host, Opts]) ->
State = parse_options(Host, Opts),
- cache_tab:new(shared_roster_ldap_user,
- [{max_size, State#state.user_cache_size}, {lru, false},
- {life_time, State#state.user_cache_validity}]),
- cache_tab:new(shared_roster_ldap_group,
- [{max_size, State#state.group_cache_size}, {lru, false},
- {life_time, State#state.group_cache_validity}]),
- ejabberd_hooks:add(roster_get, Host, ?MODULE,
- get_user_roster, 70),
+ if
+ State#state.roster_cache_size > 0 ->
+ cache_tab:new(shared_roster_ldap_sr,
+ [{max_size, State#state.roster_cache_size},
+ {lru, false}, % We don't need LRU algorithm
+ {life_time, State#state.roster_cache_validity}]);
+ true ->
+ false
+ end,
+ ejabberd_hooks:add(roster_get, Host,
+ ?MODULE, get_user_roster, 70),
ejabberd_hooks:add(roster_in_subscription, Host,
- ?MODULE, in_subscription, 30),
+ ?MODULE, in_subscription, 30),
ejabberd_hooks:add(roster_out_subscription, Host,
- ?MODULE, out_subscription, 30),
+ ?MODULE, out_subscription, 30),
ejabberd_hooks:add(roster_get_subscription_lists, Host,
- ?MODULE, get_subscription_lists, 70),
- 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, get_subscription_lists, 70),
+ ejabberd_hooks:add(roster_get_jid_info, Host,
+ ?MODULE, get_jid_info, 70),
+ ejabberd_hooks:add(roster_process_item, Host,
+ ?MODULE, process_item, 50),
eldap_pool:start_link(State#state.eldap_id,
- State#state.servers, State#state.backups,
- State#state.port, State#state.dn,
- State#state.password, State#state.tls_options),
+ State#state.servers,
+ State#state.backups,
+ State#state.port,
+ State#state.dn,
+ State#state.password,
+ State#state.tls_options),
{ok, State}.
handle_call(get_state, _From, State) ->
{reply, {ok, State}, State};
+
handle_call(_Request, _From, State) ->
{reply, {error, badarg}, State}.
-handle_cast(_Msg, State) -> {noreply, State}.
+handle_cast(_Msg, State) ->
+ {noreply, State}.
-handle_info(_Info, State) -> {noreply, State}.
+handle_info(_Info, State) ->
+ {noreply, State}.
terminate(_Reason, State) ->
Host = State#state.host,
- ejabberd_hooks:delete(roster_get, Host, ?MODULE,
- get_user_roster, 70),
+ ejabberd_hooks:delete(roster_get, Host,
+ ?MODULE, get_user_roster, 70),
ejabberd_hooks:delete(roster_in_subscription, Host,
- ?MODULE, in_subscription, 30),
+ ?MODULE, in_subscription, 30),
ejabberd_hooks:delete(roster_out_subscription, Host,
- ?MODULE, out_subscription, 30),
- ejabberd_hooks:delete(roster_get_subscription_lists,
- Host, ?MODULE, get_subscription_lists, 70),
+ ?MODULE, out_subscription, 30),
+ ejabberd_hooks:delete(roster_get_subscription_lists, Host,
+ ?MODULE, get_subscription_lists, 70),
ejabberd_hooks:delete(roster_get_jid_info, Host,
- ?MODULE, get_jid_info, 70),
+ ?MODULE, get_jid_info, 70),
ejabberd_hooks:delete(roster_process_item, Host,
- ?MODULE, process_item, 50).
+ ?MODULE, process_item, 50).
-code_change(_OldVsn, State, _Extra) -> {ok, State}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-get_user_to_groups_map({_, Server} = US, SkipUS) ->
- DisplayedGroups = get_user_displayed_groups(US),
- lists:foldl(fun (Group, Dict1) ->
- GroupName = get_group_name(Server, Group),
- lists:foldl(fun (Contact, Dict) ->
- if SkipUS, Contact == US -> Dict;
- true ->
- dict:append(Contact,
- GroupName, Dict)
- end
- end,
- Dict1, get_group_users(Server, Group))
- end,
- dict:new(), DisplayedGroups).
-
-eldap_search(State, FilterParseArgs, AttributesList) ->
- case apply(eldap_filter, parse, FilterParseArgs) of
- {ok, EldapFilter} ->
- case eldap_pool:search(State#state.eldap_id,
- [{base, State#state.base},
- {filter, EldapFilter},
- {timeout, ?LDAP_SEARCH_TIMEOUT},
- {deref_aliases, State#state.deref_aliases},
- {attributes, AttributesList}])
- of
- #eldap_search_result{entries = Es} ->
- %% A result with entries. Return their list.
- Es;
- _ ->
- %% Something else. Pretend we got no results.
- []
- end;
- _ ->
- %% Filter parsing failed. Pretend we got no results.
- []
+do_eldap_search(PoolName, Opts) ->
+ case eldap_pool:search(PoolName, Opts) of
+ #eldap_search_result{entries = Es} ->
+ %% A result with entries. Return their list.
+ Es;
+ Err ->
+ %% Something else. Pretend we got no results.
+ ?ERROR_MSG("Error searching: ~p ~p", [Err, Opts]),
+ []
end.
-get_user_displayed_groups({User, Host}) ->
- {ok, State} = eldap_utils:get_state(Host, ?MODULE),
- GroupAttr = State#state.group_attr,
- Entries = eldap_search(State,
- [eldap_filter:do_sub(State#state.rfilter,
- [{<<"%u">>, User}])],
- [GroupAttr]),
- Reply = lists:flatmap(fun (#eldap_entry{attributes =
- Attrs}) ->
- case Attrs of
- [{GroupAttr, ValuesList}] -> ValuesList;
- _ -> []
- end
- end,
- Entries),
- lists:usort(Reply).
-
-get_group_users(Host, Group) ->
- {ok, State} = eldap_utils:get_state(Host, ?MODULE),
- case cache_tab:dirty_lookup(shared_roster_ldap_group,
- {Group, Host},
- fun () -> search_group_info(State, Group) end)
- of
- {ok, #group_info{members = Members}}
- when Members /= undefined ->
- Members;
- _ -> []
+%% Pass given Filter or FilterTemplate and SubstList to eldap_filter:parse,
+%% and if successful, run LDAP search on the whole subtree of Base, using
+%% resulting filter, retrieving given AttributesList. Return the result entries.
+%% On any error, print an error message and return an empty list of results.
+eldap_search(State, Base, EldapFilter, AttributesList) when is_tuple(EldapFilter) ->
+ do_eldap_search(State#state.eldap_id,
+ [{base, Base},
+%% {scope, wholeSubtree} %% This is the default
+ {filter, EldapFilter},
+ {timeout, ?LDAP_SEARCH_TIMEOUT},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, AttributesList}]);
+%% Filter is string
+eldap_search(State, Base, Filter, AttributesList) ->
+ eldap_search(State, Base, Filter, [], AttributesList).
+
+eldap_search(State, Base, FilterTemplate, SubstList, AttributesList) ->
+ case apply(eldap_filter, parse, [eldap_filter:do_sub(FilterTemplate, SubstList)]) of
+ {ok, EldapFilter} ->
+ %% Filter parsing succeeded
+ eldap_search(State, Base, EldapFilter, AttributesList);
+ Err ->
+ %% Filter parsing failed. Pretend we got no results.
+ ?ERROR_MSG("Error parsing filter: ~p", [Err]),
+ []
end.
-get_group_name(Host, Group) ->
- {ok, State} = eldap_utils:get_state(Host, ?MODULE),
- case cache_tab:dirty_lookup(shared_roster_ldap_group,
- {Group, Host},
- fun () -> search_group_info(State, Group) end)
- of
- {ok, #group_info{desc = GroupName}}
- when GroupName /= undefined ->
- GroupName;
- _ -> Group
+%% The same as above, but gets the Attributes for the specified DN.
+%% Note that this function doesn't honor the State's base DN;
+%% TODO: fix this (create a custom check?)
+eldap_search_dn(State, DN, EldapFilter, AttributesList) when is_tuple(EldapFilter) ->
+ do_eldap_search(State#state.eldap_id,
+ [{scope, baseObject},
+ {base, DN},
+ {filter, EldapFilter},
+ {timeout, ?LDAP_SEARCH_TIMEOUT},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, AttributesList}]);
+%% Filter is string.
+eldap_search_dn(State, DN, Filter, AttributesList) ->
+ case eldap_filter:parse(Filter) of
+ {ok, EldapFilter} ->
+ %% Filter parsing succeeded
+ eldap_search_dn(State, DN, EldapFilter, AttributesList);
+ Err ->
+ %% Filter parsing failed. Pretend we got no results.
+ ?ERROR_MSG("Error parsing filter: ~p", [Err]),
+ []
end.
-get_user_name(User, Host) ->
- {ok, State} = eldap_utils:get_state(Host, ?MODULE),
- case cache_tab:dirty_lookup(shared_roster_ldap_user,
- {User, Host},
- fun () -> search_user_name(State, User) end)
- of
- {ok, UserName} -> UserName;
- error -> User
+intersection(L1,L2) -> lists:filter(fun(X) -> lists:member(X,L1) end, L2).
+
+filter_roster(Roster, all) -> Roster;
+filter_roster(_, []) -> [];
+filter_roster(Roster, IncludeGroups) when is_list(IncludeGroups) ->
+ lists:foldl(
+ fun(RosterItem, Acc) ->
+ case intersection(IncludeGroups, RosterItem#shared_roster_item.groups) of
+ [] -> Acc;
+ CommonGroups -> [RosterItem#shared_roster_item{groups=CommonGroups} | Acc]
+ end
+ end,
+ [], Roster).
+
+get_user_visible_groups(UserGroups, VisibilityMap) ->
+ lists:foldl(
+ fun(Group, Acc) ->
+ case (lists:keysearch(Group, #shg_data.grp, VisibilityMap)) of
+ {value, #shg_data{shgrps=Gs}} when is_list(Gs) -> Gs ++ Acc;
+ _ -> Acc
+ end
+ end,
+ UserGroups, UserGroups).
+
+%% Returns [#shared_roster_item];
+%% Removes the US from returned data
+%% If State#state.user_groups_only is 'true', then it removes all users that are not in US's groups,
+%% and also removes the groups from the users that the US is not member of.
+get_shared_roster(State, {_, Server} = US) ->
+ case (catch get_full_roster(State, Server)) of
+ {ok, {VisibilityMap, FullRoster}} ->
+ %%?ERROR_MSG("XXXXXX get_shared_roster: VMap=~p FullRoster=~p", [VisibilityMap, FullRoster]),
+ CommonRosterGroups = lists:foldl(
+ fun(_, all) -> all;
+ (#shg_data{grp=all, shgrps=all}, _) -> all;
+ (#shg_data{grp=all, shgrps=Gs}, Acc) when is_list(Gs) -> Gs ++ Acc;
+ (_, Acc) -> Acc
+ end,
+ [], VisibilityMap),
+ case lists:keytake(US, #shared_roster_item.us, FullRoster) of
+ false -> filter_roster(FullRoster, CommonRosterGroups);
+ {value, #shared_roster_item{groups=UserGroups}, Roster2} ->
+ VisibleGroups = case (CommonRosterGroups) of
+ all -> all;
+ CRG -> get_user_visible_groups(UserGroups, VisibilityMap) ++ CRG
+ end,
+ filter_roster(Roster2, VisibleGroups)
+ end;
+ {'EXIT', CatchData} -> ?ERROR_MSG("Error getting shared roster for user ~p: ~p", [US, CatchData]), [];
+ _Unexpected -> []
end.
+%% 1. If user is not a member of shared roster -> no additional subscriptions
+%% 2. Else if ldap_subscribe_all is set AND this user is member of a group published to all ->
+%% add all registered users of this vhost
+%% 3. Else add only those groups this user' groups are published to
+get_presense_subscribers(State, {_, Server} = US) ->
+ case (catch get_full_roster(State, Server)) of
+ {ok, {VisibilityMap, FullRoster}} ->
+ case lists:keytake(US, #shared_roster_item.us, FullRoster) of
+ false -> []; % Case #1
+ {value, #shared_roster_item{groups=UserGroups}, Roster2} ->
+ AllGroups = lists:usort(lists:foldl(
+ fun(#shared_roster_item{groups=Gs}, Acc) -> Gs ++ Acc end,
+ [], FullRoster)),
+ Fun = case (State#state.subscribe_all) of
+ true -> % Possible case 2
+ fun(_, all) -> all;
+ (#shg_data{grp=all, shgrps=all}, _) -> all;
+ (#shg_data{grp=all, shgrps=Gs}, Acc) when is_list(Gs) ->
+ case intersection(Gs, UserGroups) of
+ [] -> Acc;
+ _SomeCommon -> all
+ end;
+ (#shg_data{grp=G, shgrps=Gs}, Acc) when is_list(Gs) ->
+ case intersection(Gs, UserGroups) of
+ [] -> Acc;
+ _SomeCommon -> [G | Acc]
+ end;
+ (_, Acc) -> Acc
+ end;
+ _False -> % Case 3
+ fun(#shg_data{grp=all}, Acc) -> AllGroups ++ Acc;
+ (#shg_data{grp=G, shgrps=Gs}, Acc) when is_list(Gs) ->
+ case intersection(Gs, UserGroups) of
+ [] -> Acc;
+ _SomeCommon -> [G | Acc]
+ end;
+ (_, Acc) -> Acc
+ end
+ end,
+ PublishTo = lists:foldl(Fun, [], VisibilityMap),
+ case (PublishTo) of
+ all ->
+ [{U1, S1, <<"">>} || {U1, S1} <- ejabberd_auth:get_vh_registered_users(Server)];
+ Groups ->
+ [{U1, S1, <<"">>} || #shared_roster_item{us = {U1, S1}} <- filter_roster(Roster2, UserGroups ++ Groups)]
+ end
+ end;
+ {'EXIT', CatchData} -> ?ERROR_MSG("Error getting shared roster for user ~p: ~p", [US, CatchData]), [];
+ _Unexpected -> []
+ end.
+
+get_full_roster(State, Server) when State#state.roster_cache_size > 0 ->
+ cache_tab:dirty_lookup(shared_roster_ldap_sr,
+ {Server},
+ fun() -> search_roster_info(State, Server) end);
+get_full_roster(State, Server) ->
+ search_roster_info(State, Server).
+
+search_visible_groups(State, _) when State#state.shgfilter == all ->
+ [{all, all}];
+search_visible_groups(State, _) when State#state.shgfilter == none ->
+ [{all, none}];
+search_visible_groups(State, Groups) ->
+ case (string:str(State#state.shgfilter, "%g")) of
+ 0 -> [{all, search_group_visible_groups(State, "")}];
+ _ -> lists:map(
+ fun(Group) -> {Group, search_group_visible_groups(State, Group)} end,
+ Groups)
+ end.
+
+search_group_visible_groups(State, Group) ->
+ Entries = eldap_search(State, State#state.group_base, State#state.shgfilter, [{<<"%g">>, Group}], [State#state.shg_attr]),
+ lists:usort(lists:flatmap(
+ fun(#eldap_entry{attributes = Attrs}) ->
+ case Attrs of
+ [{_GroupAttr, ValuesList}] ->
+ ValuesList;
+ _ ->
+ []
+ end
+ end, Entries)).
+
+group2name(all, _) -> all;
+group2name(none, _) -> none;
+group2name(Group, GroupNames) ->
+ case (lists:keysearch(Group, 1, GroupNames)) of
+ {value, {_, Name}} -> Name;
+ _ -> false
+ end.
+
+groups2names(all, _) -> all;
+groups2names(none, _) -> none;
+groups2names(GroupList, GroupNames) ->
+ lists:foldl(
+ fun(G, Acc) ->
+ case (group2name(G, GroupNames)) of
+ false -> Acc;
+ Name -> [Name | Acc]
+ end
+ end,
+ [], GroupList).
+
+prep_vis_map(VisGroups, GroupNames) ->
+ lists:foldl(
+ fun({G, Gs}, Acc) ->
+ case (group2name(G, GroupNames)) of
+ false -> Acc;
+ Name -> [#shg_data{grp=Name, shgrps=groups2names(Gs, GroupNames)} | Acc]
+ end
+ end,
+ [], VisGroups).
+
+search_roster_info(State, _Host) ->
+ Entries = eldap_search(State, State#state.group_base, State#state.rfilter, [State#state.group_attr]),
+ AllGroupIds = lists:usort(lists:flatmap(
+ fun(#eldap_entry{attributes = Attrs}) ->
+ case Attrs of
+ [{_GroupAttr, ValuesList}] ->
+ ValuesList;
+ _ ->
+ []
+ end
+ end, Entries)),
+ VisGroups = search_visible_groups(State, AllGroupIds),
+ %%?ERROR_MSG("XXXXXX search_roster_info: VisGroups=~p", [VisGroups]),
+
+ {GroupNames, RosterItems} = case State#state.member_selection_mode of
+ group_children ->
+ {GroupNames0, UsersDict0} = lists:foldl(
+ fun(Group, {GrNAcc, Dict1} = Acc) ->
+ case search_group_info(State, Group) of
+ {ok, #group_info{desc = GroupName, members = GroupDN}} ->
+ {[{Group, GroupName} | GrNAcc], search_users_info(State, GroupDN, GroupName, Dict1)};
+ _ -> Acc %% Error getting group data -> No users!
+ end
+ end,
+ {[], dict:new()}, AllGroupIds),
+
+ {GroupNames0, dict:fold(
+ fun(#user_info{us=US, name=UserName}, Groups, AccIn) ->
+ [#shared_roster_item{us = US, name = UserName, groups = Groups} | AccIn]
+ end,
+ [], UsersDict0)};
+ _ ->
+ {GroupNames1, UsersDict1} = lists:foldl(
+ fun(Group, {GrNAcc, Dict1} = Acc) ->
+ case search_group_info(State, Group) of
+ {ok, #group_info{desc = GroupName, members = Members}} ->
+ {[{Group, GroupName} | GrNAcc], lists:foldl(
+ fun(Member, Dict) -> dict:append(Member, GroupName, Dict) end,
+ Dict1, Members)};
+ _ -> Acc %% Error getting group data -> No users!
+ end
+ end,
+ {[], dict:new()}, AllGroupIds),
+
+ %%?ERROR_MSG("UsersDict1: ~p", [UsersDict1]),
+ %%?ERROR_MSG("GroupNames1: ~p", [GroupNames1]),
+
+ {GroupNames1, dict:fold(
+ fun(Member, Groups, AccIn) ->
+ case search_user_info(State, Member) of
+ {ok, #user_info{us=US, name=UserName}} ->
+ %%?ERROR_MSG("XXXX found user: ~p ~p ~p", [UserName, Groups, US]),
+ [#shared_roster_item{us = US, name = UserName, groups = Groups} | AccIn];
+ _ -> AccIn
+ end
+ end,
+ [], UsersDict1)}
+ end,
+
+ VisibilityMap = prep_vis_map(VisGroups, GroupNames),
+ {ok, {VisibilityMap, RosterItems}}.
+
search_group_info(State, Group) ->
+ AttList = case State#state.member_selection_mode of
+ group_children -> [State#state.group_desc];
+ _ -> [State#state.group_desc, State#state.member_attr]
+ end,
+ SearchResult = case State#state.group_is_dn of
+ true -> eldap_search_dn(State,
+ Group,
+ State#state.gfilter,
+ AttList);
+ _ -> eldap_search(State,
+ State#state.group_base,
+ State#state.gfilter,
+ [{<<"%g">>, Group}],
+ AttList)
+ end,
+ case SearchResult of
+ [] ->
+ error;
+ LDAPEntries ->
+ case State#state.member_selection_mode of
+ group_children ->
+ [#eldap_entry{object_name=Name, attributes=Attrs} | _] = LDAPEntries,
+ {ok, #group_info{desc = eldap_utils:get_ldap_attr(State#state.group_desc, Attrs),
+ members = Name}};
+ _ ->
+ {GroupDesc, MembersLists} = lists:foldl(
+ fun(#eldap_entry{attributes=Attrs}, {DescAcc, MembersAcc}) ->
+ case {eldap_utils:get_ldap_attr(State#state.group_desc, Attrs),
+ lists:keysearch(State#state.member_attr, 1, Attrs)} of
+ {Desc, {value, {GroupMemberAttr, Members}}}
+ when GroupMemberAttr == State#state.member_attr ->
+ {Desc, lists:usort(Members ++ MembersAcc)};
+ _ ->
+ {DescAcc, MembersAcc}
+ end
+ end,
+ {Group, []}, LDAPEntries),
+ {ok, #group_info{desc = GroupDesc,
+ members = lists:usort(MembersLists)}}
+ end
+ end.
+
+%% Takes the attributes from LDAP user search;
+%% returns error or {ok, #user_info}
+construct_user(State, Attrs) ->
Extractor = case State#state.uid_format_re of
- <<"">> ->
- fun (UID) ->
- catch eldap_utils:get_user_part(UID,
- State#state.uid_format)
- end;
- _ ->
- fun (UID) ->
- catch get_user_part_re(UID,
- State#state.uid_format_re)
- end
- end,
+ <<"">> -> fun(UID) ->
+ catch eldap_utils:get_user_part(UID, State#state.uid_format)
+ end;
+ _ -> fun(UID) ->
+ catch get_user_part_re(UID, State#state.uid_format_re)
+ end
+ end,
AuthChecker = case State#state.auth_check of
- true -> fun ejabberd_auth:is_user_exists/2;
- _ -> fun (_U, _S) -> true end
- end,
+ true -> fun ejabberd_auth:is_user_exists/2;
+ _ -> fun(_U, _S) -> true end
+ end,
Host = State#state.host,
- case eldap_search(State,
- [eldap_filter:do_sub(State#state.gfilter,
- [{<<"%g">>, Group}])],
- [State#state.group_attr, State#state.group_desc,
- State#state.uid])
- of
- [] -> error;
- LDAPEntries ->
- {GroupDesc, MembersLists} = lists:foldl(fun
- (#eldap_entry{attributes =
- Attrs},
- {DescAcc, JIDsAcc}) ->
- case
- {eldap_utils:get_ldap_attr(State#state.group_attr,
- Attrs),
- eldap_utils:get_ldap_attr(State#state.group_desc,
- Attrs),
- lists:keysearch(State#state.uid,
- 1,
- Attrs)}
- of
- {ID, Desc,
- {value,
- {GroupMemberAttr,
- Members}}}
- when ID /= <<"">>,
- GroupMemberAttr
- ==
- State#state.uid ->
- JIDs =
- lists:foldl(fun
- ({ok,
- UID},
- L) ->
- PUID =
- jid:nodeprep(UID),
- case
- PUID
- of
- error ->
- L;
- _ ->
- case
- AuthChecker(PUID,
- Host)
- of
- true ->
- [{PUID,
- Host}
- | L];
- _ ->
- L
- end
- end;
- (_,
- L) ->
- L
- end,
- [],
- lists:map(Extractor,
- Members)),
- {Desc,
- [JIDs
- | JIDsAcc]};
- _ ->
- {DescAcc, JIDsAcc}
- end
- end,
- {Group, []}, LDAPEntries),
- {ok,
- #group_info{desc = GroupDesc,
- members = lists:usort(lists:flatten(MembersLists))}}
+
+ case {eldap_utils:get_ldap_attr(State#state.uid, Attrs),
+ eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)} of
+ {UID, Desc} when UID /= "" ->
+ %% By returning "" get_ldap_attr means "not found"
+ case Extractor(UID) of
+ {ok, UID1} ->
+ UID2 = jlib:nodeprep(UID1),
+ case UID2 of
+ error -> error;
+ _ ->
+ case AuthChecker(UID2, Host) of
+ true -> {ok, #user_info{us={UID2, Host}, name=Desc}};
+ _ -> error
+ end
+ end;
+ _ -> error
+ end;
+ _ ->
+ error
end.
-search_user_name(State, User) ->
- case eldap_search(State,
- [eldap_filter:do_sub(State#state.ufilter,
- [{<<"%u">>, User}])],
- [State#state.user_desc, State#state.user_uid])
- of
- [#eldap_entry{attributes = Attrs} | _] ->
- case {eldap_utils:get_ldap_attr(State#state.user_uid,
- Attrs),
- eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)}
- of
- {UID, Desc} when UID /= <<"">> -> {ok, Desc};
- _ -> error
- end;
- [] -> error
+%% This function is used when State#state.member_selection_mode is group_children
+%% Returns UsersDict to which the users (#user_info) of this group are added
+%%search_users_info(State, GroupInfo) ->
+search_users_info(State, GroupDN, GroupName, UsersDict) ->
+ SearchResult = eldap_search(State,
+ GroupDN,
+ State#state.ufilter,
+ [State#state.user_desc, State#state.uid]),
+ lists:foldl(
+ fun(#eldap_entry{attributes=Attrs}, Dict1) ->
+ case construct_user(State, Attrs) of
+ {ok, UserInfo} ->
+ dict:append(UserInfo, GroupName, Dict1);
+ _ -> Dict1
+ end
+ end, UsersDict, SearchResult).
+
+%% This function is used when State#state.member_selection_mode is either memberattr_normal or memberattr_dn
+search_user_info(State, User) ->
+ %%?ERROR_MSG("XXX search_user_info: searching for ~p", [User]),
+ SearchResult = case State#state.member_selection_mode of
+ memberattr_dn -> eldap_search_dn(State,
+ User,
+ State#state.ufilter,
+ [State#state.user_desc, State#state.uid]);
+ memberattr_normal -> eldap_search(State,
+ State#state.base,
+ State#state.ufilter,
+ [{<<"%u">>, User}],
+ [State#state.user_desc, State#state.uid])
+ end,
+ case SearchResult of
+ [#eldap_entry{attributes=Attrs}|_] ->
+ construct_user(State, Attrs);
+ [] ->
+ %%?ERROR_MSG("XX not found", []),
+ error
end.
%% Getting User ID part by regex pattern
get_user_part_re(String, Pattern) ->
case catch re:run(String, Pattern) of
- {match, Captured} ->
- {First, Len} = lists:nth(2, Captured),
- Result = str:sub_string(String, First + 1, First + Len),
- {ok, Result};
- _ -> {error, badmatch}
+ {match, Captured} ->
+ {First, Len} = lists:nth(2,Captured),
+ Result = string:sub_string(String, First+1, First+Len),
+ {ok,Result};
+ _ -> {error, badmatch}
end.
+% select(SelectFirst, First, Second) ->
+% case SelectFirst of
+% true -> First;
+% _ -> Second
+% end.
+
+% prepare_filter(Opts, Name, Default, ReturnParsed) ->
+% F = gen_mod:get_opt(Name, Opts, Default),
+% prepare_filter(F, Name, ReturnParsed).
+
+% prepare_filter(F, Name, ReturnParsed) ->
+% case eldap_filter:parse(F) of
+% {ok, EldapFilter} ->
+% case ReturnParsed of
+% true -> EldapFilter;
+% _ -> F
+% end;
+% _ ->
+% ?ERROR_MSG(?INVALID_SETTING_MSG, [atom_to_list(Name), ?MODULE]),
+% []
+% end.
+
parse_options(Host, Opts) ->
Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
Cfg = eldap_utils:get_config(Host, Opts),
@@ -516,84 +761,114 @@ parse_options(Host, Opts) ->
(false) -> false;
(true) -> true
end, true),
- UserCacheValidity = gen_mod:get_opt(
- {ldap_user_cache_validity, Host}, Opts,
- fun(I) when is_integer(I), I>0 -> I end,
- ?USER_CACHE_VALIDITY),
- GroupCacheValidity = gen_mod:get_opt(
+ RosterCacheValidity = eldap_utils:get_opt(
{ldap_group_cache_validity, Host}, Opts,
fun(I) when is_integer(I), I>0 -> I end,
- ?GROUP_CACHE_VALIDITY),
- UserCacheSize = gen_mod:get_opt(
- {ldap_user_cache_size, Host}, Opts,
- fun(I) when is_integer(I), I>0 -> I end,
- ?CACHE_SIZE),
- GroupCacheSize = gen_mod:get_opt(
- {ldap_group_cache_size, Host}, Opts,
+ ?CACHE_VALIDITY),
+ RosterCacheSize = eldap_utils:get_opt(
+ {ldap_roster_cache_size, Host}, Opts,
fun(I) when is_integer(I), I>0 -> I end,
?CACHE_SIZE),
- ConfigFilter = gen_mod:get_opt({ldap_filter, Host}, Opts,
+ ConfigFilter = eldap_utils:get_opt({ldap_filter, Host}, Opts,
fun check_filter/1, <<"">>),
- ConfigUserFilter = gen_mod:get_opt({ldap_ufilter, Host}, Opts,
+ ConfigUserFilter = eldap_utils:get_opt({ldap_ufilter, Host}, Opts,
fun check_filter/1, <<"">>),
- ConfigGroupFilter = gen_mod:get_opt({ldap_gfilter, Host}, Opts,
+ ConfigGroupFilter = eldap_utils:get_opt({ldap_gfilter, Host}, Opts,
fun check_filter/1, <<"">>),
- RosterFilter = gen_mod:get_opt({ldap_rfilter, Host}, Opts,
+ RosterFilter = eldap_utils:get_opt({ldap_rfilter, Host}, Opts,
fun check_filter/1, <<"">>),
SubFilter = <<"(&(", UIDAttr/binary, "=",
- UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>,
+ UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>,
UserSubFilter = case ConfigUserFilter of
- <<"">> ->
- eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]);
- UString -> UString
- end,
+ <<"">> ->
+ eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]);
+ UString -> UString
+ end,
GroupSubFilter = case ConfigGroupFilter of
- <<"">> ->
- eldap_filter:do_sub(SubFilter,
- [{<<"%u">>, <<"*">>}]);
- GString -> GString
+ <<"">> ->
+ eldap_filter:do_sub(SubFilter,
+ [{<<"%u">>, <<"*">>}]);
+ GString -> GString
end,
Filter = case ConfigFilter of
- <<"">> -> SubFilter;
- _ ->
- <<"(&", SubFilter/binary, ConfigFilter/binary, ")">>
- end,
+ <<"">> -> SubFilter;
+ _ ->
+ <<"(&", SubFilter/binary, ConfigFilter/binary, ")">>
+ end,
UserFilter = case ConfigFilter of
- <<"">> -> UserSubFilter;
- _ ->
- <<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">>
- end,
+ <<"">> -> UserSubFilter;
+ _ ->
+ <<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">>
+ end,
GroupFilter = case ConfigFilter of
- <<"">> -> GroupSubFilter;
- _ ->
- <<"(&", GroupSubFilter/binary, ConfigFilter/binary,
- ")">>
- end,
+ <<"">> -> GroupSubFilter;
+ _ ->
+ <<"(&", GroupSubFilter/binary, ConfigFilter/binary,
+ ")">>
+ end,
+%%%%%%%%%%%%%
+ GroupBase = gen_mod:get_opt(ldap_group_base, Opts, fun iolist_to_binary/1,
+ Cfg#eldap_config.base),
+ GroupIsDN = gen_mod:get_opt(ldap_group_is_dn, Opts,
+ fun(on) -> true;
+ (off) -> false;
+ (false) -> false;
+ (true) -> true
+ end, true),
+ MemberSelMode = gen_mod:get_opt(ldap_member_selection_mode, Opts,
+ fun(memberattr_normal) -> memberattr_normal;
+ (memberattr_dn) -> memberattr_dn;
+ (group_children) -> group_children;
+ (Invalid) ->
+ ?ERROR_MSG("Invalid ldap_member_selection_mode '~p'. "
+ "Value 'memberattr_normal' will be used instead.",
+ [Invalid])
+ end, memberattr_normal),
+ SubscribeAll = gen_mod:get_opt(ldap_subscribe_all, Opts,
+ fun(on) -> true;
+ (off) -> false;
+ (false) -> false;
+ (true) -> true
+ end, false),
+ % MemberIsDN = (MemberSelMode == member_attr_dn) or (MemberSelMode == group_children),
+ ShGFilter = gen_mod:get_opt(ldap_shgfilter, Opts,
+ fun(all) -> all;
+ (none) -> none;
+ (S) -> check_filter(S)
+ end, all),
+ ShGAttr = gen_mod:get_opt(ldap_shgattr, Opts,
+ fun iolist_to_binary/1,
+ << GroupAttr/binary >>),
+%%%%%%
#state{host = Host, eldap_id = Eldap_ID,
- servers = Cfg#eldap_config.servers,
- backups = Cfg#eldap_config.backups,
+ servers = Cfg#eldap_config.servers,
+ backups = Cfg#eldap_config.backups,
port = Cfg#eldap_config.port,
- tls_options = Cfg#eldap_config.tls_options,
- dn = Cfg#eldap_config.dn,
+ tls_options = Cfg#eldap_config.tls_options,
+ dn = Cfg#eldap_config.dn,
password = Cfg#eldap_config.password,
base = Cfg#eldap_config.base,
deref_aliases = Cfg#eldap_config.deref_aliases,
- uid = UIDAttr,
- group_attr = GroupAttr, group_desc = GroupDesc,
- user_desc = UserDesc, user_uid = UserUID,
- uid_format = UIDAttrFormat,
- uid_format_re = UIDAttrFormatRe, filter = Filter,
- ufilter = UserFilter, rfilter = RosterFilter,
- gfilter = GroupFilter, auth_check = AuthCheck,
- user_cache_size = UserCacheSize,
- user_cache_validity = UserCacheValidity,
- group_cache_size = GroupCacheSize,
- group_cache_validity = GroupCacheValidity}.
+ group_attr = GroupAttr, group_desc = GroupDesc,
+ user_desc = UserDesc, uid = UserUID,
+ uid_format = UIDAttrFormat,
+ uid_format_re = UIDAttrFormatRe, filter = Filter,
+ ufilter = UserFilter, rfilter = RosterFilter,
+ gfilter = GroupFilter, auth_check = AuthCheck,
+ group_base = GroupBase,
+ member_attr = UIDAttr,
+ member_selection_mode = MemberSelMode,
+ group_is_dn = GroupIsDN,
+ shgfilter = ShGFilter,
+ shg_attr = ShGAttr,
+ subscribe_all = SubscribeAll,
+ roster_cache_size = RosterCacheSize,
+ roster_cache_validity = RosterCacheValidity}.
check_filter(F) ->
- NewF = iolist_to_binary(F),
- {ok, _} = eldap_filter:parse(NewF),
- NewF.
+ NewF = iolist_to_binary(F),
+ {ok, _} = eldap_filter:parse(NewF),
+ NewF.
mod_opt_type(deref_aliases) ->
fun (never) -> never;
@@ -661,6 +936,20 @@ mod_opt_type(ldap_user_cache_validity) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(ldap_userdesc) -> fun iolist_to_binary/1;
mod_opt_type(ldap_useruid) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_group_base) -> fun iolist_to_binary/1;
+mod_opt_type(ldap_group_is_dn) -> fun(B) when is_boolean(B) -> B end;
+mod_opt_type(ldap_member_selection_mode) ->
+ fun(memberattr_normal) -> memberattr_normal;
+ (memberattr_dn) -> memberattr_dn;
+ (group_children) -> group_children
+ end;
+mod_opt_type(ldap_subscribe_all) -> fun(B) when is_boolean(B) -> B end;
+mod_opt_type(ldap_shgfilter) ->
+ fun(all) -> all;
+ (none) -> none;
+ (S) -> check_filter(S)
+ end;
+mod_opt_type(ldap_shgattr) -> fun iolist_to_binary/1;
mod_opt_type(_) ->
[ldap_auth_check, ldap_filter, ldap_gfilter,
ldap_group_cache_size, ldap_group_cache_validity,
@@ -672,7 +961,9 @@ mod_opt_type(_) ->
ldap_deref_aliases, ldap_encrypt, ldap_password,
ldap_port, ldap_rootdn, ldap_servers,
ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth,
- ldap_tls_verify].
+ ldap_tls_verify, ldap_group_base, ldap_group_is_dn,
+ ldap_member_selection_mode, ldap_subscribe_all,
+ ldap_shgfilter, ldap_shgattr].
opt_type(ldap_filter) -> fun check_filter/1;
opt_type(ldap_gfilter) -> fun check_filter/1;