./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
:-(
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(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()]}]) -> [eldap:eldap_entry()].
160 search(HostType, LServer, Data) ->
161
:-(
State = get_state(HostType, LServer),
162
:-(
search_internal(State, Data).
163
164 -spec search_fields(mongooseim:host_type(), jid:lserver()) -> [{binary(), binary()}].
165 search_fields(HostType, LServer) ->
166
:-(
State = get_state(HostType, LServer),
167
:-(
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
:-(
State = get_state(HostType, LServer),
172
:-(
SearchReported = State#state.search_reported,
173
:-(
#xmlel{name = <<"reported">>, attrs = [],
174 children =
175 [?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
176 <<"jid">>)]
177 ++
178 lists:map(fun ({Name, Value}) ->
179
:-(
?TLFIELD(<<"text-single">>, Name,
180 Value)
181 end,
182 SearchReported)}.
183
184 %%--------------------------------------------------------------------
185 %% API
186 %%--------------------------------------------------------------------
187 default_vcard_map() ->
188 152 ?VCARD_MAP.
189
190 default_search_fields() ->
191 152 ?SEARCH_FIELDS.
192
193 default_search_reported() ->
194 152 ?SEARCH_REPORTED.
195
196 %%--------------------------------------------------------------------
197 %% Internal
198 %%--------------------------------------------------------------------
199 find_ldap_user(User, State) ->
200
:-(
Base = State#state.base,
201
:-(
RFC2254Filter = State#state.user_filter,
202
:-(
EldapID = State#state.eldap_id,
203
:-(
VCardAttrs = State#state.vcard_map_attrs,
204
:-(
case eldap_filter:parse(RFC2254Filter, [{<<"%u">>, User}]) of
205 {ok, EldapFilter} ->
206
:-(
Res = eldap_pool_search(EldapID, Base, EldapFilter, State#state.deref, VCardAttrs, false),
207
:-(
case Res of
208 [H | _] ->
209
:-(
H;
210 _ ->
211
:-(
Res
212 end;
213
:-(
_ -> false
214 end.
215
216 eldap_pool_search(EldapID, Base, EldapFilter, Deref, Attrs, NoResultRes) ->
217
:-(
SearchOpts = search_opts(Base, EldapFilter, Deref, Attrs),
218
:-(
case eldap_pool:search(EldapID, SearchOpts) of
219
:-(
#eldap_search_result{entries = E} -> E;
220
:-(
_ -> NoResultRes
221 end.
222
223 search_opts(Base, EldapFilter, Deref, Attrs) ->
224
:-(
[{base, Base}, {filter, EldapFilter},
225 {deref, Deref}, {attributes, Attrs}].
226
227 ldap_attributes_to_vcard(Attributes, VCardMap, UD) ->
228
:-(
Attrs = lists:map(fun ({VCardName, _, _}) ->
229
:-(
{jid:str_tolower(VCardName),
230 map_vcard_attr(VCardName, Attributes, VCardMap,
231 UD)}
232 end,
233 VCardMap),
234
:-(
Elts = [ldap_attribute_to_vcard(vCard, Attr)
235
:-(
|| Attr <- Attrs],
236
:-(
NElts = [ldap_attribute_to_vcard(vCardN, Attr)
237
:-(
|| Attr <- Attrs],
238
:-(
OElts = [ldap_attribute_to_vcard(vCardO, Attr)
239
:-(
|| Attr <- Attrs],
240
:-(
AElts = [ldap_attribute_to_vcard(vCardA, Attr)
241
:-(
|| Attr <- Attrs],
242
:-(
[#xmlel{name = <<"vCard">>,
243 attrs = [{<<"xmlns">>, ?NS_VCARD}],
244 children =
245
:-(
lists:append([X || X <- Elts, X /= none],
246 [#xmlel{name = <<"N">>, attrs = [],
247
:-(
children = [X || X <- NElts, X /= none]},
248 #xmlel{name = <<"ORG">>, attrs = [],
249
:-(
children = [X || X <- OElts, X /= none]},
250 #xmlel{name = <<"ADR">>, attrs = [],
251 children =
252
:-(
[X || X <- AElts, X /= none]}])}].
253
254 ldap_attribute_to_vcard(vCard, {<<"fn">>, Value}) ->
255
:-(
#xmlel{name = <<"FN">>, attrs = [],
256 children = [{xmlcdata, Value}]};
257 ldap_attribute_to_vcard(vCard,
258 {<<"nickname">>, Value}) ->
259
:-(
#xmlel{name = <<"NICKNAME">>, attrs = [],
260 children = [{xmlcdata, Value}]};
261 ldap_attribute_to_vcard(vCard, {<<"title">>, Value}) ->
262
:-(
#xmlel{name = <<"TITLE">>, attrs = [],
263 children = [{xmlcdata, Value}]};
264 ldap_attribute_to_vcard(vCard, {<<"bday">>, Value}) ->
265
:-(
#xmlel{name = <<"BDAY">>, attrs = [],
266 children = [{xmlcdata, Value}]};
267 ldap_attribute_to_vcard(vCard, {<<"url">>, Value}) ->
268
:-(
#xmlel{name = <<"URL">>, attrs = [],
269 children = [{xmlcdata, Value}]};
270 ldap_attribute_to_vcard(vCard, {<<"desc">>, Value}) ->
271
:-(
#xmlel{name = <<"DESC">>, attrs = [],
272 children = [{xmlcdata, Value}]};
273 ldap_attribute_to_vcard(vCard, {<<"role">>, Value}) ->
274
:-(
#xmlel{name = <<"ROLE">>, attrs = [],
275 children = [{xmlcdata, Value}]};
276 ldap_attribute_to_vcard(vCard, {<<"tel">>, Value}) ->
277
:-(
#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
:-(
#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
:-(
#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
:-(
#xmlel{name = <<"FAMILY">>, attrs = [],
301 children = [{xmlcdata, Value}]};
302 ldap_attribute_to_vcard(vCardN, {<<"given">>, Value}) ->
303
:-(
#xmlel{name = <<"GIVEN">>, attrs = [],
304 children = [{xmlcdata, Value}]};
305 ldap_attribute_to_vcard(vCardN,
306 {<<"middle">>, Value}) ->
307
:-(
#xmlel{name = <<"MIDDLE">>, attrs = [],
308 children = [{xmlcdata, Value}]};
309 ldap_attribute_to_vcard(vCardO,
310 {<<"orgname">>, Value}) ->
311
:-(
#xmlel{name = <<"ORGNAME">>, attrs = [],
312 children = [{xmlcdata, Value}]};
313 ldap_attribute_to_vcard(vCardO,
314 {<<"orgunit">>, Value}) ->
315
:-(
#xmlel{name = <<"ORGUNIT">>, attrs = [],
316 children = [{xmlcdata, Value}]};
317 ldap_attribute_to_vcard(vCardA,
318 {<<"locality">>, Value}) ->
319
:-(
#xmlel{name = <<"LOCALITY">>, attrs = [],
320 children = [{xmlcdata, Value}]};
321 ldap_attribute_to_vcard(vCardA,
322 {<<"street">>, Value}) ->
323
:-(
#xmlel{name = <<"STREET">>, attrs = [],
324 children = [{xmlcdata, Value}]};
325 ldap_attribute_to_vcard(vCardA, {<<"ctry">>, Value}) ->
326
:-(
#xmlel{name = <<"CTRY">>, attrs = [],
327 children = [{xmlcdata, Value}]};
328 ldap_attribute_to_vcard(vCardA,
329 {<<"region">>, Value}) ->
330
:-(
#xmlel{name = <<"REGION">>, attrs = [],
331 children = [{xmlcdata, Value}]};
332 ldap_attribute_to_vcard(vCardA, {<<"pcode">>, Value}) ->
333
:-(
#xmlel{name = <<"PCODE">>, attrs = [],
334 children = [{xmlcdata, Value}]};
335
:-(
ldap_attribute_to_vcard(_, _) -> none.
336
337 search_internal(_, []) ->
338
:-(
[];
339 search_internal(State, Data) ->
340
:-(
Base = State#state.base,
341
:-(
SearchFilter = State#state.search_filter,
342
:-(
EldapID = State#state.eldap_id,
343
:-(
UIDs = State#state.uids,
344
:-(
Limit = State#state.matches,
345
:-(
ReportedAttrs = State#state.search_reported_attrs,
346
:-(
Op = State#state.search_operator,
347
:-(
Filter = eldap:'and'([SearchFilter, eldap_utils:make_filter(Data, UIDs, Op)]),
348
:-(
E = eldap_pool_search(EldapID, Base, Filter, State#state.deref, ReportedAttrs, error),
349
:-(
case E of
350 error ->
351
:-(
error;
352 E ->
353
:-(
Limited = limited_results(E, Limit),
354
:-(
search_items(Limited, State)
355 end.
356
357 limited_results(E, Limit) when length(E) > Limit ->
358
:-(
lists:sublist(E, Limit);
359 limited_results(E, _) when not is_list(E) ->
360
:-(
[E];
361 limited_results(E, _) ->
362
:-(
E.
363
364 search_items(Entries, State) ->
365
:-(
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
:-(
case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
370 {U, UIDAttrFormat} ->
371
:-(
case eldap_utils:get_user_part(U, UIDAttrFormat) of
372 {ok, Username} ->
373
:-(
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
:-(
JID = jid:make(Username, LServer, <<>>),
383
:-(
case ejabberd_auth:does_user_exist(JID) of
384 true ->
385
:-(
RFields = lists:map(fun ({_, VCardName}) ->
386
:-(
{VCardName, map_vcard_attr(VCardName, Attrs, VCardMap,
387 {Username, LServer})}
388 end,
389 SearchReported),
390
:-(
Result = [?FIELD(<<"jid">>, <<Username/binary, "@", LServer/binary>>)] ++
391
:-(
[?FIELD(Name, search_item_value(Name, Value, BinFields)) || {Name, Value} <- RFields],
392
:-(
[#xmlel{name = <<"item">>, attrs = [], children = Result}];
393
:-(
_ -> []
394 end.
395
396 %%%-----------------------
397 %%% Auxiliary functions.
398 %%%-----------------------
399 search_item_value(Name, Value, BinaryFields) ->
400
:-(
case lists:member(Name, BinaryFields) of
401
:-(
true -> jlib:encode_base64(Value);
402
:-(
false -> Value
403 end.
404
405 map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
406
:-(
Res = lists:filter(fun ({Name, _, _}) ->
407
:-(
eldap_utils:case_insensitive_match(Name,
408 VCardName)
409 end,
410 Pattern),
411
:-(
case Res of
412 [{_, Str, Attrs}] ->
413
:-(
process_pattern(Str, UD,
414
:-(
[eldap_utils:get_ldap_attr(X, Attributes)
415
:-(
|| X <- Attrs]);
416
:-(
_ -> <<"">>
417 end.
418
419 process_pattern(Str, {User, Domain}, AttrValues) ->
420
:-(
eldap_filter:do_sub(Str,
421 [{<<"%u">>, User}, {<<"%d">>, Domain}] ++
422
:-(
[{<<"%s">>, V, 1} || V <- AttrValues]).
423
424 get_state(HostType, LServer) ->
425
:-(
Key = config_key(HostType, LServer),
426
:-(
case persistent_term:get(Key, undefined) of
427 undefined ->
428
:-(
State = create_state(HostType, LServer),
429
:-(
persistent_term:put(Key, State),
430
:-(
State;
431 State ->
432
:-(
State
433 end.
434
435 create_state(HostType, LServer) ->
436
:-(
Opts = gen_mod:get_loaded_module_opts(HostType, mod_vcard),
437
:-(
Matches = gen_mod:get_opt(matches, Opts),
438
:-(
Host = gen_mod:get_opt(host, Opts),
439
:-(
LDAPOpts = gen_mod:get_opt(ldap, Opts),
440
:-(
#{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
:-(
MyHost = mongoose_subdomain_utils:get_fqdn(Host, LServer),
451
:-(
DerefAliases = eldap_utils:deref_aliases(Deref),
452
:-(
UIDs = eldap_utils:uids_domain_subst(LServer, RawUIDs),
453
:-(
UserFilter = eldap_utils:process_user_filter(UIDs, RawUserFilter),
454
:-(
{ok, SearchFilter} = eldap_filter:parse(eldap_utils:get_search_filter(UserFilter)),
455
456
:-(
UIDAttrs = lists:map(fun({UID, _}) -> UID;
457
:-(
({UID}) -> UID end, UIDs),
458
:-(
VCardMapAttrs = lists:usort(lists:append([A || {_, _, A} <- VCardMap])
459 ++ UIDAttrs),
460
:-(
SearchReportedAttrs = lists:usort(lists:flatmap(
461 fun ({_, N}) ->
462
:-(
case lists:keysearch(N, 1, VCardMap) of
463 {value, {_, _, L}} ->
464
:-(
L;
465
:-(
_ -> []
466 end
467 end,
468 SearchReported) ++ UIDAttrs),
469
:-(
#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
:-(
Terms = persistent_term:get(),
486
:-(
States = lists:filter(fun({K, _V}) -> is_host_type_config_key(HostType, K) end, Terms),
487
:-(
[persistent_term:erase(Key) || {Key, _V} <- States].
488
489 is_host_type_config_key(HostType, {?MODULE, HostType, _LServer}) ->
490
:-(
true;
491 is_host_type_config_key(_HT, _K) ->
492
:-(
false.
493
494 config_key(HostType, LServer) ->
495
:-(
{?MODULE, HostType, LServer}.
Line Hits Source