1 |
|
%%%------------------------------------------------------------------- |
2 |
|
%%% File : mod_shared_roster_ldap.erl |
3 |
|
%%% Author : Realloc <realloc@realloc.spb.ru> |
4 |
|
%%% Marcin Owsiany <marcin@owsiany.pl> |
5 |
|
%%% Evgeniy Khramtsov <ekhramtsov@process-one.net> |
6 |
|
%%% Description : LDAP shared roster management |
7 |
|
%%% Created : 5 Mar 2005 by Alexey Shchepin <alexey@process-one.net> |
8 |
|
%%% |
9 |
|
%%% |
10 |
|
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne |
11 |
|
%%% |
12 |
|
%%% This program is free software; you can redistribute it and/or |
13 |
|
%%% modify it under the terms of the GNU General Public License as |
14 |
|
%%% published by the Free Software Foundation; either version 2 of the |
15 |
|
%%% License, or (at your option) any later version. |
16 |
|
%%% |
17 |
|
%%% This program is distributed in the hope that it will be useful, |
18 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
20 |
|
%%% General Public License for more details. |
21 |
|
%%% |
22 |
|
%%% You should have received a copy of the GNU General Public License |
23 |
|
%%% along with this program; if not, write to the Free Software |
24 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
25 |
|
%%% |
26 |
|
%%%------------------------------------------------------------------- |
27 |
|
-module(mod_shared_roster_ldap). |
28 |
|
|
29 |
|
-behaviour(gen_server). |
30 |
|
|
31 |
|
-behaviour(gen_mod). |
32 |
|
|
33 |
|
-behaviour(mongoose_module_metrics). |
34 |
|
|
35 |
|
%% API |
36 |
|
-export([start_link/2, start/2, stop/1, config_spec/0]). |
37 |
|
|
38 |
|
%% gen_server callbacks |
39 |
|
-export([init/1, handle_call/3, handle_cast/2, |
40 |
|
handle_info/2, terminate/2, code_change/3]). |
41 |
|
|
42 |
|
%% Hook handlers |
43 |
|
-export([get_user_roster/3, get_subscription_lists/3, |
44 |
|
get_jid_info/3, process_item/3, in_subscription/3, |
45 |
|
out_subscription/3]). |
46 |
|
|
47 |
|
-ignore_xref([start_link/2]). |
48 |
|
|
49 |
|
-include("jlib.hrl"). |
50 |
|
-include("mod_roster.hrl"). |
51 |
|
-include("mongoose_config_spec.hrl"). |
52 |
|
-include_lib("eldap/include/eldap.hrl"). |
53 |
|
|
54 |
|
-define(CACHE_SIZE, 1000). |
55 |
|
|
56 |
|
-define(USER_CACHE_VALIDITY, 300). |
57 |
|
|
58 |
|
-define(GROUP_CACHE_VALIDITY, 300). |
59 |
|
|
60 |
|
-define(LDAP_SEARCH_TIMEOUT, 5). |
61 |
|
|
62 |
|
%% re:mp() type (it is not exprted in the re module) |
63 |
|
-type re_mp() :: {re_pattern, _, _, _, _}. |
64 |
|
|
65 |
|
-record(state, |
66 |
|
{host = <<>> :: binary(), |
67 |
|
eldap_id :: eldap_utils:eldap_id(), |
68 |
|
base = <<>> :: binary(), |
69 |
|
uid = <<>> :: binary(), |
70 |
|
deref = neverDerefAliases :: eldap_utils:deref(), |
71 |
|
group_attr = <<>> :: binary(), |
72 |
|
group_desc = <<>> :: binary(), |
73 |
|
user_desc = <<>> :: binary(), |
74 |
|
user_uid = <<>> :: binary(), |
75 |
|
uid_format = <<>> :: binary(), |
76 |
|
uid_format_re = <<>> :: binary() | re_mp(), |
77 |
|
filter = <<>> :: binary(), |
78 |
|
ufilter = <<>> :: binary(), |
79 |
|
rfilter = <<>> :: binary(), |
80 |
|
gfilter = <<>> :: binary(), |
81 |
|
auth_check = true :: boolean(), |
82 |
|
user_cache_size = ?CACHE_SIZE :: non_neg_integer(), |
83 |
|
group_cache_size = ?CACHE_SIZE :: non_neg_integer(), |
84 |
|
user_cache_validity = ?USER_CACHE_VALIDITY :: non_neg_integer(), |
85 |
|
group_cache_validity = ?GROUP_CACHE_VALIDITY :: non_neg_integer()}). |
86 |
|
|
87 |
|
-record(group_info, {desc, members}). |
88 |
|
|
89 |
|
%%==================================================================== |
90 |
|
%% API |
91 |
|
%%==================================================================== |
92 |
|
start_link(Host, Opts) -> |
93 |
:-( |
Proc = gen_mod:get_module_proc(Host, ?MODULE), |
94 |
:-( |
gen_server:start_link({local, Proc}, ?MODULE, |
95 |
|
[Host, Opts], []). |
96 |
|
|
97 |
|
start(Host, Opts) -> |
98 |
:-( |
Proc = gen_mod:get_module_proc(Host, ?MODULE), |
99 |
:-( |
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, |
100 |
|
permanent, 1000, worker, [?MODULE]}, |
101 |
:-( |
ejabberd_sup:start_child(ChildSpec). |
102 |
|
|
103 |
|
stop(Host) -> |
104 |
:-( |
Proc = gen_mod:get_module_proc(Host, ?MODULE), |
105 |
:-( |
ejabberd_sup:stop_child(Proc). |
106 |
|
|
107 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
108 |
|
config_spec() -> |
109 |
208 |
CommonLDAPSpec = mongoose_ldap_config:spec(), |
110 |
208 |
Items = #{<<"groupattr">> => #option{type = binary}, |
111 |
|
<<"groupdesc">> => #option{type = binary}, |
112 |
|
<<"userdesc">> => #option{type = binary}, |
113 |
|
<<"useruid">> => #option{type = binary}, |
114 |
|
<<"memberattr">> => #option{type = binary}, |
115 |
|
<<"memberattr_format">> => #option{type = binary}, |
116 |
|
<<"memberattr_format_re">> => #option{type = binary}, |
117 |
|
<<"auth_check">> => #option{type = boolean}, |
118 |
|
<<"user_cache_validity">> => #option{type = integer, |
119 |
|
validate = positive}, |
120 |
|
<<"group_cache_validity">> => #option{type = integer, |
121 |
|
validate = positive}, |
122 |
|
<<"user_cache_size">> => #option{type = integer, |
123 |
|
validate = positive}, |
124 |
|
<<"group_cache_size">> => #option{type = integer, |
125 |
|
validate = positive}, |
126 |
|
<<"rfilter">> => #option{type = binary}, |
127 |
|
<<"gfilter">> => #option{type = binary}, |
128 |
|
<<"ufilter">> => #option{type = binary} |
129 |
|
}, |
130 |
208 |
Defaults = #{<<"groupattr">> => <<"cn">>, |
131 |
|
<<"userdesc">> => <<"cn">>, |
132 |
|
<<"useruid">> => <<"cn">>, |
133 |
|
<<"memberattr">> => <<"memberUid">>, |
134 |
|
<<"memberattr_format">> => <<"%u">>, |
135 |
|
<<"memberattr_format_re">> => <<>>, |
136 |
|
<<"auth_check">> => true, |
137 |
|
<<"user_cache_validity">> => ?USER_CACHE_VALIDITY, |
138 |
|
<<"group_cache_validity">> => ?GROUP_CACHE_VALIDITY, |
139 |
|
<<"user_cache_size">> => ?CACHE_SIZE, |
140 |
|
<<"group_cache_size">> => ?CACHE_SIZE, |
141 |
|
<<"rfilter">> => <<>>, |
142 |
|
<<"gfilter">> => <<>>, |
143 |
|
<<"ufilter">> => <<>>}, |
144 |
208 |
CommonLDAPSpec#section{items = maps:merge(CommonLDAPSpec#section.items, Items), |
145 |
|
defaults = maps:merge(CommonLDAPSpec#section.defaults, Defaults), |
146 |
|
process = fun process_ldap_options/1}. |
147 |
|
|
148 |
|
process_ldap_options(Opts = #{groupattr := GroupAttr}) -> |
149 |
:-( |
GroupDesc = maps:get(groupdesc, Opts, GroupAttr), |
150 |
:-( |
Opts#{groupdesc => GroupDesc}. |
151 |
|
|
152 |
|
%%-------------------------------------------------------------------- |
153 |
|
%% Hooks |
154 |
|
%%-------------------------------------------------------------------- |
155 |
|
-spec get_user_roster(Acc, Params, Extra) -> {ok, Acc} when |
156 |
|
Acc :: [mod_roster:roster()], |
157 |
|
Params :: #{mongoose_acc := mongoose_acc:t(), jid := jid:jid()}, |
158 |
|
Extra :: gen_hook:extra(). |
159 |
|
get_user_roster(Items, #{jid := JID}, _) -> |
160 |
:-( |
US = jid:to_lus(JID), |
161 |
:-( |
SRUsers = get_user_to_groups_map(US, true), |
162 |
:-( |
{NewItems1, SRUsersRest} = |
163 |
|
lists:mapfoldl( |
164 |
|
fun (Item, SRUsers1) -> |
165 |
:-( |
Contact = Item#roster.jid, |
166 |
:-( |
US1 = jid:to_lus(Contact), |
167 |
:-( |
case dict:find(US1, SRUsers1) of |
168 |
|
{ok, _GroupNames} -> |
169 |
:-( |
{Item#roster{subscription = both, ask = none}, |
170 |
|
dict:erase(US1, SRUsers1)}; |
171 |
|
error -> |
172 |
:-( |
{Item, SRUsers1} |
173 |
|
end |
174 |
|
end, |
175 |
|
SRUsers, Items), |
176 |
:-( |
SRItems = [#roster{usj = {US, {U1, S1, <<>>}}, |
177 |
|
us = US, jid = {U1, S1, <<>>}, |
178 |
|
name = get_user_name(U1, S1), subscription = both, |
179 |
|
ask = none, groups = GroupNames} |
180 |
:-( |
|| {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)], |
181 |
:-( |
{ok, SRItems ++ NewItems1}. |
182 |
|
|
183 |
|
%% This function in use to rewrite the roster entries when moving or renaming |
184 |
|
%% them in the user contact list. |
185 |
|
-spec process_item(Acc, Params, Extra) -> {ok, Acc} when |
186 |
|
Acc :: mod_roster:roster(), |
187 |
|
Params :: map(), |
188 |
|
Extra :: gen_hook:extra(). |
189 |
|
process_item(RosterItem, _, _) -> |
190 |
:-( |
USFrom = RosterItem#roster.us, |
191 |
:-( |
{User, Server, _Resource} = RosterItem#roster.jid, |
192 |
:-( |
USTo = {User, Server}, |
193 |
:-( |
Map = get_user_to_groups_map(USFrom, false), |
194 |
:-( |
NewRosterItem = case dict:find(USTo, Map) of |
195 |
:-( |
error -> RosterItem; |
196 |
:-( |
{ok, []} -> RosterItem; |
197 |
|
{ok, GroupNames} |
198 |
|
when RosterItem#roster.subscription == remove -> |
199 |
:-( |
RosterItem#roster{subscription = both, ask = none, |
200 |
|
groups = GroupNames}; |
201 |
:-( |
_ -> RosterItem#roster{subscription = both, ask = none} |
202 |
|
end, |
203 |
:-( |
{ok, NewRosterItem}. |
204 |
|
|
205 |
|
-spec get_subscription_lists(Acc, Params, Extra) -> {ok, Acc} when |
206 |
|
Acc ::mongoose_acc:t(), |
207 |
|
Params :: #{jid := jid:jid()}, |
208 |
|
Extra :: gen_hook:extra(). |
209 |
|
get_subscription_lists(Acc, #{jid := #jid{lserver = LServer} = JID}, _) -> |
210 |
:-( |
{F, T, P} = mongoose_acc:get(roster, subscription_lists, {[], [], []}, Acc), |
211 |
:-( |
US = jid:to_lus(JID), |
212 |
:-( |
DisplayedGroups = get_user_displayed_groups(US), |
213 |
:-( |
SRUsers = lists:usort(lists:flatmap(fun (Group) -> |
214 |
:-( |
get_group_users(LServer, Group) |
215 |
|
end, |
216 |
|
DisplayedGroups)), |
217 |
:-( |
SRJIDs = [{U1, S1, <<>>} || {U1, S1} <- SRUsers], |
218 |
:-( |
NewLists = {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T), P}, |
219 |
:-( |
{ok, mongoose_acc:set(roster, subscription_lists, NewLists, Acc)}. |
220 |
|
|
221 |
|
-spec get_jid_info(Acc, Params, Extra) -> {ok, Acc} when |
222 |
|
Acc :: {mod_roster:subscription_state(), [binary()]}, |
223 |
|
Params :: #{to := jid:jid(), remote := jid:jid() | jid:simple_jid()}, |
224 |
|
Extra :: gen_hook:extra(). |
225 |
|
get_jid_info({Subscription, Groups}, #{to := ToJID, remote := JID}, _) -> |
226 |
:-( |
ToUS = jid:to_lus(ToJID), |
227 |
:-( |
US1 = jid:to_lus(JID), |
228 |
:-( |
SRUsers = get_user_to_groups_map(ToUS, false), |
229 |
:-( |
NewAcc = case dict:find(US1, SRUsers) of |
230 |
|
{ok, GroupNames} -> |
231 |
:-( |
NewGroups = case Groups of |
232 |
:-( |
[] -> GroupNames; |
233 |
:-( |
_ -> Groups |
234 |
|
end, |
235 |
:-( |
{both, NewGroups}; |
236 |
:-( |
error -> {Subscription, Groups} |
237 |
|
end, |
238 |
:-( |
{ok, NewAcc}. |
239 |
|
|
240 |
|
-spec in_subscription(Acc, Params, Extra) -> {ok | stop, Acc} when |
241 |
|
Acc :: mongoose_acc:t(), |
242 |
|
Params :: #{to := jid:jid(), |
243 |
|
from := jid:jid(), |
244 |
|
type := mod_roster:sub_presence()}, |
245 |
|
Extra :: gen_hook:extra(). |
246 |
|
in_subscription(Acc, #{to := ToJID, from := FromJID, type := Type}, _) -> |
247 |
:-( |
case process_subscription(in, ToJID, FromJID, Type) of |
248 |
|
stop -> |
249 |
:-( |
{stop, Acc}; |
250 |
|
{stop, false} -> |
251 |
:-( |
{stop, mongoose_acc:set(hook, result, false, Acc)}; |
252 |
:-( |
_ -> {ok, Acc} |
253 |
|
end. |
254 |
|
|
255 |
|
-spec out_subscription(Acc, Params, Extra) -> {ok | stop, Acc} when |
256 |
|
Acc :: mongoose_acc:t(), |
257 |
|
Params :: #{to := jid:jid(), |
258 |
|
from := jid:jid(), |
259 |
|
type := mod_roster:sub_presence()}, |
260 |
|
Extra :: gen_hook:extra(). |
261 |
|
out_subscription(Acc, #{to := ToJID, from := FromJID, type := Type}, _) -> |
262 |
:-( |
case process_subscription(out, FromJID, ToJID, Type) of |
263 |
|
stop -> |
264 |
:-( |
{stop, Acc}; |
265 |
|
{stop, false} -> |
266 |
:-( |
{stop, Acc}; |
267 |
:-( |
false -> {ok, Acc} |
268 |
|
end. |
269 |
|
|
270 |
|
process_subscription(Direction, #jid{luser = LUser, lserver = LServer}, ToJID, _Type) -> |
271 |
:-( |
US = {LUser, LServer}, |
272 |
:-( |
{U1, S1, _} = jid:to_lower(jid:to_bare(ToJID)), |
273 |
:-( |
US1 = {U1, S1}, |
274 |
:-( |
DisplayedGroups = get_user_displayed_groups(US), |
275 |
:-( |
SRUsers = lists:usort(lists:flatmap( |
276 |
|
fun (Group) -> |
277 |
:-( |
get_group_users(LServer, Group) |
278 |
|
end, |
279 |
|
DisplayedGroups)), |
280 |
:-( |
case lists:member(US1, SRUsers) of |
281 |
|
true -> |
282 |
:-( |
case Direction of |
283 |
:-( |
in -> {stop, false}; |
284 |
:-( |
out -> stop |
285 |
|
end; |
286 |
:-( |
false -> false |
287 |
|
end. |
288 |
|
|
289 |
|
|
290 |
|
%%==================================================================== |
291 |
|
%% gen_server callbacks |
292 |
|
%%==================================================================== |
293 |
|
init([Host, Opts]) -> |
294 |
:-( |
State = parse_options(Host, Opts), |
295 |
:-( |
process_flag(trap_exit, true), |
296 |
:-( |
cache_tab:new(shared_roster_ldap_user, |
297 |
|
[{max_size, State#state.user_cache_size}, {lru, false}, |
298 |
|
{life_time, State#state.user_cache_validity}]), |
299 |
:-( |
cache_tab:new(shared_roster_ldap_group, |
300 |
|
[{max_size, State#state.group_cache_size}, {lru, false}, |
301 |
|
{life_time, State#state.group_cache_validity}]), |
302 |
:-( |
gen_hook:add_handlers(hooks(Host)), |
303 |
:-( |
{ok, State}. |
304 |
|
|
305 |
|
handle_call(get_state, _From, State) -> |
306 |
:-( |
{reply, {ok, State}, State}; |
307 |
|
handle_call(_Request, _From, State) -> |
308 |
:-( |
{reply, {error, badarg}, State}. |
309 |
|
|
310 |
:-( |
handle_cast(_Msg, State) -> {noreply, State}. |
311 |
|
|
312 |
:-( |
handle_info(_Info, State) -> {noreply, State}. |
313 |
|
|
314 |
|
terminate(_Reason, State) -> |
315 |
:-( |
Host = State#state.host, |
316 |
:-( |
gen_hook:delete_handlers(hooks(Host)). |
317 |
|
|
318 |
:-( |
code_change(_OldVsn, State, _Extra) -> {ok, State}. |
319 |
|
|
320 |
|
-spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(). |
321 |
|
hooks(HostType) -> |
322 |
:-( |
[ |
323 |
|
{roster_get, HostType, fun ?MODULE:get_user_roster/3, #{}, 70}, |
324 |
|
{roster_in_subscription, HostType, fun ?MODULE:in_subscription/3, #{}, 70}, |
325 |
|
{roster_out_subscription, HostType, fun ?MODULE:out_subscription/3, #{}, 70}, |
326 |
|
{roster_get_subscription_lists, HostType, fun ?MODULE:get_subscription_lists/3, #{}, 70}, |
327 |
|
{roster_get_jid_info, HostType, fun ?MODULE:get_jid_info/3, #{}, 70}, |
328 |
|
{roster_process_item, HostType, fun ?MODULE:process_item/3, #{}, 70} |
329 |
|
]. |
330 |
|
|
331 |
|
%%-------------------------------------------------------------------- |
332 |
|
%%% Internal functions |
333 |
|
%%-------------------------------------------------------------------- |
334 |
|
%% For a given user, map all his shared roster contacts to groups they are |
335 |
|
%% members of. Skip the user himself iff SkipUS is true. |
336 |
|
get_user_to_groups_map({_, Server} = US, SkipUS) -> |
337 |
:-( |
DisplayedGroups = get_user_displayed_groups(US), |
338 |
|
%% Pass given FilterParseArgs to eldap_filter:parse, and if successful, run and |
339 |
|
%% return the resulting filter, retrieving given AttributesList. Return the |
340 |
|
%% result entries. On any error silently return an empty list of results. |
341 |
|
%% |
342 |
|
%% Eldap server ID and base DN for the query are both retrieved from the State |
343 |
|
%% record. |
344 |
:-( |
lists:foldl(fun (Group, Dict1) -> |
345 |
:-( |
GroupName = get_group_name(Server, Group), |
346 |
:-( |
lists:foldl(fun (Contact, Dict) when SkipUS, Contact == US -> |
347 |
:-( |
Dict; |
348 |
|
(Contact, Dict) -> |
349 |
:-( |
dict:append(Contact, GroupName, Dict) |
350 |
|
end, |
351 |
|
Dict1, get_group_users(Server, Group)) |
352 |
|
end, |
353 |
|
dict:new(), DisplayedGroups). |
354 |
|
|
355 |
|
eldap_search(State, FilterParseArgs, AttributesList) -> |
356 |
:-( |
case apply(eldap_filter, parse, FilterParseArgs) of |
357 |
|
{ok, EldapFilter} -> |
358 |
:-( |
SearchOpts = search_opts(EldapFilter, AttributesList, State), |
359 |
:-( |
case eldap_pool:search(State#state.eldap_id, SearchOpts) of |
360 |
|
#eldap_search_result{entries = Es} -> |
361 |
|
%% A result with entries. Return their list. |
362 |
:-( |
Es; |
363 |
|
_ -> |
364 |
|
%% Something else. Pretend we got no results. |
365 |
:-( |
[] |
366 |
|
end; |
367 |
|
_ -> |
368 |
|
%% Filter parsing failed. Pretend we got no results. |
369 |
:-( |
[] |
370 |
|
end. |
371 |
|
|
372 |
|
search_opts(EldapFilter, AttributesList, State) -> |
373 |
:-( |
[{base, State#state.base}, |
374 |
|
{filter, EldapFilter}, |
375 |
|
{timeout, ?LDAP_SEARCH_TIMEOUT}, |
376 |
|
{deref, State#state.deref}, |
377 |
|
{attributes, AttributesList}]. |
378 |
|
|
379 |
|
get_user_displayed_groups({User, Host}) -> |
380 |
:-( |
{ok, State} = eldap_utils:get_state(Host, ?MODULE), |
381 |
:-( |
GroupAttr = State#state.group_attr, |
382 |
:-( |
Entries = eldap_search(State, |
383 |
|
[eldap_filter:do_sub(State#state.rfilter, [{<<"%u">>, User}])], |
384 |
|
[GroupAttr]), |
385 |
:-( |
Reply = lists:flatmap(fun (#eldap_entry{attributes = Attrs}) -> |
386 |
:-( |
case eldap_utils:singleton_value(Attrs) of |
387 |
:-( |
{GroupAttr, Value} -> [eldap_utils:maybe_list2b(Value)]; |
388 |
:-( |
_ -> [] |
389 |
|
end |
390 |
|
end, |
391 |
|
Entries), |
392 |
:-( |
lists:usort(Reply). |
393 |
|
|
394 |
|
get_group_users(Host, Group) -> |
395 |
:-( |
{ok, State} = eldap_utils:get_state(Host, ?MODULE), |
396 |
:-( |
case cache_tab:dirty_lookup(shared_roster_ldap_group, |
397 |
|
{Group, Host}, |
398 |
:-( |
fun() -> search_group_info(State, Group) end) |
399 |
|
of |
400 |
|
{ok, #group_info{members = Members}} |
401 |
|
when Members /= undefined -> |
402 |
:-( |
Members; |
403 |
:-( |
_ -> [] |
404 |
|
end. |
405 |
|
|
406 |
|
get_group_name(Host, Group) -> |
407 |
:-( |
{ok, State} = eldap_utils:get_state(Host, ?MODULE), |
408 |
:-( |
case cache_tab:dirty_lookup(shared_roster_ldap_group, |
409 |
|
{Group, Host}, |
410 |
:-( |
fun() -> search_group_info(State, Group) end) |
411 |
|
of |
412 |
|
{ok, #group_info{desc = GroupName}} |
413 |
|
when GroupName /= undefined -> |
414 |
:-( |
GroupName; |
415 |
:-( |
_ -> Group |
416 |
|
end. |
417 |
|
|
418 |
|
get_user_name(User, Host) -> |
419 |
:-( |
{ok, State} = eldap_utils:get_state(Host, ?MODULE), |
420 |
:-( |
case cache_tab:dirty_lookup(shared_roster_ldap_user, |
421 |
|
{User, Host}, |
422 |
:-( |
fun() -> search_user_name(State, User) end) |
423 |
|
of |
424 |
:-( |
{ok, UserName} -> UserName; |
425 |
:-( |
error -> User |
426 |
|
end. |
427 |
|
|
428 |
|
search_group_info(State, Group) -> |
429 |
:-( |
Extractor = case State#state.uid_format_re of |
430 |
|
<<"">> -> |
431 |
:-( |
fun (UID) -> |
432 |
:-( |
catch eldap_utils:get_user_part( |
433 |
|
UID, |
434 |
|
State#state.uid_format) |
435 |
|
end; |
436 |
|
_ -> |
437 |
:-( |
fun (UID) -> |
438 |
:-( |
catch get_user_part_re( |
439 |
|
UID, |
440 |
|
State#state.uid_format_re) |
441 |
|
end |
442 |
|
end, |
443 |
:-( |
AuthChecker = case State#state.auth_check of |
444 |
:-( |
true -> fun ejabberd_auth:does_user_exist/1; |
445 |
:-( |
false -> fun(_JID) -> true end |
446 |
|
end, |
447 |
:-( |
Host = State#state.host, |
448 |
:-( |
case eldap_search(State, |
449 |
|
[eldap_filter:do_sub(State#state.gfilter, |
450 |
|
[{<<"%g">>, Group}])], |
451 |
|
[State#state.group_attr, State#state.group_desc, |
452 |
|
State#state.uid]) of |
453 |
|
[] -> |
454 |
:-( |
error; |
455 |
|
LDAPEntries -> |
456 |
:-( |
{GroupDesc, MembersLists} = ldap_entries_to_group(LDAPEntries, Host, Group, State, |
457 |
|
Extractor, AuthChecker), |
458 |
:-( |
{ok, #group_info{desc = GroupDesc, members = lists:usort(MembersLists)}} |
459 |
|
end. |
460 |
|
|
461 |
|
ldap_entries_to_group(LDAPEntries, Host, Group, State, Extractor, AuthChecker) -> |
462 |
:-( |
ldap_entries_to_group(LDAPEntries, Host, Group, [], State, Extractor, AuthChecker). |
463 |
|
|
464 |
|
ldap_entries_to_group([#eldap_entry{ attributes = Attrs } | REntries], Host, |
465 |
|
DescAcc, JIDsAcc, State, Extractor, AuthChecker) -> |
466 |
:-( |
UID = lists:keysearch(State#state.uid, 1, Attrs), |
467 |
:-( |
ListUID = State#state.uid, |
468 |
:-( |
case {eldap_utils:get_ldap_attr(State#state.group_attr, Attrs), |
469 |
|
eldap_utils:get_ldap_attr(State#state.group_desc, Attrs), UID} of |
470 |
|
{ID, Desc, {value, {GroupMemberAttr, MemberIn}}} |
471 |
|
when ID /= <<"">>, GroupMemberAttr == ListUID -> |
472 |
:-( |
Member = case MemberIn of |
473 |
:-( |
[M] -> M; |
474 |
:-( |
_ -> MemberIn |
475 |
|
end, |
476 |
:-( |
Extracted = Extractor(eldap_utils:maybe_list2b(Member)), |
477 |
:-( |
NewJIDsAcc = check_and_accumulate_member(Extracted, AuthChecker, Host, JIDsAcc), |
478 |
:-( |
ldap_entries_to_group(REntries, Host, Desc, NewJIDsAcc, State, Extractor, AuthChecker); |
479 |
|
_ -> |
480 |
:-( |
ldap_entries_to_group(REntries, Host, DescAcc, JIDsAcc, State, Extractor, AuthChecker) |
481 |
|
end; |
482 |
|
ldap_entries_to_group([], _Host, DescAcc, JIDsAcc, _State, _Extractor, _AuthChecker) -> |
483 |
:-( |
{DescAcc, JIDsAcc}. |
484 |
|
|
485 |
|
check_and_accumulate_member({ok, UID}, AuthChecker, Host, JIDsAcc) -> |
486 |
:-( |
PUID = jid:nodeprep(UID), |
487 |
:-( |
case PUID of |
488 |
|
error -> |
489 |
:-( |
JIDsAcc; |
490 |
|
_ -> |
491 |
:-( |
JID = jid:make_bare(PUID, Host), |
492 |
:-( |
case AuthChecker(JID) of |
493 |
|
true -> |
494 |
:-( |
[{PUID, Host} | JIDsAcc]; |
495 |
|
_ -> |
496 |
:-( |
JIDsAcc |
497 |
|
end |
498 |
|
end; |
499 |
|
check_and_accumulate_member(_, _AuthChecker, _Host, JIDsAcc) -> |
500 |
:-( |
JIDsAcc. |
501 |
|
|
502 |
|
search_user_name(State, User) -> |
503 |
:-( |
case eldap_search(State, |
504 |
|
[eldap_filter:do_sub(State#state.ufilter, |
505 |
|
[{<<"%u">>, User}])], |
506 |
|
[State#state.user_desc, State#state.user_uid]) |
507 |
|
of |
508 |
|
[#eldap_entry{attributes = Attrs} | _] -> |
509 |
:-( |
case {eldap_utils:get_ldap_attr(State#state.user_uid, Attrs), |
510 |
|
eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)} |
511 |
|
of |
512 |
:-( |
{UID, Desc} when UID /= <<"">> -> {ok, Desc}; |
513 |
:-( |
_ -> error |
514 |
|
end; |
515 |
:-( |
[] -> error |
516 |
|
end. |
517 |
|
|
518 |
|
%% Getting User ID part by regex pattern |
519 |
|
get_user_part_re(String, Pattern) -> |
520 |
:-( |
case catch re:run(String, Pattern) of |
521 |
|
{match, Captured} -> |
522 |
:-( |
{First, Len} = lists:nth(2, Captured), |
523 |
:-( |
Result = binary:part(String, First, Len), |
524 |
:-( |
{ok, Result}; |
525 |
:-( |
_ -> {error, badmatch} |
526 |
|
end. |
527 |
|
|
528 |
|
|
529 |
|
parse_options(Host, #{base := Base, pool_tag := EldapID, deref := Deref, filter := FilterIn, |
530 |
|
groupattr := GroupAttr, groupdesc := GroupDesc, userdesc := UserDesc, |
531 |
|
useruid := UserUID, memberattr := UIDAttr, memberattr_format := UIDAttrFormat, |
532 |
|
memberattr_format_re := UIDAttrFormatReIn, auth_check := AuthCheck, |
533 |
|
user_cache_validity := UserCacheValidity, group_cache_validity := GroupCacheValidity, |
534 |
|
user_cache_size := UserCacheSize, group_cache_size := GroupCacheSize, |
535 |
|
ufilter := UFilterIn, gfilter := GFilterIn, rfilter := RFilterIn}) -> |
536 |
:-( |
DerefAliases = eldap_utils:deref_aliases(Deref), |
537 |
:-( |
ConfigFilter = check_filter(FilterIn), |
538 |
:-( |
ConfigUserFilter = check_filter(UFilterIn), |
539 |
:-( |
ConfigGroupFilter = check_filter(GFilterIn), |
540 |
:-( |
RosterFilter = check_filter(RFilterIn), |
541 |
:-( |
SubFilter = <<"(&(", UIDAttr/binary, "=", UIDAttrFormat/binary, |
542 |
|
")(", GroupAttr/binary, "=%g))">>, |
543 |
:-( |
UIDAttrFormatRe = case UIDAttrFormatReIn of |
544 |
:-( |
<<>> -> UIDAttrFormatReIn; |
545 |
|
RE -> |
546 |
:-( |
{ok, MP} = re:compile(RE), |
547 |
:-( |
MP |
548 |
|
end, |
549 |
:-( |
UserSubFilter = case ConfigUserFilter of |
550 |
|
<<"">> -> |
551 |
:-( |
eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]); |
552 |
:-( |
UString -> UString |
553 |
|
end, |
554 |
:-( |
GroupSubFilter = case ConfigGroupFilter of |
555 |
|
<<"">> -> |
556 |
:-( |
eldap_filter:do_sub(SubFilter, [{<<"%u">>, <<"*">>}]); |
557 |
:-( |
GString -> GString |
558 |
|
end, |
559 |
:-( |
Filter = case ConfigFilter of |
560 |
:-( |
<<"">> -> SubFilter; |
561 |
|
_ -> |
562 |
:-( |
<<"(&", SubFilter/binary, ConfigFilter/binary, ")">> |
563 |
|
end, |
564 |
:-( |
UserFilter = case ConfigFilter of |
565 |
:-( |
<<"">> -> UserSubFilter; |
566 |
|
_ -> |
567 |
:-( |
<<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">> |
568 |
|
end, |
569 |
:-( |
GroupFilter = case ConfigFilter of |
570 |
:-( |
<<"">> -> GroupSubFilter; |
571 |
|
_ -> |
572 |
:-( |
<<"(&", GroupSubFilter/binary, ConfigFilter/binary, ")">> |
573 |
|
end, |
574 |
:-( |
#state{host = Host, |
575 |
|
eldap_id = {Host, EldapID}, |
576 |
|
base = Base, |
577 |
|
deref = DerefAliases, |
578 |
|
uid = UIDAttr, |
579 |
|
group_attr = GroupAttr, group_desc = GroupDesc, |
580 |
|
user_desc = UserDesc, user_uid = UserUID, |
581 |
|
uid_format = UIDAttrFormat, |
582 |
|
uid_format_re = UIDAttrFormatRe, filter = Filter, |
583 |
|
ufilter = UserFilter, rfilter = RosterFilter, |
584 |
|
gfilter = GroupFilter, auth_check = AuthCheck, |
585 |
|
user_cache_size = UserCacheSize, |
586 |
|
user_cache_validity = UserCacheValidity, |
587 |
|
group_cache_size = GroupCacheSize, |
588 |
|
group_cache_validity = GroupCacheValidity}. |
589 |
|
|
590 |
:-( |
check_filter(<<>>) -> <<>>; |
591 |
|
check_filter(F) -> |
592 |
:-( |
{ok, _} = eldap_filter:parse(F), |
593 |
:-( |
F. |