./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("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: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 333 ok.
124
125 -spec tear_down(mongooseim:host_type()) -> ok.
126 tear_down(HostType) ->
127 320 clear_persistent_term(HostType),
128 320 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 2291 ok.
135
136 -spec get_vcard(mongooseim:host_type(), jid:luser(), jid:lserver()) -> {ok, [exml:element()]}.
137 get_vcard(HostType, LUser, LServer) ->
138 44 State = get_state(HostType, LServer),
139 44 JID = jid:make(LUser, LServer, <<>>),
140 44 case ejabberd_auth:does_user_exist(JID) of
141 true ->
142 31 case find_ldap_user(LUser, State) of
143 #eldap_entry{attributes = Attributes} ->
144 31 VCardMap = State#state.vcard_map,
145 31 VCard = ldap_attributes_to_vcard(Attributes, VCardMap, {LUser, LServer}),
146 31 {ok, VCard};
147 _ ->
148
:-(
{ok, []}
149 end;
150 _ ->
151 13 {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()]}]) -> [eldap:eldap_entry()].
160 search(HostType, LServer, Data) ->
161 21 State = get_state(HostType, LServer),
162 21 search_internal(State, Data).
163
164 -spec search_fields(mongooseim:host_type(), jid:lserver()) -> [{binary(), binary()}].
165 search_fields(HostType, LServer) ->
166 2 State = get_state(HostType, LServer),
167 2 State#state.search_fields.
168
169 -spec search_reported_fields(mongooseim:host_type(), jid:lserver(), binary()) -> exml:element().
170 search_reported_fields(HostType, LServer, Lang) ->
171 21 State = get_state(HostType, LServer),
172 21 SearchReported = State#state.search_reported,
173 21 #xmlel{name = <<"reported">>, attrs = [],
174 children =
175 [?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
176 <<"jid">>)]
177 ++
178 lists:map(fun ({Name, Value}) ->
179 233 ?TLFIELD(<<"text-single">>, Name,
180 Value)
181 end,
182 SearchReported)}.
183
184 %%--------------------------------------------------------------------
185 %% API
186 %%--------------------------------------------------------------------
187 default_vcard_map() ->
188 164 ?VCARD_MAP.
189
190 default_search_fields() ->
191 164 ?SEARCH_FIELDS.
192
193 default_search_reported() ->
194 164 ?SEARCH_REPORTED.
195
196 %%--------------------------------------------------------------------
197 %% Internal
198 %%--------------------------------------------------------------------
199 find_ldap_user(User, State) ->
200 31 Base = State#state.base,
201 31 RFC2254Filter = State#state.user_filter,
202 31 EldapID = State#state.eldap_id,
203 31 VCardAttrs = State#state.vcard_map_attrs,
204 31 case eldap_filter:parse(RFC2254Filter, [{<<"%u">>, User}]) of
205 {ok, EldapFilter} ->
206 31 Res = eldap_pool_search(EldapID, Base, EldapFilter, State#state.deref, VCardAttrs, false),
207 31 case Res of
208 [H | _] ->
209 31 H;
210 _ ->
211
:-(
Res
212 end;
213
:-(
_ -> false
214 end.
215
216 eldap_pool_search(EldapID, Base, EldapFilter, Deref, Attrs, NoResultRes) ->
217 50 SearchOpts = search_opts(Base, EldapFilter, Deref, Attrs),
218 50 case eldap_pool:search(EldapID, SearchOpts) of
219 50 #eldap_search_result{entries = E} -> E;
220
:-(
_ -> NoResultRes
221 end.
222
223 search_opts(Base, EldapFilter, Deref, Attrs) ->
224 50 [{base, Base}, {filter, EldapFilter},
225 {deref, Deref}, {attributes, Attrs}].
226
227 ldap_attributes_to_vcard(Attributes, VCardMap, UD) ->
228 31 Attrs = lists:map(fun ({VCardName, _, _}) ->
229 563 {jid:str_tolower(VCardName),
230 map_vcard_attr(VCardName, Attributes, VCardMap,
231 UD)}
232 end,
233 VCardMap),
234 31 Elts = [ldap_attribute_to_vcard(vCard, Attr)
235 31 || Attr <- Attrs],
236 31 NElts = [ldap_attribute_to_vcard(vCardN, Attr)
237 31 || Attr <- Attrs],
238 31 OElts = [ldap_attribute_to_vcard(vCardO, Attr)
239 31 || Attr <- Attrs],
240 31 AElts = [ldap_attribute_to_vcard(vCardA, Attr)
241 31 || Attr <- Attrs],
242 31 [#xmlel{name = <<"vCard">>,
243 attrs = [{<<"xmlns">>, ?NS_VCARD}],
244 children =
245 283 lists:append([X || X <- Elts, X /= none],
246 [#xmlel{name = <<"N">>, attrs = [],
247 84 children = [X || X <- NElts, X /= none]},
248 #xmlel{name = <<"ORG">>, attrs = [],
249 56 children = [X || X <- OElts, X /= none]},
250 #xmlel{name = <<"ADR">>, attrs = [],
251 children =
252 140 [X || X <- AElts, X /= none]}])}].
253
254 ldap_attribute_to_vcard(vCard, {<<"fn">>, Value}) ->
255 31 #xmlel{name = <<"FN">>, attrs = [],
256 children = [{xmlcdata, Value}]};
257 ldap_attribute_to_vcard(vCard,
258 {<<"nickname">>, Value}) ->
259 28 #xmlel{name = <<"NICKNAME">>, attrs = [],
260 children = [{xmlcdata, Value}]};
261 ldap_attribute_to_vcard(vCard, {<<"title">>, Value}) ->
262 28 #xmlel{name = <<"TITLE">>, attrs = [],
263 children = [{xmlcdata, Value}]};
264 ldap_attribute_to_vcard(vCard, {<<"bday">>, Value}) ->
265 28 #xmlel{name = <<"BDAY">>, attrs = [],
266 children = [{xmlcdata, Value}]};
267 ldap_attribute_to_vcard(vCard, {<<"url">>, Value}) ->
268 28 #xmlel{name = <<"URL">>, attrs = [],
269 children = [{xmlcdata, Value}]};
270 ldap_attribute_to_vcard(vCard, {<<"desc">>, Value}) ->
271 28 #xmlel{name = <<"DESC">>, attrs = [],
272 children = [{xmlcdata, Value}]};
273 ldap_attribute_to_vcard(vCard, {<<"role">>, Value}) ->
274 28 #xmlel{name = <<"ROLE">>, attrs = [],
275 children = [{xmlcdata, Value}]};
276 ldap_attribute_to_vcard(vCard, {<<"tel">>, Value}) ->
277 28 #xmlel{name = <<"TEL">>, attrs = [],
278 children =
279 [#xmlel{name = <<"VOICE">>, attrs = [], children = []},
280 #xmlel{name = <<"WORK">>, attrs = [], children = []},
281 #xmlel{name = <<"NUMBER">>, attrs = [],
282 children = [{xmlcdata, Value}]}]};
283 ldap_attribute_to_vcard(vCard, {<<"email">>, Value}) ->
284 28 #xmlel{name = <<"EMAIL">>, attrs = [],
285 children =
286 [#xmlel{name = <<"INTERNET">>, attrs = [],
287 children = []},
288 #xmlel{name = <<"PREF">>, attrs = [], children = []},
289 #xmlel{name = <<"USERID">>, attrs = [],
290 children = [{xmlcdata, Value}]}]};
291 ldap_attribute_to_vcard(vCard, {<<"photo">>, Value}) ->
292 28 #xmlel{name = <<"PHOTO">>, attrs = [],
293 children =
294 [#xmlel{name = <<"TYPE">>, attrs = [],
295 children = [{xmlcdata, <<"image/jpeg">>}]},
296 #xmlel{name = <<"BINVAL">>, attrs = [],
297 children = [{xmlcdata, jlib:encode_base64(Value)}]}]};
298 ldap_attribute_to_vcard(vCardN,
299 {<<"family">>, Value}) ->
300 28 #xmlel{name = <<"FAMILY">>, attrs = [],
301 children = [{xmlcdata, Value}]};
302 ldap_attribute_to_vcard(vCardN, {<<"given">>, Value}) ->
303 28 #xmlel{name = <<"GIVEN">>, attrs = [],
304 children = [{xmlcdata, Value}]};
305 ldap_attribute_to_vcard(vCardN,
306 {<<"middle">>, Value}) ->
307 28 #xmlel{name = <<"MIDDLE">>, attrs = [],
308 children = [{xmlcdata, Value}]};
309 ldap_attribute_to_vcard(vCardO,
310 {<<"orgname">>, Value}) ->
311 28 #xmlel{name = <<"ORGNAME">>, attrs = [],
312 children = [{xmlcdata, Value}]};
313 ldap_attribute_to_vcard(vCardO,
314 {<<"orgunit">>, Value}) ->
315 28 #xmlel{name = <<"ORGUNIT">>, attrs = [],
316 children = [{xmlcdata, Value}]};
317 ldap_attribute_to_vcard(vCardA,
318 {<<"locality">>, Value}) ->
319 28 #xmlel{name = <<"LOCALITY">>, attrs = [],
320 children = [{xmlcdata, Value}]};
321 ldap_attribute_to_vcard(vCardA,
322 {<<"street">>, Value}) ->
323 28 #xmlel{name = <<"STREET">>, attrs = [],
324 children = [{xmlcdata, Value}]};
325 ldap_attribute_to_vcard(vCardA, {<<"ctry">>, Value}) ->
326 28 #xmlel{name = <<"CTRY">>, attrs = [],
327 children = [{xmlcdata, Value}]};
328 ldap_attribute_to_vcard(vCardA,
329 {<<"region">>, Value}) ->
330 28 #xmlel{name = <<"REGION">>, attrs = [],
331 children = [{xmlcdata, Value}]};
332 ldap_attribute_to_vcard(vCardA, {<<"pcode">>, Value}) ->
333 28 #xmlel{name = <<"PCODE">>, attrs = [],
334 children = [{xmlcdata, Value}]};
335 1689 ldap_attribute_to_vcard(_, _) -> none.
336
337 search_internal(_, []) ->
338 2 [];
339 search_internal(State, Data) ->
340 19 Base = State#state.base,
341 19 SearchFilter = State#state.search_filter,
342 19 EldapID = State#state.eldap_id,
343 19 UIDs = State#state.uids,
344 19 Limit = State#state.matches,
345 19 ReportedAttrs = State#state.search_reported_attrs,
346 19 Op = State#state.search_operator,
347 19 Filter = eldap:'and'([SearchFilter, eldap_utils:make_filter(Data, UIDs, Op)]),
348 19 E = eldap_pool_search(EldapID, Base, Filter, State#state.deref, ReportedAttrs, error),
349 19 case E of
350 error ->
351
:-(
error;
352 E ->
353 19 Limited = limited_results(E, Limit),
354 19 search_items(Limited, State)
355 end.
356
357 limited_results(E, Limit) when length(E) > Limit ->
358 1 lists:sublist(E, Limit);
359 limited_results(E, _) when not is_list(E) ->
360
:-(
[E];
361 limited_results(E, _) ->
362 18 E.
363
364 search_items(Entries, State) ->
365 19 lists:flatmap(fun(#eldap_entry{attributes = Attrs}) -> attrs_to_item_xml(Attrs, State) end,
366 Entries).
367
368 attrs_to_item_xml(Attrs, #state{uids = UIDs} = State) ->
369 39 case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
370 {U, UIDAttrFormat} ->
371 39 case eldap_utils:get_user_part(U, UIDAttrFormat) of
372 {ok, Username} ->
373 39 make_user_item_if_exists(Username, Attrs, State);
374
:-(
_ -> []
375 end;
376
:-(
<<"">> -> []
377 end.
378
379 make_user_item_if_exists(Username, Attrs,
380 #state{serverhost = LServer, search_reported = SearchReported,
381 vcard_map = VCardMap, binary_search_fields = BinFields}) ->
382 39 JID = jid:make(Username, LServer, <<>>),
383 39 case ejabberd_auth:does_user_exist(JID) of
384 true ->
385 39 RFields = lists:map(fun ({_, VCardName}) ->
386 433 {VCardName, map_vcard_attr(VCardName, Attrs, VCardMap,
387 {Username, LServer})}
388 end,
389 SearchReported),
390 39 Result = [?FIELD(<<"jid">>, <<Username/binary, "@", LServer/binary>>)] ++
391 433 [?FIELD(Name, search_item_value(Name, Value, BinFields)) || {Name, Value} <- RFields],
392 39 [#xmlel{name = <<"item">>, attrs = [], children = Result}];
393
:-(
_ -> []
394 end.
395
396 %%%-----------------------
397 %%% Auxiliary functions.
398 %%%-----------------------
399 search_item_value(Name, Value, BinaryFields) ->
400 433 case lists:member(Name, BinaryFields) of
401 4 true -> jlib:encode_base64(Value);
402 429 false -> Value
403 end.
404
405 map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
406 996 Res = lists:filter(fun ({Name, _, _}) ->
407 19445 eldap_utils:case_insensitive_match(Name,
408 VCardName)
409 end,
410 Pattern),
411 996 case Res of
412 [{_, Str, Attrs}] ->
413 865 process_pattern(Str, UD,
414 837 [eldap_utils:get_ldap_attr(X, Attributes)
415 865 || X <- Attrs]);
416 131 _ -> <<"">>
417 end.
418
419 process_pattern(Str, {User, Domain}, AttrValues) ->
420 865 eldap_filter:do_sub(Str,
421 [{<<"%u">>, User}, {<<"%d">>, Domain}] ++
422 837 [{<<"%s">>, V, 1} || V <- AttrValues]).
423
424 get_state(HostType, LServer) ->
425 88 Key = config_key(HostType, LServer),
426 88 case persistent_term:get(Key, undefined) of
427 undefined ->
428 8 State = create_state(HostType, LServer),
429 8 persistent_term:put(Key, State),
430 8 State;
431 State ->
432 80 State
433 end.
434
435 create_state(HostType, LServer) ->
436 8 Opts = gen_mod:get_loaded_module_opts(HostType, mod_vcard),
437 8 Matches = gen_mod:get_opt(matches, Opts),
438 8 Host = gen_mod:get_opt(host, Opts),
439 8 LDAPOpts = gen_mod:get_opt(ldap, Opts),
440 8 #{pool_tag := PoolTag,
441 base := Base,
442 deref := Deref,
443 uids := RawUIDs,
444 filter := RawUserFilter,
445 vcard_map := VCardMap,
446 search_fields := SearchFields,
447 search_reported := SearchReported,
448 search_operator := SearchOperator,
449 binary_search_fields := BinaryFields} = LDAPOpts,
450 8 MyHost = mongoose_subdomain_utils:get_fqdn(Host, LServer),
451 8 DerefAliases = eldap_utils:deref_aliases(Deref),
452 8 UIDs = eldap_utils:uids_domain_subst(LServer, RawUIDs),
453 8 UserFilter = eldap_utils:process_user_filter(UIDs, RawUserFilter),
454 8 {ok, SearchFilter} = eldap_filter:parse(eldap_utils:get_search_filter(UserFilter)),
455
456 8 UIDAttrs = lists:map(fun({UID, _}) -> UID;
457
:-(
({UID}) -> UID end, UIDs),
458 8 VCardMapAttrs = lists:usort(lists:append([A || {_, _, A} <- VCardMap])
459 ++ UIDAttrs),
460 8 SearchReportedAttrs = lists:usort(lists:flatmap(
461 fun ({_, N}) ->
462 90 case lists:keysearch(N, 1, VCardMap) of
463 {value, {_, _, L}} ->
464 58 L;
465 32 _ -> []
466 end
467 end,
468 SearchReported) ++ UIDAttrs),
469 8 #state{serverhost = LServer,
470 myhost = MyHost,
471 eldap_id = {HostType, PoolTag},
472 base = Base,
473 deref = DerefAliases,
474 uids = UIDs, vcard_map = VCardMap,
475 vcard_map_attrs = VCardMapAttrs,
476 user_filter = UserFilter, search_filter = SearchFilter,
477 search_fields = SearchFields,
478 binary_search_fields = BinaryFields,
479 search_reported = SearchReported,
480 search_reported_attrs = SearchReportedAttrs,
481 search_operator = SearchOperator,
482 matches = Matches}.
483
484 clear_persistent_term(HostType) ->
485 320 Terms = persistent_term:get(),
486 320 States = lists:filter(fun({K, _V}) -> is_host_type_config_key(HostType, K) end, Terms),
487 320 [persistent_term:erase(Key) || {Key, _V} <- States].
488
489 is_host_type_config_key(HostType, {?MODULE, HostType, _LServer}) ->
490 8 true;
491 is_host_type_config_key(_HT, _K) ->
492 205975 false.
493
494 config_key(HostType, LServer) ->
495 88 {?MODULE, HostType, LServer}.
Line Hits Source