]> granicus.if.org Git - ejabberd/commitdiff
JWT-only authentication for some users (#3012)
authorAlexey Shchepin <alexey@process-one.net>
Wed, 18 Sep 2019 15:45:51 +0000 (18:45 +0300)
committerAlexey Shchepin <alexey@process-one.net>
Wed, 18 Sep 2019 15:46:24 +0000 (18:46 +0300)
src/ejabberd_auth.erl
src/ejabberd_auth_jwt.erl
src/ejabberd_option.erl
src/ejabberd_options.erl

index 2f4983500ac3d4297cf5d0d047b529416259b302..9a7479e40d6d1229443a6deb2388a8b24c4b8a04 100644 (file)
@@ -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}].
 
index 3b3698d1cd54a7d7a9e7234615eff4501149277d..33c6cc601c6834acc59a3222d44d3f38efa20633 100644 (file)
@@ -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
 %%%----------------------------------------------------------------------
index d7fd65cfed1fd7c25df70bc728167b9be3a4eccf..9e6b14043d1fb162236ab1d57447f39a13c2cebc 100644 (file)
@@ -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).
index a83f0add717d2b16dac10edc30f4dc95834b9b00..7764f451b4ff1bb74d3cf936faa022abff37a16b 100644 (file)
@@ -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() ->