Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to customize auth module list per listener #3539

Merged
merged 4 commits into from
Feb 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions big_tests/default.spec
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
{suites, "tests", domain_removal_SUITE}.
{suites, "tests", mam_send_message_SUITE}.
{suites, "tests", dynamic_domains_SUITE}.
{suites, "tests", auth_methods_for_c2s_SUITE}.

{config, ["test.config"]}.
{logdir, "ct_report"}.
Expand Down
1 change: 1 addition & 0 deletions big_tests/dynamic_domains.spec
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
{suites, "tests", xep_0352_csi_SUITE}.

{suites, "tests", domain_removal_SUITE}.
{suites, "tests", auth_methods_for_c2s_SUITE}.

{config, ["dynamic_domains.config", "test.config"]}.

Expand Down
82 changes: 82 additions & 0 deletions big_tests/tests/auth_methods_for_c2s_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
-module(auth_methods_for_c2s_SUITE).

-compile([export_all, nowarn_export_all]).

-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("exml/include/exml.hrl").

-import(distributed_helper, [mim/0, rpc/4]).

all() ->
[{group, two_methods_enabled}].

groups() ->
[{two_methods_enabled, [parallel],
[can_login_with_allowed_method,
cannot_login_with_not_allowed_method,
can_login_to_another_listener]}].

init_per_suite(Config) ->
Config0 = escalus:init_per_suite(Config),
Config1 = ejabberd_node_utils:init(Config0),
ejabberd_node_utils:backup_config_file(Config1),
Config1.

end_per_suite(Config) ->
ejabberd_node_utils:restore_config_file(Config),
ejabberd_node_utils:restart_application(mongooseim),
escalus:end_per_suite(Config).

init_per_group(_, Config) ->
modify_config_and_restart(Config),
escalus_cleaner:start(Config).

end_per_group(_, _Config) ->
escalus_fresh:clean().

init_per_testcase(TC, Config) ->
Spec = escalus_fresh:freshen_spec(Config, alice),
Clean = register_internal_user(Spec),
[{clean_fn, Clean}, {spec, Spec}|escalus:init_per_testcase(TC, Config)].
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaces


end_per_testcase(TC, Config) ->
Clean = proplists:get_value(clean_fn, Config),
Clean(),
escalus:end_per_testcase(TC, Config).

modify_config_and_restart(Config) ->
NewConfigValues = [{auth_method, "internal]\n [auth.dummy"},
{auth_method_opts, false},
{allowed_auth_methods, "\"internal\""}],
ejabberd_node_utils:modify_config_file(NewConfigValues, Config),
ejabberd_node_utils:restart_application(mongooseim).

can_login_with_allowed_method(Config) ->
Spec = proplists:get_value(spec, Config),
{ok, _, _} = escalus_connection:start(Spec).

cannot_login_with_not_allowed_method(Config) ->
Spec = proplists:get_value(spec, Config),
{error, _} = escalus_connection:start([{password, <<"wrong">>}|Spec]).

can_login_to_another_listener(Config) ->
Spec = proplists:get_value(spec, Config),
Spec2 = [{port, ct:get_config({hosts, mim, c2s_tls_port})},
{password, <<"wrong">>}|Spec],
{ok, _, _} = escalus_connection:start(Spec2).

%% Helpers

