Add `auth_external_user_exists_check` option

This makes `user_check` hook work better with authentication methods
that don't have a way to determine if user exists (like is the case for
jwt and cert based authentication), and as result will improve mod_offline
and mod_mam handling of offline messages to those users. This reuses
information stored by `mod_last` for this purpose.

Should fix issue #3377.
This commit is contained in:
Paweł Chmielowski 2023-10-11 14:16:51 +02:00
parent 9acf591242
commit 12d47455ba
4 changed files with 52 additions and 20 deletions

View File

@ -398,15 +398,29 @@ user_exists(_User, <<"">>) ->
user_exists(User, Server) -> user_exists(User, Server) ->
case validate_credentials(User, Server) of case validate_credentials(User, Server) of
{ok, LUser, LServer} -> {ok, LUser, LServer} ->
lists:any( {Exists, PerformExternalUserCheck} =
fun(M) -> lists:foldl(
case db_user_exists(LUser, LServer, M) of fun(M, {Exists0, PerformExternalUserCheck0}) ->
{error, _} -> case db_user_exists(LUser, LServer, M) of
false; {{error, _}, Check} ->
Else -> {Exists0, PerformExternalUserCheck0 orelse Check};
Else {Else, Check2} ->
end {Exists0 orelse Else, PerformExternalUserCheck0 orelse Check2}
end, auth_modules(LServer)); end
end, {false, false}, auth_modules(LServer)),
case (not Exists) andalso PerformExternalUserCheck andalso
ejabberd_option:auth_external_user_exists_check(Server) andalso
gen_mod:is_loaded(Server, mod_last) of
true ->
case mod_last:get_last_info(User, Server) of
not_found ->
false;
_ ->
true
end;
_ ->
Exists
end;
_ -> _ ->
false false
end. end.
@ -420,11 +434,11 @@ user_exists_in_other_modules_loop([], _User, _Server) ->
false; false;
user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) -> user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) ->
case db_user_exists(User, Server, AuthModule) of case db_user_exists(User, Server, AuthModule) of
true -> {true, _} ->
true; true;
false -> {false, _} ->
user_exists_in_other_modules_loop(AuthModules, User, Server); user_exists_in_other_modules_loop(AuthModules, User, Server);
{error, _} -> {{error, _}, _} ->
maybe maybe
end. end.
@ -628,9 +642,9 @@ db_get_password(User, Server, Mod) ->
db_user_exists(User, Server, Mod) -> db_user_exists(User, Server, Mod) ->
case db_get_password(User, Server, Mod) of case db_get_password(User, Server, Mod) of
{ok, _} -> {ok, _} ->
true; {true, false};
not_found -> not_found ->
false; {false, false};
error -> error ->
case {Mod:store_type(Server), use_cache(Mod, Server)} of case {Mod:store_type(Server), use_cache(Mod, Server)} of
{external, true} -> {external, true} ->
@ -649,18 +663,18 @@ db_user_exists(User, Server, Mod) ->
end, end,
case Val of case Val of
{ok, _} -> {ok, _} ->
true; {true, Mod /= ejabberd_auth_anonymous} ;
not_found -> not_found ->
false; {false, Mod /= ejabberd_auth_anonymous};
error -> error ->
false; {false, Mod /= ejabberd_auth_anonymous};
{error, _} = Err -> {error, _} = Err ->
Err {Err, Mod /= ejabberd_auth_anonymous}
end; end;
{external, false} -> {external, false} ->
ets_cache:untag(Mod:user_exists(User, Server)); {ets_cache:untag(Mod:user_exists(User, Server)), Mod /= ejabberd_auth_anonymous};
_ -> _ ->
false {false, false}
end end
end. end.

View File

@ -14,6 +14,7 @@
-export([auth_cache_life_time/0]). -export([auth_cache_life_time/0]).
-export([auth_cache_missed/0]). -export([auth_cache_missed/0]).
-export([auth_cache_size/0]). -export([auth_cache_size/0]).
-export([auth_external_user_exists_check/0, auth_external_user_exists_check/1]).
-export([auth_method/0, auth_method/1]). -export([auth_method/0, auth_method/1]).
-export([auth_opts/0, auth_opts/1]). -export([auth_opts/0, auth_opts/1]).
-export([auth_password_format/0, auth_password_format/1]). -export([auth_password_format/0, auth_password_format/1]).
@ -224,6 +225,13 @@ auth_cache_missed() ->
auth_cache_size() -> auth_cache_size() ->
ejabberd_config:get_option({auth_cache_size, global}). ejabberd_config:get_option({auth_cache_size, global}).
-spec auth_external_user_exists_check() -> boolean().
auth_external_user_exists_check() ->
auth_external_user_exists_check(global).
-spec auth_external_user_exists_check(global | binary()) -> boolean().
auth_external_user_exists_check(Host) ->
ejabberd_config:get_option({auth_external_user_exists_check, Host}).
-spec auth_method() -> [atom()]. -spec auth_method() -> [atom()].
auth_method() -> auth_method() ->
auth_method(global). auth_method(global).

View File

@ -81,6 +81,8 @@ opt_type(auth_password_format) ->
econf:enum([plain, scram]); econf:enum([plain, scram]);
opt_type(auth_scram_hash) -> opt_type(auth_scram_hash) ->
econf:enum([sha, sha256, sha512]); econf:enum([sha, sha256, sha512]);
opt_type(auth_external_user_exists_check) ->
econf:bool();
opt_type(auth_use_cache) -> opt_type(auth_use_cache) ->
econf:bool(); econf:bool();
opt_type(c2s_cafile) -> opt_type(c2s_cafile) ->
@ -542,6 +544,7 @@ options() ->
{auth_opts, []}, {auth_opts, []},
{auth_password_format, plain}, {auth_password_format, plain},
{auth_scram_hash, sha}, {auth_scram_hash, sha},
{auth_external_user_exists_check, true},
{auth_use_cache, {auth_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end}, fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{c2s_cafile, undefined}, {c2s_cafile, undefined},

View File

@ -395,6 +395,13 @@ doc() ->
"You shouldn't change this if you already have passwords generated with " "You shouldn't change this if you already have passwords generated with "
"a different algorithm - users that have such passwords will not be able " "a different algorithm - users that have such passwords will not be able "
"to authenticate. The default value is 'sha'.")}}, "to authenticate. The default value is 'sha'.")}},
{auth_external_user_exists_check,
#{value => "true | false",
desc =>
?T("Supplement check for user existence based on 'mod_last' data, for authentication "
"methods that don't have a way to reliable tell if user exists (like is the case for "
"'jwt' and certificate based authentication). This helps with processing offline message "
"for those users. The default value is 'true'.")}},
{auth_use_cache, {auth_use_cache,
#{value => "true | false", #{value => "true | false",
desc => desc =>