From 0fe1e40a9d6edb6f70d76f60b8f3dc95ae6a34eb Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Wed, 18 Sep 2019 18:45:51 +0300 Subject: [PATCH] JWT-only authentication for some users (#3012) --- src/ejabberd_auth.erl | 38 +++++++++++++++++++++++++------------- src/ejabberd_auth_jwt.erl | 17 ++++++++++++++--- src/ejabberd_option.erl | 8 ++++++++ src/ejabberd_options.erl | 7 +++++-- 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 2f4983500..9a7479e40 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -76,7 +76,7 @@ {ets_cache:tag(), {ok, password()} | {error, db_failure | not_allowed}}. -callback remove_user(binary(), binary()) -> ok | {error, db_failure | not_allowed}. -callback user_exists(binary(), binary()) -> {ets_cache:tag(), boolean() | {error, db_failure}}. --callback check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean()}. +-callback check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean() | {stop, boolean()}}. -callback try_register(binary(), binary(), password()) -> {ets_cache:tag(), {ok, password()} | {error, exists | db_failure | not_allowed}}. -callback get_users(binary(), opts()) -> [{binary(), binary()}]. @@ -237,17 +237,20 @@ check_password_with_authmodule(User, AuthzId, Server, Password, Digest, DigestGe error -> false; LAuthzId -> - lists:foldl( - fun(Mod, false) -> - case db_check_password( - LUser, LAuthzId, LServer, Password, - Digest, DigestGen, Mod) of - true -> {true, Mod}; - false -> false - end; - (_, Acc) -> - Acc - end, false, auth_modules(LServer)) + untag_stop( + lists:foldl( + fun(Mod, false) -> + case db_check_password( + LUser, LAuthzId, LServer, Password, + Digest, DigestGen, Mod) of + true -> {true, Mod}; + false -> false; + {stop, true} -> {stop, {true, Mod}}; + {stop, false} -> {stop, false} + end; + (_, Acc) -> + Acc + end, false, auth_modules(LServer))) end; _ -> false @@ -484,7 +487,11 @@ remove_user(User, Server, Password) -> <<"">>, undefined, Mod) of true -> db_remove_user(LUser, LServer, Mod); + {stop, true} -> + db_remove_user(LUser, LServer, Mod); false -> + {error, not_allowed}; + {stop, false} -> {error, not_allowed} end end, {error, not_allowed}, auth_modules(Server)) of @@ -654,7 +661,9 @@ db_check_password(User, AuthzId, Server, ProvidedPassword, case Mod:check_password( User, AuthzId, Server, ProvidedPassword) of {CacheTag, true} -> {CacheTag, {ok, ProvidedPassword}}; - {CacheTag, false} -> {CacheTag, error} + {CacheTag, {stop, true}} -> {CacheTag, {ok, ProvidedPassword}}; + {CacheTag, false} -> {CacheTag, error}; + {CacheTag, {stop, false}} -> {CacheTag, error} end end) of {ok, _} -> @@ -891,6 +900,9 @@ validate_credentials(User, Server, Password) -> end end. +untag_stop({stop, Val}) -> Val; +untag_stop(Val) -> Val. + import_info() -> [{<<"users">>, 3}]. diff --git a/src/ejabberd_auth_jwt.erl b/src/ejabberd_auth_jwt.erl index 3b3698d1c..33c6cc601 100644 --- a/src/ejabberd_auth_jwt.erl +++ b/src/ejabberd_auth_jwt.erl @@ -31,7 +31,7 @@ -export([start/1, stop/1, check_password/4, store_type/1, plain_password_required/1, - user_exists/2 + user_exists/2, use_cache/1 ]). -include("xmpp.hrl"). @@ -55,7 +55,7 @@ plain_password_required(_Host) -> true. store_type(_Host) -> external. --spec check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean()}. +-spec check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean() | {stop, boolean()}}. check_password(User, AuthzId, Server, Token) -> %% MREMOND: Should we move the AuthzId check at a higher level in %% the call stack? @@ -64,12 +64,23 @@ check_password(User, AuthzId, Server, Token) -> true -> if Token == <<"">> -> {nocache, false}; true -> - {nocache, check_jwt_token(User, Server, Token)} + Res = check_jwt_token(User, Server, Token), + Rule = ejabberd_option:jwt_auth_only_rule(Server), + case acl:match_rule(Server, Rule, + jid:make(User, Server, <<"">>)) of + deny -> + {nocache, Res}; + allow -> + {nocache, {stop, Res}} + end end end. user_exists(_User, _Host) -> {nocache, false}. +use_cache(_) -> + false. + %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- diff --git a/src/ejabberd_option.erl b/src/ejabberd_option.erl index d7fd65cfe..9e6b14043 100644 --- a/src/ejabberd_option.erl +++ b/src/ejabberd_option.erl @@ -50,6 +50,7 @@ -export([host_config/0]). -export([hosts/0]). -export([include_config_file/0, include_config_file/1]). +-export([jwt_auth_only_rule/0, jwt_auth_only_rule/1]). -export([jwt_key/0, jwt_key/1]). -export([language/0, language/1]). -export([ldap_backups/0, ldap_backups/1]). @@ -424,6 +425,13 @@ include_config_file() -> include_config_file(Host) -> ejabberd_config:get_option({include_config_file, Host}). +-spec jwt_auth_only_rule() -> atom(). +jwt_auth_only_rule() -> + jwt_auth_only_rule(global). +-spec jwt_auth_only_rule(global | binary()) -> atom(). +jwt_auth_only_rule(Host) -> + ejabberd_config:get_option({jwt_auth_only_rule, Host}). + -spec jwt_key() -> jose_jwk:key() | 'undefined'. jwt_key() -> jwt_key(global). diff --git a/src/ejabberd_options.erl b/src/ejabberd_options.erl index a83f0add7..7764f451b 100644 --- a/src/ejabberd_options.erl +++ b/src/ejabberd_options.erl @@ -409,7 +409,9 @@ opt_type(jwt_key) -> {error, Reason} -> econf:fail({read_file, Reason, Path}) end - end). + end); +opt_type(jwt_auth_only_rule) -> + econf:atom(). %% We only define the types of options that cannot be derived %% automatically by tools/opt_type.sh script @@ -635,7 +637,8 @@ options() -> {websocket_origin, []}, {websocket_ping_interval, timer:seconds(60)}, {websocket_timeout, timer:minutes(5)}, - {jwt_key, undefined}]. + {jwt_key, undefined}, + {jwt_auth_only_rule, none}]. -spec globals() -> [atom()]. globals() ->