%% If dummy backend is enabled, it is not possible to create new users
%% (we check if an user does exist before registering the user).
register_internal_user(Spec) ->
#{username := User, server := Server,
password := Password} = maps:from_list(Spec),
LUser = jid:nodeprep(User),
LServer = escalus_utils:jid_to_lower(Server),
HostType = domain_helper:host_type(),
rpc(mim(), ejabberd_auth_internal, try_register,
[HostType, LUser, LServer, Password]),
fun() -> rpc(mim(), ejabberd_auth_internal, remove_user,
[HostType, LUser, LServer]) end.
11 changes: 11 additions & 0 deletions doc/configuration/listen.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ The value of the access rule needs to be either the shaper name or the string `"

Enables ZLIB support, the integer value is a limit for a decompressed output size in bytes (to prevent a successful [ZLIB bomb attack](https://xmpp.org/community/security-notices/uncontrolled-resource-consumption-with-highly-compressed-xmpp-stanzas.html)).

### `listen.c2s.allowed_auth_methods`

* **Syntax:** array of strings. Allowed values: `"internal"`, `"rdbms"`, `"external"`, `"anonymous"`, `"ldap"`, `"jwt"`, `"riak"`, `"http"`, `"pki"`, `"dummy"`
* **Default:** not set
* **Example:** `allowed_auth_methods = ["internal"]`

A subset of enabled methods to login with for this listener.
This option allows to enable only some backends.
It is useful, if you want to have several listeners for different type of users (for example, some users use PKI while other users use LDAP auth).
Same syntax as for `auth.methods` option.

## TLS options for C2S

The following options allow enabling and configuring TLS which makes the client-to-server conenctions secure.
Expand Down
3 changes: 2 additions & 1 deletion include/ejabberd_c2s.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
csi_buffer = [] :: [mongoose_acc:t()],
hibernate_after = 0 :: non_neg_integer(),
replaced_pids = [] :: [{MonitorRef :: reference(), ReplacedPid :: pid()}],
handlers = #{} :: #{ term() => {module(), atom(), term()} }
handlers = #{} :: #{ term() => {module(), atom(), term()} },
cred_opts :: mongoose_credentials:opts()
}).
-type aux_key() :: atom().
-type aux_value() :: any().
Expand Down
3 changes: 3 additions & 0 deletions rel/files/mongooseim.toml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@
{{#c2s_dhfile}}
tls.dhfile = {{{c2s_dhfile}}}
{{/c2s_dhfile}}
{{#allowed_auth_methods}}
allowed_auth_methods = [{{{allowed_auth_methods}}}]
{{/allowed_auth_methods}}
{{#secondary_c2s}}

{{{secondary_c2s}}}
Expand Down
18 changes: 15 additions & 3 deletions src/auth/ejabberd_auth.erl
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
-export([check_digest/4]).

-export([auth_modules/1,
auth_methods/1]).
auth_methods/1,
auth_modules_for_host_type/1,
methods_to_modules/1]).

%% Library functions for reuse in ejabberd_auth_* modules
-export([authorize_with_check_password/2]).
Expand Down Expand Up @@ -132,7 +134,6 @@ supports_sasl_module(HostType, SASLModule) ->
-spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()}
| {error, not_authorized}.
authorize(Creds) ->
HostType = mongoose_credentials:host_type(Creds),
F = fun(Mod, {_CurResult, CurCreds}) ->
case mongoose_gen_auth:authorize(Mod, CurCreds) of
{ok, NewCreds} ->
Expand All @@ -143,7 +144,7 @@ authorize(Creds) ->
end
end,
Opts = #{default => {not_authorized, Creds}, metric => authorize},
case call_auth_modules_for_host_type(HostType, F, Opts) of
case call_auth_modules_with_creds(Creds, F, Opts) of
Res = {ok, _Creds} -> Res;
{not_authorized, _Creds} -> {error, not_authorized}
end.
Expand Down Expand Up @@ -413,6 +414,10 @@ auth_modules(LServer) ->
-spec auth_modules_for_host_type(HostType :: mongooseim:host_type()) -> [authmodule()].
auth_modules_for_host_type(HostType) ->
Methods = auth_methods(HostType),
methods_to_modules(Methods).

-spec methods_to_modules([atom()]) -> [authmodule()].
methods_to_modules(Methods) ->
[auth_method_to_module(M) || M <- Methods].

-spec auth_methods(mongooseim:host_type()) -> [atom()].
Expand Down Expand Up @@ -508,6 +513,13 @@ call_auth_modules_for_host_type(HostType, F, Opts) ->
Modules = auth_modules_for_host_type(HostType),
call_auth_modules(Modules, F, Opts).

-spec call_auth_modules_with_creds(mongoose_credentials:t(),
mod_fun() | mod_fold_fun(), call_opts()) ->
mod_res() | [mod_res()].
call_auth_modules_with_creds(Creds, F, Opts) ->
Modules = mongoose_credentials:auth_modules(Creds),
call_auth_modules(Modules, F, Opts).

%% @doc Perform a map or a fold operation with function F over the provided Modules
-spec call_auth_modules([authmodule()], mod_fun() | mod_fold_fun(), call_opts()) ->
mod_res() | [mod_res()].
Expand Down
2 changes: 2 additions & 0 deletions src/config/mongoose_config_spec.erl
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ xmpp_listener_items(<<"c2s">>) ->
validate = positive},
<<"max_fsm_queue">> => #option{type = integer,
validate = positive},
<<"allowed_auth_methods">> => #list{items = #option{type = atom,
validate = {module, ejabberd_auth}}},
<<"tls">> => c2s_tls()};
xmpp_listener_items(<<"s2s">>) ->
#{<<"shaper">> => #option{type = atom,
Expand Down
33 changes: 17 additions & 16 deletions src/ejabberd_c2s.erl
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,27 @@
-export_type([broadcast/0, packet/0, state/0]).

-type packet() :: {jid:jid(), jid:jid(), exml:element()}.
-type sock_data() :: term().
-type start_result() :: {error, _}
| {ok, undefined | pid()}
| {ok, undefined | pid(), _}.

%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------

-spec start(_, list())
-> {'error', _} | {'ok', 'undefined' | pid()} | {'ok', 'undefined' | pid(), _}.
-spec start(sock_data(), ejabberd_listener:opts()) -> start_result().
start(SockData, Opts) ->
?SUPERVISOR_START(SockData, Opts).

-spec start_link(sock_data(), ejabberd_listener:opts()) -> start_result().
start_link(SockData, Opts) ->
p1_fsm_old:start_link(
ejabberd_c2s, [SockData, Opts], ?FSMOPTS ++ fsm_limit_opts(Opts)).
p1_fsm_old:start_link(ejabberd_c2s, {SockData, Opts},
?FSMOPTS ++ fsm_limit_opts(Opts)).

socket_type() ->
xml_stream.


%% @doc Return Username, Resource and presence information
get_presence(FsmRef) ->
p1_fsm_old:sync_send_all_state_event(FsmRef, get_presence, 1000).
Expand Down Expand Up @@ -174,14 +177,9 @@ run_remote_hook_after(Delay, Pid, HandlerName, Args) ->
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------

%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
-spec init({sock_data(), ejabberd_listener:opts()}) ->
{stop, normal} | {ok, wait_for_stream, state(), non_neg_integer()}.
init({{SockMod, Socket}, Opts}) ->
Access = case lists:keyfind(access, 1, Opts) of
{_, A} -> A;
_ -> all
Expand Down Expand Up @@ -238,6 +236,7 @@ init([{SockMod, Socket}, Opts]) ->
false -> Socket
end,
SocketMonitor = mongoose_transport:monitor(SockMod, Socket1),
CredOpts = mongoose_credentials:make_opts(Opts),
{ok, wait_for_stream, #state{server = ?MYNAME,
socket = Socket1,
sockmod = SockMod,
Expand All @@ -254,7 +253,8 @@ init([{SockMod, Socket}, Opts]) ->
shaper = Shaper,
ip = IP,
lang = ?MYLANG,
hibernate_after= HibernateAfter
hibernate_after= HibernateAfter,
cred_opts = CredOpts
},
?C2S_OPEN_TIMEOUT}
end.
Expand Down Expand Up @@ -344,8 +344,9 @@ stream_start_negotiate_features(#state{} = S) ->
end.

stream_start_features_before_auth(#state{server = Server,
host_type = HostType} = S) ->
Creds0 = mongoose_credentials:new(Server, HostType),
host_type = HostType,
cred_opts = CredOpts} = S) ->
Creds0 = mongoose_credentials:new(Server, HostType, CredOpts),
Creds = maybe_add_cert(Creds0, S),
SASLState = cyrsasl:server_new(<<"jabber">>, Server, HostType, <<>>, [], Creds),
SockMod = (S#state.sockmod):get_sockmod(S#state.socket),
Expand Down
4 changes: 4 additions & 0 deletions src/ejabberd_listener.erl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
-export([format_error/1, socket_error/6]).

-ignore_xref([start_link/0, init/1, start_listener/1, stop_listener/1]).
-export_type([opts/0, mod/0]).

-type opts() :: list().
-type mod() :: module().

-include("mongoose.hrl").

Expand Down
8 changes: 4 additions & 4 deletions src/ejabberd_socket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
%% Function:
%% Description:
%%--------------------------------------------------------------------
-spec start(atom() | tuple(), ejabberd:sockmod(),
Socket :: port(), Opts :: [{atom(), _}]) -> ok.
-spec start(ejabberd_listener:mod(), ejabberd:sockmod(),
Socket :: port(), Opts :: ejabberd_listener:opts()) -> ok.
start(Module, SockMod, Socket, Opts) ->
case Module:socket_type() of
xml_stream ->
Expand All @@ -78,8 +78,8 @@ start(Module, SockMod, Socket, Opts) ->
start_raw_stream(Module, SockMod, Socket, Opts)
end.

-spec start_raw_stream(atom() | tuple(), ejabberd:sockmod(),
Socket :: port(), Opts :: [{atom(), _}]) -> ok.
-spec start_raw_stream(ejabberd_listener:mod(), ejabberd:sockmod(),
Socket :: port(), Opts :: ejabberd_listener:opts()) -> ok.
start_raw_stream(Module, SockMod, Socket, Opts) ->
case Module:start({SockMod, Socket}, Opts) of
{ok, Pid} ->
Expand Down
2 changes: 1 addition & 1 deletion src/mongoose_client_api/mongoose_client_api.erl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ check_password(JID, Password) ->
{LUser, LServer} = jid:to_lus(JID),
case mongoose_domain_api:get_domain_host_type(LServer) of
{ok, HostType} ->
Creds0 = mongoose_credentials:new(LServer, HostType),
Creds0 = mongoose_credentials:new(LServer, HostType, #{}),
Creds1 = mongoose_credentials:set(Creds0, username, LUser),
Creds2 = mongoose_credentials:set(Creds1, password, Password),
case ejabberd_auth:authorize(Creds2) of
Expand Down
42 changes: 35 additions & 7 deletions src/mongoose_credentials.erl
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
-module(mongoose_credentials).

-export([new/2,
-export([make_opts/1,
new/3,
lserver/1,
host_type/1,
auth_modules/1,
get/2, get/3,
set/3,
extend/2,
register/3]).

-export_type([t/0]).
-export_type([t/0, opts/0]).

-record(mongoose_credentials, {lserver, host_type, registry = [], extra = []}).
-record(mongoose_credentials, {lserver, host_type, registry = [], extra = [], modules}).

-type auth_event() :: any().

Expand All @@ -22,18 +24,44 @@
registry :: [{ejabberd_gen_auth:t(), auth_event()}],
%% These values are dependent on the ejabberd_auth backend in use.
%% Each backend may require different values to be present.
extra :: [proplists:property()] }.
extra :: [proplists:property()],
modules :: [ejabberd_auth:authmodule()] }.

-spec new(jid:lserver(), binary()) -> mongoose_credentials:t().
new(LServer, HostType) when is_binary(LServer), is_binary(HostType) ->
#mongoose_credentials{lserver = LServer, host_type = HostType}.
-type opts() :: #{}.

-spec make_opts(ejabberd_listener:opts()) -> opts().
make_opts(Opts) ->
case lists:keyfind(allowed_auth_methods, 1, Opts) of
{allowed_auth_methods, Methods} ->
#{allowed_modules => ejabberd_auth:methods_to_modules(Methods)};
_ ->
#{}
end.

-spec new(jid:lserver(), binary(), opts()) -> mongoose_credentials:t().
new(LServer, HostType, Opts) when is_binary(LServer), is_binary(HostType) ->
HostTypeModules = ejabberd_auth:auth_modules_for_host_type(HostType),
Modules = filter_modules(HostTypeModules, Opts),
#mongoose_credentials{lserver = LServer, host_type = HostType,
modules = Modules}.

%% Allows to enable only some modules for some listeners
-spec filter_modules([ejabberd_auth:authmodule()], opts()) ->
[ejabberd_auth:authmodule()].
filter_modules(Modules, #{allowed_modules := AllowedModules}) ->
[M || M <- Modules, lists:member(M, AllowedModules)];
filter_modules(Modules, _) ->
Modules.

-spec host_type(t()) -> mongooseim:host_type().
host_type(#mongoose_credentials{host_type = HostType}) -> HostType.

-spec lserver(t()) -> jid:lserver().
lserver(#mongoose_credentials{lserver = S}) -> S.

-spec auth_modules(t()) -> [ejabberd_auth:authmodule()].
auth_modules(#mongoose_credentials{modules = Modules}) -> Modules.

%% @doc Calls erlang:error/2 when Key is not found!
-spec get(t(), Key) -> Value when
Key :: any(),
Expand Down
Loading