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