./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 -module(mod_vcard_ldap).
27 -author('alexey@process-one.net').
28
29 -behaviour(mod_vcard_backend).
30
31 %% mod_vcard_backend callbacks
32 -export([init/2,
33 tear_down/1,
34 remove_user/3,
35 get_vcard/3,
36 set_vcard/5,
37 search/3,
38 search_fields/2,
39 search_reported_fields/3]).
40
41 -export([default_vcard_map/0,
42 default_search_fields/0,
43 default_search_reported/0]).
44
45 -include_lib("eldap/include/eldap.hrl").
46 -include("mod_vcard.hrl").
47 -include("jlib.hrl").
48
49 -record(state,
50 {serverhost = <<>> :: binary(),
51 myhost = <<>> :: binary(),
52 eldap_id :: eldap_utils:eldap_id(),
53 base = <<>> :: binary(),
54 password = <<>> :: binary(),
55 uids = [] :: [{binary()} | {binary(), binary()}],
56 vcard_map = [] :: [{binary(), binary(), [binary()]}],
57 vcard_map_attrs = [] :: [binary()],
58 user_filter = <<>> :: binary(),
59 search_filter :: eldap_utils:filter(),
60 search_fields = [] :: [{binary(), binary()}],
61 search_reported = [] :: [{binary(), binary()}],
62 search_reported_attrs = [] :: [binary()],
63 search_operator :: 'or' | 'and',
64 binary_search_fields :: [binary()],
65 deref = neverDerefAliases :: eldap_utils:deref(),
66 matches = 0 :: non_neg_integer() | infinity}).
67
68 -define(VCARD_MAP,
69 [{<<"NICKNAME">>, <<"%u">>, []},
70 {<<"FN">>, <<"%s">>, [<<"displayName">>]},
71 {<<"FAMILY">>, <<"%s">>, [<<"sn">>]},
72 {<<"GIVEN">>, <<"%s">>, [<<"givenName">>]},
73 {<<"MIDDLE">>, <<"%s">>, [<<"initials">>]},
74 {<<"ORGNAME">>, <<"%s">>, [<<"o">>]},
75 {<<"ORGUNIT">>, <<"%s">>, [<<"ou">>]},
76 {<<"CTRY">>, <<"%s">>, [<<"c">>]},
77 {<<"LOCALITY">>, <<"%s">>, [<<"l">>]},
78 {<<"STREET">>, <<"%s">>, [<<"street">>]},
79 {<<"REGION">>, <<"%s">>, [<<"st">>]},
80 {<<"PCODE">>, <<"%s">>, [<<"postalCode">>]},
81 {<<"TITLE">>, <<"%s">>, [<<"title">>]},
82 {<<"URL">>, <<"%s">>, [<<"labeleduri">>]},
83 {<<"DESC">>, <<"%s">>, [<<"description">>]},
84 {<<"TEL">>, <<"%s">>, [<<"telephoneNumber">>]},
85 {<<"EMAIL">>, <<"%s">>, [<<"mail">>]},
86 {<<"BDAY">>, <<"%s">>, [<<"birthDay">>]},
87 {<<"ROLE">>, <<"%s">>, [<<"employeeType">>]},
88 {<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}]).
89
90 -define(SEARCH_FIELDS,
91 [{<<"User">>, <<"%u">>},
92 {<<"Full Name">>, <<"displayName">>},
93 {<<"Given Name">>, <<"givenName">>},
94 {<<"Middle Name">>, <<"initials">>},
95 {<<"Family Name">>, <<"sn">>},
96 {<<"Nickname">>, <<"%u">>},
97 {<<"Birthday">>, <<"birthDay">>},
98 {<<"Country">>, <<"c">>}, {<<"City">>, <<"l">>},
99 {<<"Email">>, <<"mail">>},
100 {<<"Organization Name">>, <<"o">>},
101 {<<"Organization Unit">>, <<"ou">>}]).
102
103 -define(SEARCH_REPORTED,
104 [{<<"Full Name">>, <<"FN">>},
105 {<<"Given Name">>, <<"FIRST">>},
106 {<<"Middle Name">>, <<"MIDDLE">>},
107 {<<"Family Name">>, <<"LAST">>},
108 {<<"Nickname">>, <<"NICK">>},
109 {<<"Birthday">>, <<"BDAY">>},
110 {<<"Country">>, <<"CTRY">>},
111 {<<"City">>, <<"LOCALITY">>},
112 {<<"Email">>, <<"EMAIL">>},
113 {<<"Organization Name">>, <<"ORGNAME">>},
114 {<<"Organization Unit">>, <<"ORGUNIT">>}]).
115
116
117
118 %%--------------------------------------------------------------------
119 %% mod_vcard_backend callbacks
120 %%--------------------------------------------------------------------
121 -spec init(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
122 init(_HostType, _Opts) ->
123
:-(
ok.
124
125 -spec tear_down(mongooseim:host_type()) -> ok.
126 tear_down(HostType) ->
127
:-(
clear_persistent_term(HostType),
128
:-(
ok.
129
130 -spec remove_user(mongooseim:host_type(), jid:luser(), jid:lserver()) -> ok.
131 remove_user(_HostType, _LUser, _LServer) ->
132 %% no need to handle this - in ldap
133 %% removing user = delete all user info
134
:-(
ok.
135
136 -spec get_vcard(mongooseim:host_type(), jid:luser(), jid:lserver()) -> {ok, [exml:element()]}.
137 get_vcard(HostType, LUser, LServer) ->
138
:-(
State = get_state(HostType, LServer),
139
:-(
JID = jid:make_bare(LUser, LServer),
140
:-(
case ejabberd_auth:does_user_exist(JID) of
141 true ->
142
:-(
case find_ldap_user(LUser, State) of
143 #eldap_entry{attributes = Attributes} ->
144
:-(
VCardMap = State#state.vcard_map,
145
:-(
VCard = ldap_attributes_to_vcard(Attributes, VCardMap, {LUser, LServer}),
146
:-(
{ok, VCard};
147 _ ->
148
:-(
{ok, []}
149 end;
150 _ ->
151
:-(
{ok, []}
152 end.
153
154 -spec set_vcard(mongooseim:host_type(), jid:luser(), jid:lserver(), exml:element(), mod_vcard:vcard_search()) ->
155 {error, exml:element()}.
156 set_vcard(_HostType, _User, _LServer, _VCard, _VCardSearch) ->
157
:-(
{error, mongoose_xmpp_errors:not_allowed()}.
158
159 -spec search(mongooseim:host_type(), jid:lserver(), [{binary(), [binary()]}]) ->
160 [[mongoose_data_forms:field()]].
161 search(HostType, LServer, Data) ->
162
:-(
State = get_state(HostType, LServer),
163
:-(
search_internal(State, Data).
164
165 -spec search_fields(mongooseim:host_type(), jid:lserver()) -> [{binary(), binary()}].
166 search_fields(HostType, LServer) ->
167
:-(
State = get_state(HostType, LServer),
168
:-(
State#state.search_fields.
169
170 -spec search_reported_fields(mongooseim:host_type(), jid:lserver(), binary()) ->
171 [mongoose_data_forms:field()].
172 search_reported_fields(HostType, LServer, Lang) ->
173
:-(
State = get_state(HostType, LServer),
174
:-(
SearchReported = State#state.search_reported,
175
:-(
[?TLFIELD(<<"text-single">>, Name, Value) ||
176
:-(
{Name, Value} <- [{<<"Jabber ID">>, <<"jid">>} | SearchReported]].
177
178 %%--------------------------------------------------------------------
179 %% API
180 %%--------------------------------------------------------------------
181 default_vcard_map() ->
182 66 ?VCARD_MAP.
183
184 default_search_fields() ->
185 66 ?SEARCH_FIELDS.
186
187 default_search_reported() ->
188 66 ?SEARCH_REPORTED.
189
190 %%--------------------------------------------------------------------
191 %% Internal
192 %%--------------------------------------------------------------------
193 find_ldap_user(User, State) ->
194
:-(
Base = State#state.base,
195
:-(
RFC2254Filter = State#state.user_filter,
196
:-(
EldapID = State#state.eldap_id,
197
:-(
VCardAttrs = State#state.vcard_map_attrs,
198
:-(
case eldap_filter:parse(RFC2254Filter, [{<<"%u">>, User}]) of
199 {ok, EldapFilter} ->
200
:-(
Res = eldap_pool_search(EldapID, Base, EldapFilter, State#state.deref, VCardAttrs, false),
201
:-(
case Res of
202 [H | _] ->
203
:-(
H;
204 _ ->
205
:-(
Res
206 end;
207
:-(
_ -> false
208 end.
209
210 eldap_pool_search(EldapID, Base, EldapFilter, Deref, Attrs, NoResultRes) ->
211
:-(
SearchOpts = search_opts(Base, EldapFilter, Deref, Attrs),
212
:-(
case eldap_pool:search(EldapID, SearchOpts) of
213
:-(
#eldap_search_result{entries = E} -> E;
214
:-(
_ -> NoResultRes
215 end.
216
217 search_opts(Base, EldapFilter, Deref, Attrs) ->
218
:-(
[{base, Base}, {filter, EldapFilter},
219 {deref, Deref}, {attributes, Attrs}].
220
221 ldap_attributes_to_vcard(Attributes, VCardMap, UD) ->
222
:-(
Attrs = lists:map(fun ({VCardName, _, _}) ->
223
:-(
{jid:str_tolower(VCardName),
224 map_vcard_attr(VCardName, Attributes, VCardMap,
225 UD)}
226 end,
227 VCardMap),
228
:-(
Elts = [ldap_attribute_to_vcard(vCard, Attr)
229
:-(
|| Attr <- Attrs],
230
:-(
NElts = [ldap_attribute_to_vcard(vCardN, Attr)
231
:-(
|| Attr <- Attrs],
232
:-(
OElts = [ldap_attribute_to_vcard(vCardO, Attr)
233
:-(
|| Attr <- Attrs],
234
:-(
AElts = [ldap_attribute_to_vcard(vCardA, Attr)
235
:-(
|| Attr <- Attrs],
236
:-(
[#xmlel{name = <<"vCard">>,
237 attrs = [{<<"xmlns">>, ?NS_VCARD}],
238 children =
239
:-(
lists:append([X || X <- Elts, X /= none],
240 [#xmlel{name = <<"N">>, attrs = [],
241
:-(
children = [X || X <- NElts, X /= none]},
242 #xmlel{name = <<"ORG">>, attrs = [],
243
:-(
children = [X || X <- OElts, X /= none]},
244 #xmlel{name = <<"ADR">>, attrs = [],
245 children =
246
:-(
[X || X <- AElts, X /= none]}])}].
247
248 ldap_attribute_to_vcard(vCard, {<<"fn">>, Value}) ->
249
:-(
#xmlel{name = <<"FN">>, attrs = [],
250 children = [{xmlcdata, Value}]};
251 ldap_attribute_to_vcard(vCard,
252 {<<"nickname">>, Value}) ->
253
:-(
#xmlel{name = <<"NICKNAME">>, attrs = [],
254 children = [{xmlcdata, Value}]};
255 ldap_attribute_to_vcard(vCard, {<<"title">>, Value}) ->
256
:-(
#xmlel{name = <<"TITLE">>, attrs = [],
257 children = [{xmlcdata, Value}]};
258 ldap_attribute_to_vcard(vCard, {<<"bday">>, Value}) ->
259
:-(
#xmlel{name = <<"BDAY">>, attrs = [],
260 children = [{xmlcdata, Value}]};
261 ldap_attribute_to_vcard(vCard, {<<"url">>, Value}) ->
262
:-(
#xmlel{name = <<"URL">>, attrs = [],
263 children = [{xmlcdata, Value}]};
264 ldap_attribute_to_vcard(vCard, {<<"desc">>, Value}) ->
265
:-(
#xmlel{name = <<"DESC">>, attrs = [],
266 children = [{xmlcdata, Value}]};
267 ldap_attribute_to_vcard(vCard, {<<"role">>, Value}) ->
268
:-(
#xmlel{name = <<"ROLE">>, attrs = [],
269 children = [{xmlcdata, Value}]};
270 ldap_attribute_to_vcard(vCard, {<<"tel">>, Value}) ->
271
:-(
#xmlel{name = <<"TEL">>, attrs = [],
272 children =
273 [#xmlel{name = <<"VOICE">>, attrs = [], children = []},
274 #xmlel{name = <<"WORK">>, attrs = [], children = []},
275 #xmlel{name = <<"NUMBER">>, attrs = [],
276 children = [{xmlcdata, Value}]}]};
277 ldap_attribute_to_vcard(vCard, {<<"email">>, Value}) ->
278
:-(
#xmlel{name = <<"EMAIL">>, attrs = [],
279 children =
280 [#xmlel{name = <<"INTERNET">>, attrs = [],
281 children = []},
282 #xmlel{name = <<"PREF">>, attrs = [], children = []},
283 #xmlel{name = <<"USERID">>, attrs = [],
284 children = [{xmlcdata, Value}]}]};
285 ldap_attribute_to_vcard(vCard, {<<"photo">>, Value}) ->
286
:-(
#xmlel{name = <<"PHOTO">>, attrs = [],
287 children =
288 [#xmlel{name = <<"TYPE">>, attrs = [],
289 children = [{xmlcdata, <<"image/jpeg">>}]},
290 #xmlel{name = <<"BINVAL">>, attrs = [],
291 children = [{xmlcdata, jlib:encode_base64(Value)}]}]};
292 ldap_attribute_to_vcard(vCardN,
293 {<<"family">>, Value}) ->
294
:-(
#xmlel{name = <<"FAMILY">>, attrs = [],
295 children = [{xmlcdata, Value}]};
296 ldap_attribute_to_vcard(vCardN, {<<"given">>, Value}) ->
297
:-(
#xmlel{name = <<"GIVEN">>, attrs = [],
298 children = [{xmlcdata, Value}]};
299 ldap_attribute_to_vcard(vCardN,
300 {<<"middle">>, Value}) ->
301
:-(
#xmlel{name = <<"MIDDLE">>, attrs = [],
302 children = [{xmlcdata, Value}]};
303 ldap_attribute_to_vcard(vCardO,
304 {<<"orgname">>, Value}) ->
305
:-(
#xmlel{name = <<"ORGNAME">>, attrs = [],
306 children = [{xmlcdata, Value}]};
307 ldap_attribute_to_vcard(vCardO,
308 {<<"orgunit">>, Value}) ->
309
:-(
#xmlel{name = <<"ORGUNIT">>, attrs = [],
310 children = [{xmlcdata, Value}]};
311 ldap_attribute_to_vcard(vCardA,
312 {<<"locality">>, Value}) ->
313
:-(
#xmlel{name = <<"LOCALITY">>, attrs = [],
314 children = [{xmlcdata, Value}]};
315 ldap_attribute_to_vcard(vCardA,
316 {<<"street">>, Value}) ->
317
:-(
#xmlel{name = <<"STREET">>, attrs = [],
318 children = [{xmlcdata, Value}]};
319 ldap_attribute_to_vcard(vCardA, {<<"ctry">>, Value}) ->
320
:-(
#xmlel{name = <<"CTRY">>, attrs = [],
321 children = [{xmlcdata, Value}]};
322 ldap_attribute_to_vcard(vCardA,
323 {<<"region">>, Value}) ->
324
:-(
#xmlel{name = <<"REGION">>, attrs = [],
325 children = [{xmlcdata, Value}]};
326 ldap_attribute_to_vcard(vCardA, {<<"pcode">>, Value}) ->
327
:-(
#xmlel{name = <<"PCODE">>, attrs = [],
328 children = [{xmlcdata, Value}]};
329
:-(
ldap_attribute_to_vcard(_, _) -> none.
330
331 search_internal(_, []) ->
332
:-(
[];
333 search_internal(State, Data) ->
334
:-(
Base = State#state.base,
335
:-(
SearchFilter = State#state.search_filter,
336
:-(
EldapID = State#state.eldap_id,
337
:-(
UIDs = State#state.uids,
338
:-(
Limit = State#state.matches,
339
:-(
ReportedAttrs = State#state.search_reported_attrs,
340
:-(
Op = State#state.search_operator,
341
:-(
Filter = eldap:'and'([SearchFilter, eldap_utils:make_filter(Data, UIDs, Op)]),
342
:-(
E = eldap_pool_search(EldapID, Base, Filter, State#state.deref, ReportedAttrs, error),
343
:-(
case E of
344 error ->
345
:-(
error;
346 E ->
347
:-(
Limited = limited_results(E, Limit),
348
:-(
search_items(Limited, State)
349 end.
350
351 limited_results(E, Limit) when length(E) > Limit ->
352
:-(
lists:sublist(E, Limit);
353 limited_results(E, _) when not is_list(E) ->
354
:-(
[E];
355 limited_results(E, _) ->
356
:-(
E.
357
358 search_items(Entries, State) ->
359
:-(
lists:flatmap(fun(#eldap_entry{attributes = Attrs}) -> attrs_to_item(Attrs, State) end,
360 Entries).
361
362 attrs_to_item(Attrs, #state{uids = UIDs} = State) ->
363
:-(
case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
364 {U, UIDAttrFormat} ->
365
:-(
case eldap_utils:get_user_part(U, UIDAttrFormat) of
366 {ok, Username} ->
367
:-(
make_user_item_if_exists(Username, Attrs, State);
368
:-(
_ -> []
369 end;
370
:-(
<<"">> -> []
371 end.
372
373 make_user_item_if_exists(Username, Attrs,
374 #state{serverhost = LServer, search_reported = SearchReported,
375 vcard_map = VCardMap, binary_search_fields = BinFields}) ->
376
:-(
JID = jid:make_bare(Username, LServer),
377
:-(
case ejabberd_auth:does_user_exist(JID) of
378 true ->
379
:-(
RFields = lists:map(fun ({_, VCardName}) ->
380
:-(
{VCardName, map_vcard_attr(VCardName, Attrs, VCardMap,
381 {Username, LServer})}
382 end,
383 SearchReported),
384
:-(
[[?FIELD(<<"jid">>, <<Username/binary, "@", LServer/binary>>)] ++
385
:-(
[?FIELD(Name, search_item_value(Name, Value, BinFields)) ||
386
:-(
{Name, Value} <- RFields]];
387
:-(
_ -> []
388 end.
389
390 %%%-----------------------
391 %%% Auxiliary functions.
392 %%%-----------------------
393 search_item_value(Name, Value, BinaryFields) ->
394
:-(
case lists:member(Name, BinaryFields) of
395
:-(
true -> jlib:encode_base64(Value);
396
:-(
false -> Value
397 end.
398
399 map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
400
:-(
Res = lists:filter(fun ({Name, _, _}) ->
401
:-(
eldap_utils:case_insensitive_match(Name,
402 VCardName)
403 end,
404 Pattern),
405
:-(
case Res of
406 [{_, Str, Attrs}] ->
407
:-(
process_pattern(Str, UD,
408
:-(
[eldap_utils:get_ldap_attr(X, Attributes)
409
:-(
|| X <- Attrs]);
410
:-(
_ -> <<"">>
411 end.
412
413 process_pattern(Str, {User, Domain}, AttrValues) ->
414
:-(
eldap_filter:do_sub(Str,
415 [{<<"%u">>, User}, {<<"%d">>, Domain}] ++
416
:-(
[{<<"%s">>, V, 1} || V <- AttrValues]).
417
418 get_state(HostType, LServer) ->
419
:-(
Key = config_key(HostType, LServer),
420
:-(
case persistent_term:get(Key, undefined) of
421 undefined ->
422
:-(
State = create_state(HostType, LServer),
423
:-(
persistent_term:put(Key, State),
424
:-(
State;
425 State ->
426
:-(
State
427 end.
428
429 create_state(HostType, LServer) ->
430
:-(
Opts = gen_mod:get_loaded_module_opts(HostType, mod_vcard),
431
:-(
Matches = gen_mod:get_opt(matches, Opts),
432
:-(
Host = gen_mod:get_opt(host, Opts),
433
:-(
LDAPOpts = gen_mod:get_opt(ldap, Opts),
434
:-(
#{pool_tag := PoolTag,
435 base := Base,
436 deref := Deref,
437 uids := RawUIDs,
438 filter := RawUserFilter,
439 vcard_map := VCardMap,
440 search_fields := SearchFields,
441 search_reported := SearchReported,
442 search_operator := SearchOperator,
443 binary_search_fields := BinaryFields} = LDAPOpts,
444
:-(
MyHost = mongoose_subdomain_utils:get_fqdn(Host, LServer),
445
:-(
DerefAliases = eldap_utils:deref_aliases(Deref),
446
:-(
UIDs = eldap_utils:uids_domain_subst(LServer, RawUIDs),
447
:-(
UserFilter = eldap_utils:process_user_filter(UIDs, RawUserFilter),
448
:-(
{ok, SearchFilter} = eldap_filter:parse(eldap_utils:get_search_filter(UserFilter)),
449
450
:-(
UIDAttrs = lists:map(fun({UID, _}) -> UID;
451
:-(
({UID}) -> UID end, UIDs),
452
:-(
VCardMapAttrs = lists:usort(lists:append([A || {_, _, A} <- VCardMap])
453 ++ UIDAttrs),
454
:-(
SearchReportedAttrs = lists:usort(lists:flatmap(
455 fun ({_, N}) ->
456
:-(
case lists:keysearch(N, 1, VCardMap) of
457 {value, {_, _, L}} ->
458
:-(
L;
459
:-(
_ -> []
460 end
461 end,
462 SearchReported) ++ UIDAttrs),
463
:-(
#state{serverhost = LServer,
464 myhost = MyHost,
465 eldap_id = {HostType, PoolTag},
466 base = Base,
467 deref = DerefAliases,
468 uids = UIDs, vcard_map = VCardMap,
469 vcard_map_attrs = VCardMapAttrs,
470 user_filter = UserFilter, search_filter = SearchFilter,
471 search_fields = SearchFields,
472 binary_search_fields = BinaryFields,
473 search_reported = SearchReported,
474 search_reported_attrs = SearchReportedAttrs,
475 search_operator = SearchOperator,
476 matches = Matches}.
477
478 clear_persistent_term(HostType) ->
479
:-(
Terms = persistent_term:get(),
480
:-(
States = lists:filter(fun({K, _V}) -> is_host_type_config_key(HostType, K) end, Terms),
481
:-(
[persistent_term:erase(Key) || {Key, _V} <- States].
482
483 is_host_type_config_key(HostType, {?MODULE, HostType, _LServer}) ->
484
:-(
true;
485 is_host_type_config_key(_HT, _K) ->
486
:-(
false.
487
488 config_key(HostType, LServer) ->
489
:-(
{?MODULE, HostType, LServer}.
Line Hits Source