./ct_report/coverage/mod_vcard_ldap.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_vcard_ldap.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Support for VCards from LDAP storage.
5 %%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2013 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 %%TODO gen_server is created only to store the state
27 %% and create/destroy pool, should it be replaced ?
28
29 -module(mod_vcard_ldap).
30 -author('alexey@process-one.net').
31
32 -behaviour(mod_vcard_backend).
33
34 %% mod_vcards callbacks
35 -export([init/2,
36 tear_down/1,
37 remove_user/3,
38 get_vcard/3,
39 set_vcard/5,
40 search/3,
41 search_fields/2,
42 search_reported_fields/3]).
43
44 -include("eldap.hrl").
45 -include("mod_vcard.hrl").
46 -include("jlib.hrl").
47
48 -record(state,
49 {serverhost = <<>> :: binary(),
50 myhost = <<>> :: binary(),
51 eldap_id :: eldap_utils:eldap_id(),
52 base = <<>> :: binary(),
53 password = <<>> :: binary(),
54 uids = [] :: [{binary()} | {binary(), binary()}],
55 vcard_map = [] :: [{binary(), binary(), [binary()]}],
56 vcard_map_attrs = [] :: [binary()],
57 user_filter = <<>> :: binary(),
58 search_filter :: eldap:filter(),
59 search_fields = [] :: [{binary(), binary()}],
60 search_reported = [] :: [{binary(), binary()}],
61 search_reported_attrs = [] :: [binary()],
62 search_operator :: 'or' | 'and',
63 binary_search_fields :: [binary()],
64 deref = neverDerefAliases :: eldap_utils:deref(),
65 matches = 0 :: non_neg_integer() | infinity}).
66
67 -define(VCARD_MAP,
68 [{<<"NICKNAME">>, <<"%u">>, []},
69 {<<"FN">>, <<"%s">>, [<<"displayName">>]},
70 {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
71 {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]},
72 {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]},
73 {<<"ORGNAME">>, <<"%s">>, [<<"o">>]},
74 {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]},
75 {<<"CTRY">>, <<"%s">>, [<<"c">>]},
76 {<<"LOCALITY">>, <<"%s">>, [<<"l">>]},
77 {<<"STREET">>, <<"%s">>, [<<"street">>]},
78 {<<"REGION">>, <<"%s">>, [<<"st">>]},
79 {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]},
80 {<<"TITLE">>, <<"%s">>, [<<"title">>]},
81 {<<"URL">>, <<"%s">>, [<<"labeleduri">>]},
82 {<<"DESC">>, <<"%s">>, [<<"description">>]},
83 {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]},
84 {<<"EMAIL">>, <<"%s">>, [<<"mail">>]},
85 {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]},
86 {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]},
87 {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}]).
88
89 -define(SEARCH_FIELDS,
90 [{<<"User">>, <<"%u">>},
91 {<<"Full Name">>, <<"displayName">>},
92 {<<"Given Name">>, <<"givenName">>},
93 {<<"Middle Name">>, <<"initials">>},
94 {<<"Family Name">>, <<"sn">>},
95 {<<"Nickname">>, <<"%u">>},
96 {<<"Birthday">>, <<"birthDay">>},
97 {<<"Country">>, <<"c">>}, {<<"City">>, <<"l">>},
98 {<<"Email">>, <<"mail">>},
99 {<<"Organization Name">>, <<"o">>},
100 {<<"Organization Unit">>, <<"ou">>}]).
101
102 -define(SEARCH_REPORTED,
103 [{<<"Full Name">>, <<"FN">>},
104 {<<"Given Name">>, <<"FIRST">>},
105 {<<"Middle Name">>, <<"MIDDLE">>},
106 {<<"Family Name">>, <<"LAST">>},
107 {<<"Nickname">>, <<"NICK">>},
108 {<<"Birthday">>, <<"BDAY">>},
109 {<<"Country">>, <<"CTRY">>},
110 {<<"City">>, <<"LOCALITY">>},
111 {<<"Email">>, <<"EMAIL">>},
112 {<<"Organization Name">>, <<"ORGNAME">>},
113 {<<"Organization Unit">>, <<"ORGUNIT">>}]).
114
115
116
117 %%--------------------------------------------------------------------
118 %% mod_vcards callbacks
119 %%--------------------------------------------------------------------
120
121 init(_HostType, _Opts) ->
122
:-(
ok.
123
124 tear_down(_HostType) ->
125
:-(
ok.
126
127 remove_user(_HostType, _LUser, _LServer) ->
128 %% no need to handle this - in ldap
129 %% removing user = delete all user info
130
:-(
ok.
131
132 get_vcard(HostType, LUser, LServer) ->
133
:-(
State = get_state(HostType, LServer),
134
:-(
LServer = State#state.serverhost,
135
:-(
JID = jid:make(LUser, LServer, <<>>),
136
:-(
case ejabberd_auth:does_user_exist(JID) of
137 true ->
138
:-(
VCardMap = State#state.vcard_map,
139
:-(
case find_ldap_user(LUser, State) of
140 #eldap_entry{attributes = Attributes} ->
141
:-(
Vcard = ldap_attributes_to_vcard(Attributes, VCardMap, {LUser, LServer}),
142
:-(
{ok, Vcard};
143 _ ->
144
:-(
{ok, []}
145 end;
146 _ ->
147
:-(
{ok, []}
148 end.
149
150 set_vcard(_HostType, _User, _LServer, _VCard, _VCardSearch) ->
151
:-(
{error, mongoose_xmpp_errors:not_allowed()}.
152
153 search(HostType, LServer, Data) ->
154
:-(
State = get_state(HostType, LServer),
155
:-(
search_internal(State, Data).
156
157 search_fields(HostType, LServer) ->
158
:-(
State = get_state(HostType, LServer),
159
:-(
State#state.search_fields.
160
161 search_reported_fields(HostType, LServer, Lang) ->
162
:-(
State = get_state(HostType, LServer),
163
:-(
SearchReported = State#state.search_reported,
164
:-(
#xmlel{name = <<"reported">>, attrs = [],
165 children =
166 [?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
167 <<"jid">>)]
168 ++
169 lists:map(fun ({Name, Value}) ->
170
:-(
?TLFIELD(<<"text-single">>, Name,
171 Value)
172 end,
173 SearchReported)}.
174
175 %%--------------------------------------------------------------------
176 %% Internal
177 %%--------------------------------------------------------------------
178 find_ldap_user(User, State) ->
179
:-(
Base = State#state.base,
180
:-(
RFC2254Filter = State#state.user_filter,
181
:-(
EldapID = State#state.eldap_id,
182
:-(
VCardAttrs = State#state.vcard_map_attrs,
183
:-(
case eldap_filter:parse(RFC2254Filter, [{<<"%u">>, User}]) of
184 {ok, EldapFilter} ->
185
:-(
Res = eldap_pool_search(EldapID, Base, EldapFilter, State#state.deref, VCardAttrs, false),
186
:-(
case Res of
187 [H | _] ->
188
:-(
H;
189 _ ->
190
:-(
Res
191 end;
192
:-(
_ -> false
193 end.
194
195 eldap_pool_search(EldapID, Base, EldapFilter, Deref, Attrs, NoResultRes) ->
196
:-(
SearchOpts = search_opts(Base, EldapFilter, Deref, Attrs),
197
:-(
case eldap_pool:search(EldapID, SearchOpts) of
198
:-(
#eldap_search_result{entries = E} -> E;
199
:-(
_ -> NoResultRes
200 end.
201
202 search_opts(Base, EldapFilter, Deref, Attrs) ->
203
:-(
[{base, Base}, {filter, EldapFilter},
204 {deref, Deref}, {attributes, Attrs}].
205
206 ldap_attributes_to_vcard(Attributes, VCardMap, UD) ->
207
:-(
Attrs = lists:map(fun ({VCardName, _, _}) ->
208
:-(
{jid:str_tolower(VCardName),
209 map_vcard_attr(VCardName, Attributes, VCardMap,
210 UD)}
211 end,
212 VCardMap),
213
:-(
Elts = [ldap_attribute_to_vcard(vCard, Attr)
214
:-(
|| Attr <- Attrs],
215
:-(
NElts = [ldap_attribute_to_vcard(vCardN, Attr)
216
:-(
|| Attr <- Attrs],
217
:-(
OElts = [ldap_attribute_to_vcard(vCardO, Attr)
218
:-(
|| Attr <- Attrs],
219
:-(
AElts = [ldap_attribute_to_vcard(vCardA, Attr)
220
:-(
|| Attr <- Attrs],
221
:-(
[#xmlel{name = <<"vCard">>,
222 attrs = [{<<"xmlns">>, ?NS_VCARD}],
223 children =
224
:-(
lists:append([X || X <- Elts, X /= none],
225 [#xmlel{name = <<"N">>, attrs = [],
226
:-(
children = [X || X <- NElts, X /= none]},
227 #xmlel{name = <<"ORG">>, attrs = [],
228
:-(
children = [X || X <- OElts, X /= none]},
229 #xmlel{name = <<"ADR">>, attrs = [],
230 children =
231
:-(
[X || X <- AElts, X /= none]}])}].
232
233 ldap_attribute_to_vcard(vCard, {<<"fn">>, Value}) ->
234
:-(
#xmlel{name = <<"FN">>, attrs = [],
235 children = [{xmlcdata, Value}]};
236 ldap_attribute_to_vcard(vCard,
237 {<<"nickname">>, Value}) ->
238
:-(
#xmlel{name = <<"NICKNAME">>, attrs = [],
239 children = [{xmlcdata, Value}]};
240 ldap_attribute_to_vcard(vCard, {<<"title">>, Value}) ->
241
:-(
#xmlel{name = <<"TITLE">>, attrs = [],
242 children = [{xmlcdata, Value}]};
243 ldap_attribute_to_vcard(vCard, {<<"bday">>, Value}) ->
244
:-(
#xmlel{name = <<"BDAY">>, attrs = [],
245 children = [{xmlcdata, Value}]};
246 ldap_attribute_to_vcard(vCard, {<<"url">>, Value}) ->
247
:-(
#xmlel{name = <<"URL">>, attrs = [],
248 children = [{xmlcdata, Value}]};
249 ldap_attribute_to_vcard(vCard, {<<"desc">>, Value}) ->
250
:-(
#xmlel{name = <<"DESC">>, attrs = [],
251 children = [{xmlcdata, Value}]};
252 ldap_attribute_to_vcard(vCard, {<<"role">>, Value}) ->
253
:-(
#xmlel{name = <<"ROLE">>, attrs = [],
254 children = [{xmlcdata, Value}]};
255 ldap_attribute_to_vcard(vCard, {<<"tel">>, Value}) ->
256
:-(
#xmlel{name = <<"TEL">>, attrs = [],
257 children =
258 [#xmlel{name = <<"VOICE">>, attrs = [], children = []},
259 #xmlel{name = <<"WORK">>, attrs = [], children = []},
260 #xmlel{name = <<"NUMBER">>, attrs = [],
261 children = [{xmlcdata, Value}]}]};
262 ldap_attribute_to_vcard(vCard, {<<"email">>, Value}) ->
263
:-(
#xmlel{name = <<"EMAIL">>, attrs = [],
264 children =
265 [#xmlel{name = <<"INTERNET">>, attrs = [],
266 children = []},
267 #xmlel{name = <<"PREF">>, attrs = [], children = []},
268 #xmlel{name = <<"USERID">>, attrs = [],
269 children = [{xmlcdata, Value}]}]};
270 ldap_attribute_to_vcard(vCard, {<<"photo">>, Value}) ->
271
:-(
#xmlel{name = <<"PHOTO">>, attrs = [],
272 children =
273 [#xmlel{name = <<"TYPE">>, attrs = [],
274 children = [{xmlcdata, <<"image/jpeg">>}]},
275 #xmlel{name = <<"BINVAL">>, attrs = [],
276 children = [{xmlcdata, jlib:encode_base64(Value)}]}]};
277 ldap_attribute_to_vcard(vCardN,
278 {<<"family">>, Value}) ->
279
:-(
#xmlel{name = <<"FAMILY">>, attrs = [],
280 children = [{xmlcdata, Value}]};
281 ldap_attribute_to_vcard(vCardN, {<<"given">>, Value}) ->
282
:-(
#xmlel{name = <<"GIVEN">>, attrs = [],
283 children = [{xmlcdata, Value}]};
284 ldap_attribute_to_vcard(vCardN,
285 {<<"middle">>, Value}) ->
286
:-(
#xmlel{name = <<"MIDDLE">>, attrs = [],
287 children = [{xmlcdata, Value}]};
288 ldap_attribute_to_vcard(vCardO,
289 {<<"orgname">>, Value}) ->
290
:-(
#xmlel{name = <<"ORGNAME">>, attrs = [],
291 children = [{xmlcdata, Value}]};
292 ldap_attribute_to_vcard(vCardO,
293 {<<"orgunit">>, Value}) ->
294
:-(
#xmlel{name = <<"ORGUNIT">>, attrs = [],
295 children = [{xmlcdata, Value}]};
296 ldap_attribute_to_vcard(vCardA,
297 {<<"locality">>, Value}) ->
298
:-(
#xmlel{name = <<"LOCALITY">>, attrs = [],
299 children = [{xmlcdata, Value}]};
300 ldap_attribute_to_vcard(vCardA,
301 {<<"street">>, Value}) ->
302
:-(
#xmlel{name = <<"STREET">>, attrs = [],
303 children = [{xmlcdata, Value}]};
304 ldap_attribute_to_vcard(vCardA, {<<"ctry">>, Value}) ->
305
:-(
#xmlel{name = <<"CTRY">>, attrs = [],
306 children = [{xmlcdata, Value}]};
307 ldap_attribute_to_vcard(vCardA,
308 {<<"region">>, Value}) ->
309
:-(
#xmlel{name = <<"REGION">>, attrs = [],
310 children = [{xmlcdata, Value}]};
311 ldap_attribute_to_vcard(vCardA, {<<"pcode">>, Value}) ->
312
:-(
#xmlel{name = <<"PCODE">>, attrs = [],
313 children = [{xmlcdata, Value}]};
314
:-(
ldap_attribute_to_vcard(_, _) -> none.
315
316 search_internal(_, []) ->
317
:-(
[];
318 search_internal(State, Data) ->
319
:-(
Base = State#state.base,
320
:-(
SearchFilter = State#state.search_filter,
321
:-(
EldapID = State#state.eldap_id,
322
:-(
UIDs = State#state.uids,
323
:-(
Limit = State#state.matches,
324
:-(
ReportedAttrs = State#state.search_reported_attrs,
325
:-(
Op = State#state.search_operator,
326
:-(
Filter = eldap:'and'([SearchFilter, eldap_utils:make_filter(Data, UIDs, Op)]),
327
:-(
E = eldap_pool_search(EldapID, Base, Filter, State#state.deref, ReportedAttrs, error),
328
:-(
case E of
329 error ->
330
:-(
error;
331 E ->
332
:-(
Limited = limited_results(E, Limit),
333
:-(
search_items(Limited, State)
334 end.
335
336 limited_results(E, Limit) when length(E) > Limit ->
337
:-(
lists:sublist(E, Limit);
338 limited_results(E, _) when not is_list(E) ->
339
:-(
[E];
340 limited_results(E, _) ->
341
:-(
E.
342
343 search_items(Entries, State) ->
344
:-(
lists:flatmap(fun(#eldap_entry{attributes = Attrs}) -> attrs_to_item_xml(Attrs, State) end,
345 Entries).
346
347 attrs_to_item_xml(Attrs, #state{uids = UIDs} = State) ->
348
:-(
case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
349 {U, UIDAttrFormat} ->
350
:-(
case eldap_utils:get_user_part(U, UIDAttrFormat) of
351 {ok, Username} ->
352
:-(
make_user_item_if_exists(Username, Attrs, State);
353
:-(
_ -> []
354 end;
355
:-(
<<"">> -> []
356 end.
357
358 make_user_item_if_exists(Username, Attrs,
359 #state{serverhost = LServer, search_reported = SearchReported,
360 vcard_map = VCardMap, binary_search_fields = BinFields}) ->
361
:-(
JID = jid:make(Username, LServer, <<>>),
362
:-(
case ejabberd_auth:does_user_exist(JID) of
363 true ->
364
:-(
RFields = lists:map(fun ({_, VCardName}) ->
365
:-(
{VCardName, map_vcard_attr(VCardName, Attrs, VCardMap,
366 {Username, LServer})}
367 end,
368 SearchReported),
369
:-(
Result = [?FIELD(<<"jid">>, <<Username/binary, "@", LServer/binary>>)] ++
370
:-(
[?FIELD(Name, search_item_value(Name, Value, BinFields)) || {Name, Value} <- RFields],
371
:-(
[#xmlel{name = <<"item">>, attrs = [], children = Result}];
372
:-(
_ -> []
373 end.
374
375 %%%-----------------------
376 %%% Auxiliary functions.
377 %%%-----------------------
378 search_item_value(Name, Value, BinaryFields) ->
379
:-(
case lists:member(Name, BinaryFields) of
380
:-(
true -> jlib:encode_base64(Value);
381
:-(
false -> Value
382 end.
383
384 map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
385
:-(
Res = lists:filter(fun ({Name, _, _}) ->
386
:-(
eldap_utils:case_insensitive_match(Name,
387 VCardName)
388 end,
389 Pattern),
390
:-(
case Res of
391 [{_, Str, Attrs}] ->
392
:-(
process_pattern(Str, UD,
393
:-(
[eldap_utils:get_ldap_attr(X, Attributes)
394
:-(
|| X <- Attrs]);
395
:-(
_ -> <<"">>
396 end.
397
398 process_pattern(Str, {User, Domain}, AttrValues) ->
399
:-(
eldap_filter:do_sub(Str,
400 [{<<"%u">>, User}, {<<"%d">>, Domain}] ++
401
:-(
[{<<"%s">>, V, 1} || V <- AttrValues]).
402
403 get_state(HostType, LServer) ->
404
:-(
Opts = gen_mod:get_loaded_module_opts(HostType, mod_vcard),
405
:-(
MyHost = gen_mod:get_opt_subhost(LServer, Opts, mod_vcard:default_host()),
406
:-(
Matches = eldap_utils:get_mod_opt(matches, Opts,
407
:-(
fun(infinity) -> infinity;
408
:-(
(I) when is_integer(I), I>=0 -> I
409 end, 30),
410
:-(
EldapID = eldap_utils:get_mod_opt(ldap_pool_tag, Opts,
411
:-(
fun(A) when is_atom(A) -> A end, default),
412
:-(
Base = eldap_utils:get_base(Opts),
413
:-(
DerefAliases = eldap_utils:get_deref_aliases(Opts),
414
:-(
UIDs = eldap_utils:get_uids(LServer, Opts),
415
:-(
UserFilter = eldap_utils:get_user_filter(UIDs, Opts),
416
:-(
{ok, SearchFilter} =
417 eldap_filter:parse(eldap_utils:get_search_filter(UserFilter)),
418
:-(
VCardMap = eldap_utils:get_mod_opt(ldap_vcard_map, Opts,
419 fun(Ls) ->
420
:-(
lists:map(
421 fun({S, P, L}) ->
422
:-(
{iolist_to_binary(S),
423 iolist_to_binary(P),
424
:-(
[iolist_to_binary(E)
425
:-(
|| E <- L]}
426 end, Ls)
427 end, ?VCARD_MAP),
428
:-(
SearchFields = eldap_utils:get_mod_opt(ldap_search_fields, Opts,
429 fun(Ls) ->
430
:-(
[{iolist_to_binary(S),
431 iolist_to_binary(P)}
432
:-(
|| {S, P} <- Ls]
433 end, ?SEARCH_FIELDS),
434
:-(
SearchReported = eldap_utils:get_mod_opt(ldap_search_reported, Opts,
435 fun(Ls) ->
436
:-(
[{iolist_to_binary(S),
437 iolist_to_binary(P)}
438
:-(
|| {S, P} <- Ls]
439 end, ?SEARCH_REPORTED),
440
:-(
UIDAttrs = lists:map(fun({UID, _}) -> UID;
441
:-(
({UID}) -> UID end, UIDs),
442
:-(
VCardMapAttrs = lists:usort(lists:append([A
443
:-(
|| {_, _, A} <- VCardMap])
444 ++ UIDAttrs),
445
:-(
SearchReportedAttrs = lists:usort(lists:flatmap(
446 fun ({_, N}) ->
447
:-(
case lists:keysearch(N, 1, VCardMap) of
448 {value, {_, _, L}} ->
449
:-(
L;
450
:-(
_ -> []
451 end
452 end,
453 SearchReported) ++ UIDAttrs),
454
:-(
SearchOperatorFun = fun
455
:-(
('or') -> 'or';
456
:-(
(_) -> 'and'
457 end,
458
:-(
SearchOperator = eldap_utils:get_mod_opt(ldap_search_operator, Opts,
459 SearchOperatorFun, 'and'),
460
:-(
BinaryFields = eldap_utils:get_mod_opt(ldap_binary_search_fields, Opts,
461
:-(
fun(X) -> X end, []),
462
:-(
#state{serverhost = LServer,
463 myhost = MyHost,
464 eldap_id = {HostType, EldapID},
465 base = Base,
466 deref = DerefAliases,
467 uids = UIDs, vcard_map = VCardMap,
468 vcard_map_attrs = VCardMapAttrs,
469 user_filter = UserFilter, search_filter = SearchFilter,
470 search_fields = SearchFields,
471 binary_search_fields = BinaryFields,
472 search_reported = SearchReported,
473 search_reported_attrs = SearchReportedAttrs,
474 search_operator = SearchOperator,
475 matches = Matches}.
Line Hits Source