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 |
|
-export([get_user_roster/2, get_subscription_lists/2, |
43 |
|
get_jid_info/4, process_item/2, in_subscription/5, |
44 |
|
out_subscription/4]). |
45 |
|
|
46 |
|
-ignore_xref([config_change/4, get_jid_info/4, get_subscription_lists/2, get_user_roster/2, |
47 |
|
in_subscription/5, out_subscription/4, process_item/2, start_link/2]). |
48 |
|
|
49 |
|
-include("mongoose.hrl"). |
50 |
|
-include("jlib.hrl"). |
51 |
|
-include("mod_roster.hrl"). |
52 |
|
-include("mongoose_config_spec.hrl"). |
53 |
|
|
54 |
|
-include("eldap.hrl"). |
55 |
|
|
56 |
|
-define(CACHE_SIZE, 1000). |
57 |
|
|
58 |
|
-define(USER_CACHE_VALIDITY, 300). |
59 |
|
|
60 |
|
-define(GROUP_CACHE_VALIDITY, 300). |
61 |
|
|
62 |
|
-define(LDAP_SEARCH_TIMEOUT, 5). |
63 |
|
|
64 |
|
-record(state, |
65 |
|
{host = <<>> :: binary(), |
66 |
|
eldap_id :: eldap_utils:eldap_id(), |
67 |
|
base = <<>> :: binary(), |
68 |
|
uid = <<>> :: binary(), |
69 |
|
deref = neverDerefAliases :: eldap_utils:deref(), |
70 |
|
group_attr = <<>> :: binary(), |
71 |
|
group_desc = <<>> :: binary(), |
72 |
|
user_desc = <<>> :: binary(), |
73 |
|
user_uid = <<>> :: binary(), |
74 |
|
uid_format = <<>> :: binary(), |
75 |
|
uid_format_re = <<>> :: binary(), |
76 |
|
filter = <<>> :: binary(), |
77 |
|
ufilter = <<>> :: binary(), |
78 |
|
rfilter = <<>> :: binary(), |
79 |
|
gfilter = <<>> :: binary(), |
80 |
|
auth_check = true :: boolean(), |
81 |
|
user_cache_size = ?CACHE_SIZE :: non_neg_integer(), |
82 |
|
group_cache_size = ?CACHE_SIZE :: non_neg_integer(), |
83 |
|
user_cache_validity = ?USER_CACHE_VALIDITY :: non_neg_integer(), |
84 |
|
group_cache_validity = ?GROUP_CACHE_VALIDITY :: non_neg_integer()}). |
85 |
|
|
86 |
|
-record(group_info, {desc, members}). |
87 |
|
|
88 |
|
%%==================================================================== |
89 |
|
%% API |
90 |
|
%%==================================================================== |
91 |
|
start_link(Host, Opts) -> |
92 |
:-( |
Proc = gen_mod:get_module_proc(Host, ?MODULE), |
93 |
:-( |
gen_server:start_link({local, Proc}, ?MODULE, |
94 |
|
[Host, Opts], []). |
95 |
|
|
96 |
|
start(Host, Opts) -> |
97 |
:-( |
Proc = gen_mod:get_module_proc(Host, ?MODULE), |
98 |
:-( |
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, |
99 |
|
permanent, 1000, worker, [?MODULE]}, |
100 |
:-( |
ejabberd_sup:start_child(ChildSpec). |
101 |
|
|
102 |
|
stop(Host) -> |
103 |
:-( |
Proc = gen_mod:get_module_proc(Host, ?MODULE), |
104 |
:-( |
ejabberd_sup:stop_child(Proc). |
105 |
|
|
106 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
107 |
|
config_spec() -> |
108 |
146 |
#section{ |
109 |
|
items = #{<<"ldap_pool_tag">> => #option{type = atom, |
110 |
|
validate = pool_name}, |
111 |
|
<<"ldap_base">> => #option{type = string}, |
112 |
|
<<"ldap_deref">> => #option{type = atom, |
113 |
|
validate = {enum, [never, always, finding, searching]}}, |
114 |
|
<<"ldap_groupattr">> => #option{type = string}, |
115 |
|
<<"ldap_groupdesc">> => #option{type = string}, |
116 |
|
<<"ldap_userdesc">> => #option{type = string}, |
117 |
|
<<"ldap_useruid">> => #option{type = string}, |
118 |
|
<<"ldap_memberattr">> => #option{type = string}, |
119 |
|
<<"ldap_memberattr_format">> => #option{type = string}, |
120 |
|
<<"ldap_memberattr_format_re">> => #option{type = string}, |
121 |
|
<<"ldap_auth_check">> => #option{type = boolean}, |
122 |
|
<<"ldap_user_cache_validity">> => #option{type = integer, |
123 |
|
validate = positive}, |
124 |
|
<<"ldap_group_cache_validity">> => #option{type = integer, |
125 |
|
validate = positive}, |
126 |
|
<<"ldap_user_cache_size">> => #option{type = integer, |
127 |
|
validate = positive}, |
128 |
|
<<"ldap_group_cache_size">> => #option{type = integer, |
129 |
|
validate = positive}, |
130 |
|
<<"ldap_rfilter">> => #option{type = string}, |
131 |
|
<<"ldap_gfilter">> => #option{type = string}, |
132 |
|
<<"ldap_ufilter">> => #option{type = string}, |
133 |
|
<<"ldap_filter">> => #option{type = string} |
134 |
|
} |
135 |
|
}. |
136 |
|
|
137 |
|
%%-------------------------------------------------------------------- |
138 |
|
%% Hooks |
139 |
|
%%-------------------------------------------------------------------- |
140 |
|
get_user_roster(Acc, #jid{luser = U, lserver = S} = JID) -> |
141 |
:-( |
US = jid:to_lus(JID), |
142 |
:-( |
Items = mongoose_acc:get(roster, items, [], Acc), |
143 |
:-( |
SRUsers = get_user_to_groups_map(US, true), |
144 |
:-( |
{NewItems1, SRUsersRest} = |
145 |
|
lists:mapfoldl( |
146 |
|
fun (Item, SRUsers1) -> |
147 |
:-( |
{_, _, {U1, S1, _}} = Item#roster.usj, |
148 |
:-( |
US1 = {U1, S1}, |
149 |
:-( |
case dict:find(US1, SRUsers1) of |
150 |
|
{ok, _GroupNames} -> |
151 |
:-( |
{Item#roster{subscription = both, ask = none}, |
152 |
|
dict:erase(US1, SRUsers1)}; |
153 |
|
error -> |
154 |
:-( |
{Item, SRUsers1} |
155 |
|
end |
156 |
|
end, |
157 |
|
SRUsers, Items), |
158 |
:-( |
SRItems = [#roster{usj = {U, S, {U1, S1, <<"">>}}, |
159 |
|
us = US, jid = {U1, S1, <<"">>}, |
160 |
|
name = get_user_name(U1, S1), subscription = both, |
161 |
|
ask = none, groups = GroupNames} |
162 |
:-( |
|| {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)], |
163 |
:-( |
mongoose_acc:set(roster, items, SRItems ++ NewItems1, Acc). |
164 |
|
|
165 |
|
%% This function in use to rewrite the roster entries when moving or renaming |
166 |
|
%% them in the user contact list. |
167 |
|
process_item(RosterItem, _Host) -> |
168 |
:-( |
USFrom = RosterItem#roster.us, |
169 |
:-( |
{User, Server, _Resource} = RosterItem#roster.jid, |
170 |
:-( |
USTo = {User, Server}, |
171 |
:-( |
Map = get_user_to_groups_map(USFrom, false), |
172 |
:-( |
case dict:find(USTo, Map) of |
173 |
:-( |
error -> RosterItem; |
174 |
:-( |
{ok, []} -> RosterItem; |
175 |
|
{ok, GroupNames} |
176 |
|
when RosterItem#roster.subscription == remove -> |
177 |
:-( |
RosterItem#roster{subscription = both, ask = none, |
178 |
|
groups = GroupNames}; |
179 |
:-( |
_ -> RosterItem#roster{subscription = both, ask = none} |
180 |
|
end. |
181 |
|
|
182 |
|
get_subscription_lists(Acc, #jid{lserver = LServer} = JID) -> |
183 |
:-( |
{F, T, P} = mongoose_acc:get(roster, subscription_lists, {[], [], []}, Acc), |
184 |
:-( |
US = jid:to_lus(JID), |
185 |
:-( |
DisplayedGroups = get_user_displayed_groups(US), |
186 |
:-( |
SRUsers = lists:usort(lists:flatmap(fun (Group) -> |
187 |
:-( |
get_group_users(LServer, Group) |
188 |
|
end, |
189 |
|
DisplayedGroups)), |
190 |
:-( |
SRJIDs = [{U1, S1, <<>>} || {U1, S1} <- SRUsers], |
191 |
:-( |
NewLists = {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T), P}, |
192 |
:-( |
mongoose_acc:set(roster, subscription_lists, NewLists, Acc). |
193 |
|
|
194 |
|
get_jid_info({Subscription, Groups}, _HostType, ToJID, JID) -> |
195 |
:-( |
ToUS = jid:to_lus(ToJID), |
196 |
:-( |
US1 = jid:to_lus(JID), |
197 |
:-( |
SRUsers = get_user_to_groups_map(ToUS, false), |
198 |
:-( |
case dict:find(US1, SRUsers) of |
199 |
|
{ok, GroupNames} -> |
200 |
:-( |
NewGroups = case Groups of |
201 |
:-( |
[] -> GroupNames; |
202 |
:-( |
_ -> Groups |
203 |
|
end, |
204 |
:-( |
{both, NewGroups}; |
205 |
:-( |
error -> {Subscription, Groups} |
206 |
|
end. |
207 |
|
|
208 |
|
-spec in_subscription(Acc:: mongoose_acc:t(), |
209 |
|
ToJID :: jid:jid(), |
210 |
|
FromJID :: jid:jid(), |
211 |
|
Type :: mod_roster:sub_presence(), |
212 |
|
_Reason :: any()) -> |
213 |
|
mongoose_acc:t() | {stop, mongoose_acc:t()}. |
214 |
|
in_subscription(Acc, ToJID, FromJID, Type, _Reason) -> |
215 |
:-( |
case process_subscription(in, ToJID, FromJID, Type) of |
216 |
|
stop -> |
217 |
:-( |
{stop, Acc}; |
218 |
|
{stop, false} -> |
219 |
:-( |
{stop, mongoose_acc:set(hook, result, false, Acc)}; |
220 |
:-( |
_ -> Acc |
221 |
|
end. |
222 |
|
|
223 |
|
-spec out_subscription(Acc:: mongoose_acc:t(), |
224 |
|
FromJID :: jid:jid(), |
225 |
|
ToJID ::jid:jid(), |
226 |
|
Type :: mod_roster:sub_presence()) -> |
227 |
|
mongoose_acc:t() | {stop, mongoose_acc:t()}. |
228 |
|
out_subscription(Acc, FromJID, ToJID, Type) -> |
229 |
:-( |
case process_subscription(out, FromJID, ToJID, Type) of |
230 |
|
stop -> |
231 |
:-( |
{stop, Acc}; |
232 |
|
{stop, false} -> |
233 |
:-( |
{stop, Acc}; |
234 |
:-( |
false -> Acc |
235 |
|
end. |
236 |
|
|
237 |
|
process_subscription(Direction, #jid{luser = LUser, lserver = LServer}, ToJID, _Type) -> |
238 |
:-( |
US = {LUser, LServer}, |
239 |
:-( |
{U1, S1, _} = jid:to_lower(jid:to_bare(ToJID)), |
240 |
:-( |
US1 = {U1, S1}, |
241 |
:-( |
DisplayedGroups = get_user_displayed_groups(US), |
242 |
:-( |
SRUsers = lists:usort(lists:flatmap( |
243 |
|
fun (Group) -> |
244 |
:-( |
get_group_users(LServer, Group) |
245 |
|
end, |
246 |
|
DisplayedGroups)), |
247 |
:-( |
case lists:member(US1, SRUsers) of |
248 |
|
true -> |
249 |
:-( |
case Direction of |
250 |
:-( |
in -> {stop, false}; |
251 |
:-( |
out -> stop |
252 |
|
end; |
253 |
:-( |
false -> false |
254 |
|
end. |
255 |
|
|
256 |
|
|
257 |
|
%%==================================================================== |
258 |
|
%% gen_server callbacks |
259 |
|
%%==================================================================== |
260 |
|
init([Host, Opts]) -> |
261 |
:-( |
State = parse_options(Host, Opts), |
262 |
:-( |
process_flag(trap_exit, true), |
263 |
:-( |
cache_tab:new(shared_roster_ldap_user, |
264 |
|
[{max_size, State#state.user_cache_size}, {lru, false}, |
265 |
|
{life_time, State#state.user_cache_validity}]), |
266 |
:-( |
cache_tab:new(shared_roster_ldap_group, |
267 |
|
[{max_size, State#state.group_cache_size}, {lru, false}, |
268 |
|
{life_time, State#state.group_cache_validity}]), |
269 |
:-( |
ejabberd_hooks:add(roster_get, Host, ?MODULE, |
270 |
|
get_user_roster, 70), |
271 |
:-( |
ejabberd_hooks:add(roster_in_subscription, Host, ?MODULE, |
272 |
|
in_subscription, 30), |
273 |
:-( |
ejabberd_hooks:add(roster_out_subscription, Host, ?MODULE, |
274 |
|
out_subscription, 30), |
275 |
:-( |
ejabberd_hooks:add(roster_get_subscription_lists, Host, ?MODULE, |
276 |
|
get_subscription_lists, 70), |
277 |
:-( |
ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE, |
278 |
|
get_jid_info, 70), |
279 |
:-( |
ejabberd_hooks:add(roster_process_item, Host, ?MODULE, |
280 |
|
process_item, 50), |
281 |
:-( |
{ok, State}. |
282 |
|
|
283 |
|
handle_call(get_state, _From, State) -> |
284 |
:-( |
{reply, {ok, State}, State}; |
285 |
|
handle_call(_Request, _From, State) -> |
286 |
:-( |
{reply, {error, badarg}, State}. |
287 |
|
|
288 |
:-( |
handle_cast(_Msg, State) -> {noreply, State}. |
289 |
|
|
290 |
:-( |
handle_info(_Info, State) -> {noreply, State}. |
291 |
|
|
292 |
|
terminate(_Reason, State) -> |
293 |
:-( |
Host = State#state.host, |
294 |
:-( |
ejabberd_hooks:delete(roster_get, Host, ?MODULE, |
295 |
|
get_user_roster, 70), |
296 |
:-( |
ejabberd_hooks:delete(roster_in_subscription, Host, |
297 |
|
?MODULE, in_subscription, 30), |
298 |
:-( |
ejabberd_hooks:delete(roster_out_subscription, Host, |
299 |
|
?MODULE, out_subscription, 30), |
300 |
:-( |
ejabberd_hooks:delete(roster_get_subscription_lists, |
301 |
|
Host, ?MODULE, get_subscription_lists, 70), |
302 |
:-( |
ejabberd_hooks:delete(roster_get_jid_info, Host, |
303 |
|
?MODULE, get_jid_info, 70), |
304 |
:-( |
ejabberd_hooks:delete(roster_process_item, Host, |
305 |
|
?MODULE, process_item, 50). |
306 |
|
|
307 |
:-( |
code_change(_OldVsn, State, _Extra) -> {ok, State}. |
308 |
|
|
309 |
|
%%-------------------------------------------------------------------- |
310 |
|
%%% Internal functions |
311 |
|
%%-------------------------------------------------------------------- |
312 |
|
%% For a given user, map all his shared roster contacts to groups they are |
313 |
|
%% members of. Skip the user himself iff SkipUS is true. |
314 |
|
get_user_to_groups_map({_, Server} = US, SkipUS) -> |
315 |
:-( |
DisplayedGroups = get_user_displayed_groups(US), |
316 |
|
%% Pass given FilterParseArgs to eldap_filter:parse, and if successful, run and |
317 |
|
%% return the resulting filter, retrieving given AttributesList. Return the |
318 |
|
%% result entries. On any error silently return an empty list of results. |
319 |
|
%% |
320 |
|
%% Eldap server ID and base DN for the query are both retrieved from the State |
321 |
|
%% record. |
322 |
:-( |
lists:foldl(fun (Group, Dict1) -> |
323 |
:-( |
GroupName = get_group_name(Server, Group), |
324 |
:-( |
lists:foldl(fun (Contact, Dict) when SkipUS, Contact == US -> |
325 |
:-( |
Dict; |
326 |
|
(Contact, Dict) -> |
327 |
:-( |
dict:append(Contact, GroupName, Dict) |
328 |
|
end, |
329 |
|
Dict1, get_group_users(Server, Group)) |
330 |
|
end, |
331 |
|
dict:new(), DisplayedGroups). |
332 |
|
|
333 |
|
eldap_search(State, FilterParseArgs, AttributesList) -> |
334 |
:-( |
case apply(eldap_filter, parse, FilterParseArgs) of |
335 |
|
{ok, EldapFilter} -> |
336 |
:-( |
SearchOpts = search_opts(EldapFilter, AttributesList, State), |
337 |
:-( |
case eldap_pool:search(State#state.eldap_id, SearchOpts) of |
338 |
|
#eldap_search_result{entries = Es} -> |
339 |
|
%% A result with entries. Return their list. |
340 |
:-( |
Es; |
341 |
|
_ -> |
342 |
|
%% Something else. Pretend we got no results. |
343 |
:-( |
[] |
344 |
|
end; |
345 |
|
_-> |
346 |
|
%% Filter parsing failed. Pretend we got no results. |
347 |
:-( |
[] |
348 |
|
end. |
349 |
|
|
350 |
|
search_opts(EldapFilter, AttributesList, State) -> |
351 |
:-( |
[{base, State#state.base}, |
352 |
|
{filter, EldapFilter}, |
353 |
|
{timeout, ?LDAP_SEARCH_TIMEOUT}, |
354 |
|
{deref, State#state.deref}, |
355 |
|
{attributes, AttributesList}]. |
356 |
|
|
357 |
|
get_user_displayed_groups({User, Host}) -> |
358 |
:-( |
{ok, State} = eldap_utils:get_state(Host, ?MODULE), |
359 |
:-( |
GroupAttr = State#state.group_attr, |
360 |
:-( |
Entries = eldap_search(State, |
361 |
|
[eldap_filter:do_sub(State#state.rfilter, [{<<"%u">>, User}])], |
362 |
|
[GroupAttr]), |
363 |
:-( |
Reply = lists:flatmap(fun (#eldap_entry{attributes = Attrs}) -> |
364 |
:-( |
case eldap_utils:singleton_value(Attrs) of |
365 |
:-( |
{GroupAttr, Value} -> [eldap_utils:maybe_list2b(Value)]; |
366 |
:-( |
_ -> [] |
367 |
|
end |
368 |
|
end, |
369 |
|
Entries), |
370 |
:-( |
lists:usort(Reply). |
371 |
|
|
372 |
|
get_group_users(Host, Group) -> |
373 |
:-( |
{ok, State} = eldap_utils:get_state(Host, ?MODULE), |
374 |
:-( |
case cache_tab:dirty_lookup(shared_roster_ldap_group, |
375 |
|
{Group, Host}, |
376 |
:-( |
fun () -> search_group_info(State, Group) end) |
377 |
|
of |
378 |
|
{ok, #group_info{members = Members}} |
379 |
|
when Members /= undefined -> |
380 |
:-( |
Members; |
381 |
:-( |
_ -> [] |
382 |
|
end. |
383 |
|
|
384 |
|
get_group_name(Host, Group) -> |
385 |
:-( |
{ok, State} = eldap_utils:get_state(Host, ?MODULE), |
386 |
:-( |
case cache_tab:dirty_lookup(shared_roster_ldap_group, |
387 |
|
{Group, Host}, |
388 |
:-( |
fun () -> search_group_info(State, Group) end) |
389 |
|
of |
390 |
|
{ok, #group_info{desc = GroupName}} |
391 |
|
when GroupName /= undefined -> |
392 |
:-( |
GroupName; |
393 |
:-( |
_ -> Group |
394 |
|
end. |
395 |
|
|
396 |
|
get_user_name(User, Host) -> |
397 |
:-( |
{ok, State} = eldap_utils:get_state(Host, ?MODULE), |
398 |
:-( |
case cache_tab:dirty_lookup(shared_roster_ldap_user, |
399 |
|
{User, Host}, |
400 |
:-( |
fun () -> search_user_name(State, User) end) |
401 |
|
of |
402 |
:-( |
{ok, UserName} -> UserName; |
403 |
:-( |
error -> User |
404 |
|
end. |
405 |
|
|
406 |
|
search_group_info(State, Group) -> |
407 |
:-( |
Extractor = case State#state.uid_format_re of |
408 |
|
<<"">> -> |
409 |
:-( |
fun (UID) -> |
410 |
:-( |
catch eldap_utils:get_user_part( |
411 |
|
UID, |
412 |
|
State#state.uid_format) |
413 |
|
end; |
414 |
|
_ -> |
415 |
:-( |
fun (UID) -> |
416 |
:-( |
catch get_user_part_re( |
417 |
|
UID, |
418 |
|
State#state.uid_format_re) |
419 |
|
end |
420 |
|
end, |
421 |
:-( |
AuthChecker = case State#state.auth_check of |
422 |
:-( |
true -> fun ejabberd_auth:does_user_exist/1; |
423 |
:-( |
_ -> fun (_U, _S) -> true end |
424 |
|
end, |
425 |
:-( |
Host = State#state.host, |
426 |
:-( |
case eldap_search(State, |
427 |
|
[eldap_filter:do_sub(State#state.gfilter, |
428 |
|
[{<<"%g">>, Group}])], |
429 |
|
[State#state.group_attr, State#state.group_desc, |
430 |
|
State#state.uid]) of |
431 |
|
[] -> |
432 |
:-( |
error; |
433 |
|
LDAPEntries -> |
434 |
:-( |
{GroupDesc, MembersLists} = ldap_entries_to_group(LDAPEntries, Host, Group, State, |
435 |
|
Extractor, AuthChecker), |
436 |
:-( |
{ok, #group_info{desc = GroupDesc, members = lists:usort(MembersLists)}} |
437 |
|
end. |
438 |
|
|
439 |
|
ldap_entries_to_group(LDAPEntries, Host, Group, State, Extractor, AuthChecker) -> |
440 |
:-( |
ldap_entries_to_group(LDAPEntries, Host, Group, [], State, Extractor, AuthChecker). |
441 |
|
|
442 |
|
ldap_entries_to_group([#eldap_entry{ attributes = Attrs } | REntries], Host, |
443 |
|
DescAcc, JIDsAcc, State, Extractor, AuthChecker) -> |
444 |
:-( |
UID = lists:keysearch(State#state.uid, 1, Attrs), |
445 |
:-( |
ListUID = State#state.uid, |
446 |
:-( |
case {eldap_utils:get_ldap_attr(State#state.group_attr, Attrs), |
447 |
|
eldap_utils:get_ldap_attr(State#state.group_desc, Attrs), UID} of |
448 |
|
{ID, Desc, {value, {GroupMemberAttr, MemberIn}}} |
449 |
|
when ID /= <<"">>, GroupMemberAttr == ListUID -> |
450 |
:-( |
Member = case MemberIn of |
451 |
:-( |
[M] -> M; |
452 |
:-( |
_ -> MemberIn |
453 |
|
end, |
454 |
:-( |
Extracted = Extractor(eldap_utils:maybe_list2b(Member)), |
455 |
:-( |
NewJIDsAcc = check_and_accumulate_member(Extracted, AuthChecker, Host, JIDsAcc), |
456 |
:-( |
ldap_entries_to_group(REntries, Host, Desc, NewJIDsAcc, State, Extractor, AuthChecker); |
457 |
|
_ -> |
458 |
:-( |
ldap_entries_to_group(REntries, Host, DescAcc, JIDsAcc, State, Extractor, AuthChecker) |
459 |
|
end; |
460 |
|
ldap_entries_to_group([], _Host, DescAcc, JIDsAcc, _State, _Extractor, _AuthChecker) -> |
461 |
:-( |
{DescAcc, JIDsAcc}. |
462 |
|
|
463 |
|
check_and_accumulate_member({ok, UID}, AuthChecker, Host, JIDsAcc) -> |
464 |
:-( |
PUID = jid:nodeprep(UID), |
465 |
:-( |
case PUID of |
466 |
|
error -> |
467 |
:-( |
JIDsAcc; |
468 |
|
_ -> |
469 |
:-( |
JID = jid:make(PUID, Host, <<>>), |
470 |
:-( |
case AuthChecker(JID) of |
471 |
|
true -> |
472 |
:-( |
[{PUID, Host} | JIDsAcc]; |
473 |
|
_ -> |
474 |
:-( |
JIDsAcc |
475 |
|
end |
476 |
|
end; |
477 |
|
check_and_accumulate_member(_, _AuthChecker, _Host, JIDsAcc) -> |
478 |
:-( |
JIDsAcc. |
479 |
|
|
480 |
|
search_user_name(State, User) -> |
481 |
:-( |
case eldap_search(State, |
482 |
|
[eldap_filter:do_sub(State#state.ufilter, |
483 |
|
[{<<"%u">>, User}])], |
484 |
|
[State#state.user_desc, State#state.user_uid]) |
485 |
|
of |
486 |
|
[#eldap_entry{attributes = Attrs} | _] -> |
487 |
:-( |
case {eldap_utils:get_ldap_attr(State#state.user_uid, Attrs), |
488 |
|
eldap_utils:get_ldap_attr(State#state.user_desc, Attrs)} |
489 |
|
of |
490 |
:-( |
{UID, Desc} when UID /= <<"">> -> {ok, Desc}; |
491 |
:-( |
_ -> error |
492 |
|
end; |
493 |
:-( |
[] -> error |
494 |
|
end. |
495 |
|
|
496 |
|
%% Getting User ID part by regex pattern |
497 |
|
get_user_part_re(String, Pattern) -> |
498 |
:-( |
case catch re:run(String, Pattern) of |
499 |
|
{match, Captured} -> |
500 |
:-( |
{First, Len} = lists:nth(2, Captured), |
501 |
:-( |
Result = binary:part(String, First, Len), |
502 |
:-( |
{ok, Result}; |
503 |
:-( |
_ -> {error, badmatch} |
504 |
|
end. |
505 |
|
|
506 |
|
parse_options(Host, Opts) -> |
507 |
:-( |
EldapID = eldap_utils:get_mod_opt(ldap_pool_tag, Opts, |
508 |
:-( |
fun(A) when is_atom(A) -> A end, default), |
509 |
:-( |
Base = eldap_utils:get_base(Opts), |
510 |
:-( |
DerefAliases = eldap_utils:get_deref_aliases(Opts), |
511 |
:-( |
GroupAttr = eldap_utils:get_mod_opt(ldap_groupattr, Opts, |
512 |
|
fun iolist_to_binary/1, |
513 |
|
<<"cn">>), |
514 |
:-( |
GroupDesc = eldap_utils:get_mod_opt(ldap_groupdesc, Opts, |
515 |
|
fun iolist_to_binary/1, |
516 |
|
GroupAttr), |
517 |
:-( |
UserDesc = eldap_utils:get_mod_opt(ldap_userdesc, Opts, |
518 |
|
fun iolist_to_binary/1, |
519 |
|
<<"cn">>), |
520 |
:-( |
UserUID = eldap_utils:get_mod_opt(ldap_useruid, Opts, |
521 |
|
fun iolist_to_binary/1, |
522 |
|
<<"cn">>), |
523 |
:-( |
UIDAttr = eldap_utils:get_mod_opt(ldap_memberattr, Opts, |
524 |
|
fun iolist_to_binary/1, |
525 |
|
<<"memberUid">>), |
526 |
:-( |
UIDAttrFormat = eldap_utils:get_mod_opt(ldap_memberattr_format, Opts, |
527 |
|
fun iolist_to_binary/1, |
528 |
|
<<"%u">>), |
529 |
:-( |
UIDAttrFormatRe = eldap_utils:get_mod_opt(ldap_memberattr_format_re, Opts, |
530 |
|
fun(S) -> |
531 |
:-( |
Re = iolist_to_binary(S), |
532 |
:-( |
{ok, MP} = re:compile(Re), |
533 |
:-( |
MP |
534 |
|
end, <<"">>), |
535 |
:-( |
AuthCheck = eldap_utils:get_mod_opt(ldap_auth_check, Opts, |
536 |
:-( |
fun(false) -> false; |
537 |
:-( |
(true) -> true |
538 |
|
end, true), |
539 |
:-( |
UserCacheValidity = eldap_utils:get_mod_opt( |
540 |
|
ldap_user_cache_validity, Opts, |
541 |
:-( |
fun(I) when is_integer(I), I>0 -> I end, |
542 |
|
?USER_CACHE_VALIDITY), |
543 |
:-( |
GroupCacheValidity = eldap_utils:get_mod_opt( |
544 |
|
ldap_group_cache_validity, Opts, |
545 |
:-( |
fun(I) when is_integer(I), I>0 -> I end, |
546 |
|
?GROUP_CACHE_VALIDITY), |
547 |
:-( |
UserCacheSize = eldap_utils:get_mod_opt( |
548 |
|
ldap_user_cache_size, Opts, |
549 |
:-( |
fun(I) when is_integer(I), I>0 -> I end, |
550 |
|
?CACHE_SIZE), |
551 |
:-( |
GroupCacheSize = eldap_utils:get_mod_opt( |
552 |
|
ldap_group_cache_size, Opts, |
553 |
:-( |
fun(I) when is_integer(I), I>0 -> I end, |
554 |
|
?CACHE_SIZE), |
555 |
:-( |
ConfigFilter = eldap_utils:get_mod_opt(ldap_filter, Opts, |
556 |
|
fun check_filter/1, <<"">>), |
557 |
:-( |
ConfigUserFilter = eldap_utils:get_mod_opt(ldap_ufilter, Opts, |
558 |
|
fun check_filter/1, <<"">>), |
559 |
:-( |
ConfigGroupFilter = eldap_utils:get_mod_opt(ldap_gfilter, Opts, |
560 |
|
fun check_filter/1, <<"">>), |
561 |
:-( |
RosterFilter = eldap_utils:get_mod_opt(ldap_rfilter, Opts, |
562 |
|
fun check_filter/1, <<"">>), |
563 |
:-( |
SubFilter = <<"(&(", UIDAttr/binary, "=", UIDAttrFormat/binary, |
564 |
|
")(", GroupAttr/binary, "=%g))">>, |
565 |
:-( |
UserSubFilter = case ConfigUserFilter of |
566 |
|
<<"">> -> |
567 |
:-( |
eldap_filter:do_sub(SubFilter, [{<<"%g">>, <<"*">>}]); |
568 |
:-( |
UString -> UString |
569 |
|
end, |
570 |
:-( |
GroupSubFilter = case ConfigGroupFilter of |
571 |
|
<<"">> -> |
572 |
:-( |
eldap_filter:do_sub(SubFilter, [{<<"%u">>, <<"*">>}]); |
573 |
:-( |
GString -> GString |
574 |
|
end, |
575 |
:-( |
Filter = case ConfigFilter of |
576 |
:-( |
<<"">> -> SubFilter; |
577 |
|
_ -> |
578 |
:-( |
<<"(&", SubFilter/binary, ConfigFilter/binary, ")">> |
579 |
|
end, |
580 |
:-( |
UserFilter = case ConfigFilter of |
581 |
:-( |
<<"">> -> UserSubFilter; |
582 |
|
_ -> |
583 |
:-( |
<<"(&", UserSubFilter/binary, ConfigFilter/binary, ")">> |
584 |
|
end, |
585 |
:-( |
GroupFilter = case ConfigFilter of |
586 |
:-( |
<<"">> -> GroupSubFilter; |
587 |
|
_ -> |
588 |
:-( |
<<"(&", GroupSubFilter/binary, ConfigFilter/binary, ")">> |
589 |
|
end, |
590 |
:-( |
#state{host = Host, |
591 |
|
eldap_id = {Host, EldapID}, |
592 |
|
base = Base, |
593 |
|
deref = DerefAliases, |
594 |
|
uid = UIDAttr, |
595 |
|
group_attr = GroupAttr, group_desc = GroupDesc, |
596 |
|
user_desc = UserDesc, user_uid = UserUID, |
597 |
|
uid_format = UIDAttrFormat, |
598 |
|
uid_format_re = UIDAttrFormatRe, filter = Filter, |
599 |
|
ufilter = UserFilter, rfilter = RosterFilter, |
600 |
|
gfilter = GroupFilter, auth_check = AuthCheck, |
601 |
|
user_cache_size = UserCacheSize, |
602 |
|
user_cache_validity = UserCacheValidity, |
603 |
|
group_cache_size = GroupCacheSize, |
604 |
|
group_cache_validity = GroupCacheValidity}. |
605 |
|
|
606 |
|
check_filter(F) -> |
607 |
:-( |
NewF = iolist_to_binary(F), |
608 |
:-( |
{ok, _} = eldap_filter:parse(NewF), |
609 |
:-( |
NewF. |