168 lines
5.7 KiB
Erlang
168 lines
5.7 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2021-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
%%
|
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
%% you may not use this file except in compliance with the License.
|
|
%% You may obtain a copy of the License at
|
|
%%
|
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
%%
|
|
%% Unless required by applicable law or agreed to in writing, software
|
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
%% See the License for the specific language governing permissions and
|
|
%% limitations under the License.
|
|
%%--------------------------------------------------------------------
|
|
|
|
-module(emqx_authn).
|
|
|
|
-behaviour(emqx_config_backup).
|
|
|
|
-export([
|
|
providers/0,
|
|
check_config/1,
|
|
check_config/2,
|
|
%% for telemetry information
|
|
get_enabled_authns/0
|
|
]).
|
|
|
|
-export([merge_config/1, merge_config_local/2, import_config/1]).
|
|
|
|
-include("emqx_authn.hrl").
|
|
|
|
providers() ->
|
|
[
|
|
{{password_based, built_in_database}, emqx_authn_mnesia},
|
|
{{password_based, mysql}, emqx_authn_mysql},
|
|
{{password_based, postgresql}, emqx_authn_pgsql},
|
|
{{password_based, mongodb}, emqx_authn_mongodb},
|
|
{{password_based, redis}, emqx_authn_redis},
|
|
{{password_based, http}, emqx_authn_http},
|
|
{jwt, emqx_authn_jwt},
|
|
{{scram, built_in_database}, emqx_enhanced_authn_scram_mnesia}
|
|
].
|
|
|
|
check_config(Config) ->
|
|
check_config(Config, #{}).
|
|
|
|
check_config(Config, Opts) ->
|
|
case do_check_config(Config, Opts) of
|
|
#{?CONF_NS_ATOM := Checked} -> Checked;
|
|
#{?CONF_NS_BINARY := WithDefaults} -> WithDefaults
|
|
end.
|
|
|
|
do_check_config(#{<<"mechanism">> := Mec0} = Config, Opts) ->
|
|
Mec = atom(Mec0, #{error => unknown_mechanism}),
|
|
Key =
|
|
case maps:get(<<"backend">>, Config, false) of
|
|
false -> Mec;
|
|
Backend -> {Mec, atom(Backend, #{error => unknown_backend})}
|
|
end,
|
|
case lists:keyfind(Key, 1, providers()) of
|
|
false ->
|
|
Reason =
|
|
case Key of
|
|
{M, B} ->
|
|
#{mechanism => M, backend => B};
|
|
M ->
|
|
#{mechanism => M}
|
|
end,
|
|
throw(Reason#{error => unknown_authn_provider});
|
|
{_, ProviderModule} ->
|
|
do_check_config_maybe_throw(ProviderModule, Config, Opts)
|
|
end;
|
|
do_check_config(Config, _Opts) when is_map(Config) ->
|
|
throw(#{
|
|
error => invalid_config,
|
|
reason => "mechanism_field_required"
|
|
}).
|
|
|
|
do_check_config_maybe_throw(ProviderModule, Config0, Opts) ->
|
|
Config = #{?CONF_NS_BINARY => Config0},
|
|
case emqx_hocon:check(ProviderModule, Config, Opts#{atom_key => true}) of
|
|
{ok, Checked} ->
|
|
Checked;
|
|
{error, Reason} ->
|
|
throw(Reason)
|
|
end.
|
|
|
|
%% The atoms have to be loaded already,
|
|
%% which might be an issue for plugins which are loaded after node boot
|
|
%% but they should really manage their own configs in that case.
|
|
atom(Bin, ErrorContext) ->
|
|
try
|
|
binary_to_existing_atom(Bin, utf8)
|
|
catch
|
|
_:_ ->
|
|
throw(ErrorContext#{value => Bin})
|
|
end.
|
|
|
|
-spec get_enabled_authns() ->
|
|
#{
|
|
authenticators => [authenticator_id()],
|
|
overridden_listeners => #{authenticator_id() => pos_integer()}
|
|
}.
|
|
get_enabled_authns() ->
|
|
%% at the moment of writing, `emqx_authentication:list_chains/0'
|
|
%% result is always wrapped in `{ok, _}', and it cannot return any
|
|
%% error values.
|
|
{ok, Chains} = emqx_authentication:list_chains(),
|
|
AuthnTypes = lists:usort([
|
|
Type
|
|
|| #{authenticators := As} <- Chains,
|
|
#{id := Type, enable := true} <- As
|
|
]),
|
|
OverriddenListeners =
|
|
lists:foldl(
|
|
fun
|
|
(#{name := ?GLOBAL}, Acc) ->
|
|
Acc;
|
|
(#{authenticators := As}, Acc) ->
|
|
lists:foldl(fun tally_authenticators/2, Acc, As)
|
|
end,
|
|
#{},
|
|
Chains
|
|
),
|
|
#{
|
|
authenticators => AuthnTypes,
|
|
overridden_listeners => OverriddenListeners
|
|
}.
|
|
|
|
tally_authenticators(#{id := AuthenticatorName}, Acc) ->
|
|
maps:update_with(AuthenticatorName, fun(N) -> N + 1 end, 1, Acc).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Data backup
|
|
%%------------------------------------------------------------------------------
|
|
|
|
-define(IMPORT_OPTS, #{override_to => cluster}).
|
|
|
|
import_config(RawConf) ->
|
|
AuthnList = authn_list(maps:get(?CONF_NS_BINARY, RawConf, [])),
|
|
OldAuthnList = emqx:get_raw_config([?CONF_NS_BINARY], []),
|
|
MergedAuthnList = emqx_utils:merge_lists(
|
|
OldAuthnList, AuthnList, fun emqx_authentication:authenticator_id/1
|
|
),
|
|
case emqx_conf:update([?CONF_NS_ATOM], MergedAuthnList, ?IMPORT_OPTS) of
|
|
{ok, #{raw_config := NewRawConf}} ->
|
|
{ok, #{root_key => ?CONF_NS_ATOM, changed => changed_paths(OldAuthnList, NewRawConf)}};
|
|
Error ->
|
|
{error, #{root_key => ?CONF_NS_ATOM, reason => Error}}
|
|
end.
|
|
|
|
changed_paths(OldAuthnList, NewAuthnList) ->
|
|
KeyFun = fun emqx_authentication:authenticator_id/1,
|
|
Changed = maps:get(changed, emqx_utils:diff_lists(NewAuthnList, OldAuthnList, KeyFun)),
|
|
[[?CONF_NS_BINARY, emqx_authentication:authenticator_id(OldAuthn)] || {OldAuthn, _} <- Changed].
|
|
|
|
authn_list(Authn) when is_list(Authn) ->
|
|
Authn;
|
|
authn_list(Authn) when is_map(Authn) ->
|
|
[Authn].
|
|
|
|
merge_config(AuthNs) ->
|
|
emqx_authn_api:update_config([?CONF_NS_ATOM], {merge_authenticators, AuthNs}).
|
|
|
|
merge_config_local(AuthNs, Opts) ->
|
|
emqx:update_config([?CONF_NS_ATOM], {merge_authenticators, AuthNs}, Opts).
|