./ct_report/coverage/ejabberd_auth_external.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : ejabberd_auth_external.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Authentification via LDAP external script
5 %%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 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(ejabberd_auth_external).
27 -author('alexey@process-one.net').
28
29 %% External exports
30 -behaviour(mongoose_gen_auth).
31
32 -export([start/1,
33 stop/1,
34 config_spec/0,
35 set_password/4,
36 authorize/1,
37 try_register/4,
38 get_registered_users/3,
39 get_registered_users_number/3,
40 get_password/3,
41 get_password_s/3,
42 does_user_exist/3,
43 remove_user/3,
44 supports_sasl_module/2,
45 supported_features/0
46 ]).
47
48 %% Internal
49 -export([check_password/4,
50 check_password/6]).
51
52 -include("mongoose.hrl").
53 -include("mongoose_config_spec.hrl").
54
55 %%%----------------------------------------------------------------------
56 %%% API
57 %%%----------------------------------------------------------------------
58
59 -spec start(HostType :: mongooseim:host_type()) -> 'ok'.
60 start(HostType) ->
61
:-(
Program = mongoose_config:get_opt([{auth, HostType}, external, program]),
62
:-(
extauth:start(HostType, Program),
63
:-(
case check_cache_last_options(HostType) of
64 cache ->
65
:-(
ok = ejabberd_auth_internal:start(HostType);
66 no_cache ->
67
:-(
ok
68 end.
69
70 -spec stop(mongooseim:host_type()) -> ok.
71 stop(HostType) ->
72
:-(
extauth:stop(HostType).
73
74 -spec config_spec() -> mongoose_config_spec:config_section().
75 config_spec() ->
76 164 #section{
77 items = #{<<"instances">> => #option{type = integer,
78 validate = positive},
79 <<"program">> => #option{type = string,
80 validate = non_empty}
81 },
82 required = [<<"program">>],
83 defaults = #{<<"instances">> => 1},
84 format_items = map
85 }.
86
87 -spec check_cache_last_options(mongooseim:host_type()) -> 'cache' | 'no_cache'.
88 check_cache_last_options(HostType) ->
89 %% if extauth_cache is enabled, then a mod_last module must also be enabled
90
:-(
case get_cache_option(HostType) of
91
:-(
false -> no_cache;
92 {true, _CacheTime} ->
93
:-(
case get_mod_last_configured(HostType) of
94 no_mod_last ->
95
:-(
?LOG_ERROR(#{what => mod_last_required_by_extauth,
96 text => <<"extauth configured with extauth_cache,"
97 " but mod_last is not enabled">>,
98
:-(
host_type => HostType}),
99
:-(
no_cache;
100
:-(
_ -> cache
101 end
102 end.
103
104 -spec supports_sasl_module(binary(), cyrsasl:sasl_module()) -> boolean().
105
:-(
supports_sasl_module(_, Module) -> Module =:= cyrsasl_plain.
106
107 -spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()}
108 | {error, any()}.
109 authorize(Creds) ->
110
:-(
ejabberd_auth:authorize_with_check_password(?MODULE, Creds).
111
112 -spec check_password(HostType :: mongooseim:host_type(),
113 LUser :: jid:luser(),
114 LServer :: jid:lserver(),
115 Password :: binary()) -> boolean().
116 check_password(HostType, LUser, LServer, Password) ->
117
:-(
case get_cache_option(HostType) of
118
:-(
false -> check_password_extauth(HostType, LUser, LServer, Password);
119
:-(
{true, CacheTime} -> check_password_cache(HostType, LUser, LServer, Password, CacheTime)
120 end.
121
122
123 -spec check_password(HostType :: mongooseim:host_type(),
124 LUser :: jid:luser(),
125 LServer :: jid:lserver(),
126 Password :: binary(),
127 Digest :: binary(),
128 DigestGen :: fun()) -> boolean().
129 check_password(HostType, LUser, LServer, Password, _Digest, _DigestGen) ->
130
:-(
check_password(HostType, LUser, LServer, Password).
131
132
133 -spec set_password(HostType :: mongooseim:host_type(),
134 LUser :: jid:luser(),
135 LServer :: jid:lserver(),
136 Password :: binary()) -> ok | {error, not_allowed}.
137 set_password(HostType, LUser, LServer, Password) ->
138
:-(
case extauth:set_password(HostType, LUser, LServer, Password) of
139 true ->
140
:-(
UseCache = get_cache_option(HostType),
141
:-(
maybe_set_password_internal(UseCache, HostType, LUser, LServer, Password);
142
:-(
_ -> {error, unknown_problem}
143 end.
144
145 maybe_set_password_internal(false, _, _, _, _) ->
146
:-(
ok;
147 maybe_set_password_internal({true, _}, HostType, LUser, LServer, Password) ->
148
:-(
set_password_internal(HostType, LUser, LServer, Password).
149
150
151 -spec try_register(HostType :: mongooseim:host_type(),
152 LUser :: jid:luser(),
153 LServer :: jid:lserver(),
154 Password :: binary()
155 ) -> ok | {error, not_allowed}.
156 try_register(HostType, LUser, LServer, Password) ->
157
:-(
case get_cache_option(HostType) of
158
:-(
false -> extauth:try_register(HostType, LUser, LServer, Password);
159
:-(
{true, _CacheTime} -> try_register_external_cache(HostType, LUser, LServer, Password)
160 end.
161
162
163 -spec get_registered_users(HostType :: mongooseim:host_type(),
164 LServer :: jid:lserver(),
165 Opts :: list()) -> [jid:simple_bare_jid()].
166 get_registered_users(HostType, LServer, Opts) ->
167
:-(
ejabberd_auth_internal:get_registered_users(HostType, LServer, Opts).
168
169
170 -spec get_registered_users_number(HostType :: mongooseim:host_type(),
171 LServer :: jid:lserver(),
172 Opts :: list()) -> non_neg_integer().
173 get_registered_users_number(HostType, LServer, Opts) ->
174
:-(
ejabberd_auth_internal:get_registered_users_number(HostType, LServer, Opts).
175
176
177 %% @doc The password can only be returned if cache is enabled, cached info
178 %% exists and is fresh enough.
179 -spec get_password(HostType :: mongooseim:host_type(),
180 LUser :: jid:luser(),
181 LServer :: jid:lserver()) -> binary() | false.
182 get_password(HostType, LUser, LServer) ->
183
:-(
case get_cache_option(HostType) of
184
:-(
false -> false;
185
:-(
{true, CacheTime} -> get_password_cache(HostType, LUser, LServer, CacheTime)
186 end.
187
188
189 -spec get_password_s(HostType :: mongooseim:host_type(),
190 LUser :: jid:luser(),
191 LServer :: jid:lserver()) -> binary().
192 get_password_s(HostType, LUser, LServer) ->
193
:-(
case get_password(HostType, LUser, LServer) of
194
:-(
false -> <<"">>;
195
:-(
Other -> Other
196 end.
197
198
199 -spec does_user_exist(HostType :: mongooseim:host_type(),
200 LUser :: jid:luser(),
201 LServer :: jid:lserver()) -> boolean() | {error, atom()}.
202 does_user_exist(HostType, LUser, LServer) ->
203
:-(
try extauth:does_user_exist(HostType, LUser, LServer) of
204
:-(
Res -> Res
205 catch
206
:-(
_:Error -> {error, Error}
207 end.
208
209
210 -spec remove_user(HostType :: mongooseim:host_type(),
211 User :: jid:luser(),
212 Server :: jid:lserver()) -> ok | {error, not_allowed}.
213 remove_user(HostType, LUser, LServer) ->
214
:-(
case extauth:remove_user(HostType, LUser, LServer) of
215
:-(
false -> {error, not_allowed};
216 true ->
217
:-(
case get_cache_option(HostType) of
218
:-(
false -> ok;
219 {true, _CacheTime} ->
220
:-(
ejabberd_auth_internal:remove_user(HostType, LUser, LServer)
221 end,
222
:-(
ok
223 end.
224
225 -spec supported_features() -> [atom()].
226
:-(
supported_features() -> [dynamic_domains].
227
228 %%%
229 %%% Extauth cache management
230 %%%
231
232 %% FIXME there is no such option in config spec
233 -spec get_cache_option(mongooseim:host_type()) -> false | {true, CacheTime::integer()}.
234 get_cache_option(HostType) ->
235
:-(
case mongoose_config:lookup_opt({extauth_cache, HostType}) of
236
:-(
{ok, CacheTime} -> {true, CacheTime};
237
:-(
{error, not_found} -> false
238 end.
239
240 -spec check_password_extauth(HostType :: mongooseim:host_type(),
241 LUser :: jid:luser(),
242 LServer :: jid:lserver(),
243 Password :: binary()) -> boolean().
244 check_password_extauth(HostType, LUser, LServer, Password) ->
245
:-(
extauth:check_password(HostType, LUser, LServer, Password) andalso Password /= "".
246
247 -spec check_password_cache(HostType :: mongooseim:host_type(),
248 LUser :: jid:luser(),
249 LServer :: jid:lserver(),
250 Password :: binary(),
251 CacheTime :: integer()) -> boolean().
252 check_password_cache(HostType, LUser, LServer, Password, CacheTime) ->
253
:-(
case get_last_access(HostType, LUser, LServer) of
254 online ->
255
:-(
check_password_internal(HostType, LUser, LServer, Password);
256 never ->
257
:-(
check_password_external_cache(HostType, LUser, LServer, Password);
258 mod_last_required ->
259
:-(
?LOG_ERROR(#{what => mod_last_required_by_extauth,
260 text => <<"extauth configured with extauth_cache but "
261 "mod_last is not enabled">>,
262
:-(
user => LUser, server => LServer}),
263
:-(
check_password_external_cache(HostType, LUser, LServer, Password);
264 TimeStamp ->
265 %% If last access exists, compare last access with cache refresh time
266
:-(
case is_fresh_enough(TimeStamp, CacheTime) of
267 %% If no need to refresh, check password against Mnesia
268 true ->
269
:-(
check_caches(HostType, LUser, LServer, Password);
270 %% Else (need to refresh), check in extauth and cache result
271 false ->
272
:-(
check_password_external_cache(HostType, LUser, LServer, Password)
273 end
274 end.
275
276 check_caches(HostType, LUser, LServer, Password) ->
277
:-(
case check_password_internal(HostType, LUser, LServer, Password) of
278
:-(
true -> true;
279
:-(
false -> check_password_external_cache(HostType, LUser, LServer, Password)
280 end.
281
282 get_password_internal(HostType, LUser, LServer) ->
283
:-(
ejabberd_auth_internal:get_password(HostType, LUser, LServer).
284
285
286 -spec get_password_cache(HostType :: mongooseim:host_type(),
287 LUser :: jid:luser(),
288 LServer :: jid:lserver(),
289 CacheTime :: integer()) -> false | binary().
290 get_password_cache(HostType, LUser, LServer, CacheTime) ->
291
:-(
case get_last_access(HostType, LUser, LServer) of
292 online ->
293
:-(
get_password_internal(HostType, LUser, LServer);
294 never ->
295
:-(
false;
296 mod_last_required ->
297
:-(
?LOG_ERROR(#{what => mod_last_required_by_extauth,
298 text => <<"extauth configured with extauth_cache but "
299 "mod_last is not enabled">>,
300
:-(
user => LUser, server => LServer}),
301
:-(
false;
302 TimeStamp ->
303
:-(
case is_fresh_enough(TimeStamp, CacheTime) of
304 true ->
305
:-(
get_password_internal(HostType, LUser, LServer);
306 false ->
307
:-(
false
308 end
309 end.
310
311
312 %% @doc Check the password using extauth; if success then cache it
313 check_password_external_cache(HostType, LUser, LServer, Password) ->
314
:-(
case check_password_extauth(HostType, LUser, LServer, Password) of
315 true ->
316 %% FIXME: here we must provide a host type as a first argument
317 %% for set_password_internal/4, current implementation will
318 %% not work with dynamic domains.
319
:-(
set_password_internal(LServer, LUser, LServer, Password), true;
320 false ->
321
:-(
false
322 end.
323
324
325 %% @doc Try to register using extauth; if success then cache it
326 -spec try_register_external_cache(HostType :: mongooseim:host_type(),
327 LUser :: jid:luser(),
328 LServer :: jid:lserver(),
329 Password :: binary()) -> ok | {error, not_allowed}.
330 try_register_external_cache(HostType, LUser, LServer, Password) ->
331
:-(
case extauth:try_register(HostType, LUser, LServer, Password) of
332 ok = R ->
333
:-(
set_password_internal(HostType, LUser, LServer, Password),
334
:-(
R;
335
:-(
_ -> {error, not_allowed}
336 end.
337
338
339 -spec check_password_internal(HostType :: mongooseim:host_type(),
340 LUser :: jid:luser(),
341 LServer :: jid:lserver(),
342 Password :: binary()) -> boolean().
343 check_password_internal(HostType, LUser, LServer, Password) ->
344 %% FIXME: here we must provide a host type as a first argument
345 %% for ejabberd_auth_internal:check_password/4, current implementation
346 %% will not work with dynamic domains.
347
:-(
ejabberd_auth_internal:check_password(HostType, LUser, LServer, Password).
348
349
350 -spec set_password_internal(HostType :: mongooseim:host_type(),
351 LUser :: jid:luser(),
352 LServer :: jid:lserver(),
353 Password :: binary()) -> ok | {error, invalid_jid}.
354 set_password_internal(HostType, LUser, LServer, Password) ->
355
:-(
ejabberd_auth_internal:set_password(HostType, LUser, LServer, Password).
356
357
358 -spec is_fresh_enough(TimeLast :: integer(),
359 CacheTime :: integer()) -> boolean().
360 is_fresh_enough(TimeStampLast, CacheTime) ->
361
:-(
Now = erlang:system_time(second),
362
:-(
(TimeStampLast + CacheTime > Now).
363
364
365 %% @doc Code copied from mod_configure.erl
366 %% Code copied from web/ejabberd_web_admin.erl
367 -spec get_last_access(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
368 online | never | mod_last_required | integer().
369 get_last_access(HostType, LUser, LServer) ->
370
:-(
JID = jid:make_noprep(LUser, LServer, <<>>),
371
:-(
case ejabberd_sm:get_user_resources(JID) of
372 [] ->
373
:-(
case get_last_info(HostType, LUser, LServer) of
374 mod_last_required ->
375
:-(
mod_last_required;
376 not_found ->
377
:-(
never;
378 {ok, Timestamp, _Status} ->
379
:-(
Timestamp
380 end;
381 _ ->
382
:-(
online
383 end.
384
385 -spec get_last_info(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
386 {ok, mod_last:timestamp(), mod_last:status()} | not_found | mod_last_required.
387 get_last_info(HostType, LUser, LServer) ->
388
:-(
case gen_mod:is_loaded(HostType, mod_last) of
389
:-(
true -> mod_last:get_last_info(HostType, LUser, LServer);
390
:-(
_ -> mod_last_required
391 end.
392
393 -spec get_mod_last_configured(mongooseim:host_type()) -> mod_last | mod_last_rdbms | no_mod_last.
394 get_mod_last_configured(HostType) ->
395
:-(
ML = is_configured(HostType, mod_last),
396
:-(
MLO = is_configured(HostType, mod_last_rdbms),
397
:-(
case {ML, MLO} of
398
:-(
{true, _} -> mod_last;
399
:-(
{false, true} -> mod_last_rdbms;
400
:-(
{false, false} -> no_mod_last
401 end.
402
403 is_configured(HostType, Module) ->
404
:-(
maps:is_key(Module, mongoose_config:get_opt({modules, HostType})).
Line Hits Source