refactor(authn): check authenticator config with provider module
mainly two changes: 1. the schema is simplified at root level, per-authenticator checks are done after the type can be identified 2. the config handling part is split out from emqx_authentication module to emqx_authentication_config module
This commit is contained in:
parent
e2f9b111b6
commit
1b9c082563
|
@ -14,33 +14,30 @@
|
|||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% @doc Authenticator management API module.
|
||||
%% Authentication is a core functionality of MQTT,
|
||||
%% the 'emqx' APP provides APIs for other APPs to implement
|
||||
%% the authentication callbacks.
|
||||
-module(emqx_authentication).
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(hocon_schema).
|
||||
-behaviour(emqx_config_handler).
|
||||
|
||||
-include("emqx.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-export([ roots/0
|
||||
, fields/1
|
||||
]).
|
||||
|
||||
-export([ pre_config_update/2
|
||||
, post_config_update/4
|
||||
]).
|
||||
|
||||
%% The authentication entrypoint.
|
||||
-export([ authenticate/2
|
||||
]).
|
||||
|
||||
-export([ initialize_authentication/2 ]).
|
||||
|
||||
%% Authenticator manager process start/stop
|
||||
-export([ start_link/0
|
||||
, stop/0
|
||||
, get_providers/0
|
||||
]).
|
||||
|
||||
-export([ register_provider/2
|
||||
%% Authenticator management APIs
|
||||
-export([ initialize_authentication/2
|
||||
, register_provider/2
|
||||
, register_providers/1
|
||||
, deregister_provider/1
|
||||
, deregister_providers/1
|
||||
|
@ -56,6 +53,7 @@
|
|||
, move_authenticator/3
|
||||
]).
|
||||
|
||||
%% APIs for observer built-in-database
|
||||
-export([ import_users/3
|
||||
, add_user/3
|
||||
, delete_user/3
|
||||
|
@ -64,8 +62,6 @@
|
|||
, list_users/2
|
||||
]).
|
||||
|
||||
-export([ generate_id/1 ]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([ init/1
|
||||
, handle_call/3
|
||||
|
@ -75,6 +71,20 @@
|
|||
, code_change/3
|
||||
]).
|
||||
|
||||
%% utility functions
|
||||
-export([ authenticator_id/1
|
||||
]).
|
||||
|
||||
%% proxy callback
|
||||
-export([ pre_config_update/2
|
||||
, post_config_update/4
|
||||
]).
|
||||
|
||||
-export_type([ authenticator_id/0
|
||||
, position/0
|
||||
, chain_name/0
|
||||
]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-compile(export_all).
|
||||
-compile(nowarn_export_all).
|
||||
|
@ -88,10 +98,6 @@
|
|||
-type chain_name() :: atom().
|
||||
-type authenticator_id() :: binary().
|
||||
-type position() :: top | bottom | {before, authenticator_id()}.
|
||||
-type update_request() :: {create_authenticator, chain_name(), map()}
|
||||
| {delete_authenticator, chain_name(), authenticator_id()}
|
||||
| {update_authenticator, chain_name(), authenticator_id(), map()}
|
||||
| {move_authenticator, chain_name(), authenticator_id(), position()}.
|
||||
-type authn_type() :: atom() | {atom(), atom()}.
|
||||
-type provider() :: module().
|
||||
|
||||
|
@ -103,8 +109,7 @@
|
|||
enable := boolean(),
|
||||
state := map()}.
|
||||
|
||||
|
||||
-type config() :: #{atom() => term()}.
|
||||
-type config() :: emqx_authentication_config:config().
|
||||
-type state() :: #{atom() => term()}.
|
||||
-type extra() :: #{is_superuser := boolean(),
|
||||
atom() => term()}.
|
||||
|
@ -173,117 +178,6 @@
|
|||
, list_users/1
|
||||
]).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Hocon Schema
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
roots() -> [{authentication, fun authentication/1}].
|
||||
|
||||
fields(_) -> [].
|
||||
|
||||
authentication(type) ->
|
||||
{ok, Refs} = get_refs(),
|
||||
hoconsc:union([hoconsc:array(hoconsc:union(Refs)) | Refs]);
|
||||
authentication(default) -> [];
|
||||
authentication(_) -> undefined.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Callbacks of config handler
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-spec pre_config_update(update_request(), emqx_config:raw_config())
|
||||
-> {ok, map() | list()} | {error, term()}.
|
||||
pre_config_update(UpdateReq, OldConfig) ->
|
||||
case do_pre_config_update(UpdateReq, to_list(OldConfig)) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
{ok, NewConfig} -> {ok, may_to_map(NewConfig)}
|
||||
end.
|
||||
|
||||
do_pre_config_update({create_authenticator, ChainName, Config}, OldConfig) ->
|
||||
try
|
||||
CertsDir = certs_dir([to_bin(ChainName), generate_id(Config)]),
|
||||
NConfig = convert_certs(CertsDir, Config),
|
||||
{ok, OldConfig ++ [NConfig]}
|
||||
catch
|
||||
error:{save_cert_to_file, _} = Reason ->
|
||||
{error, Reason};
|
||||
error:{missing_parameter, _} = Reason ->
|
||||
{error, Reason}
|
||||
end;
|
||||
do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) ->
|
||||
NewConfig = lists:filter(fun(OldConfig0) ->
|
||||
AuthenticatorID =/= generate_id(OldConfig0)
|
||||
end, OldConfig),
|
||||
{ok, NewConfig};
|
||||
do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) ->
|
||||
try
|
||||
CertsDir = certs_dir([to_bin(ChainName), AuthenticatorID]),
|
||||
NewConfig = lists:map(
|
||||
fun(OldConfig0) ->
|
||||
case AuthenticatorID =:= generate_id(OldConfig0) of
|
||||
true -> convert_certs(CertsDir, Config, OldConfig0);
|
||||
false -> OldConfig0
|
||||
end
|
||||
end, OldConfig),
|
||||
{ok, NewConfig}
|
||||
catch
|
||||
error:{save_cert_to_file, _} = Reason ->
|
||||
{error, Reason};
|
||||
error:{missing_parameter, _} = Reason ->
|
||||
{error, Reason}
|
||||
end;
|
||||
do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) ->
|
||||
case split_by_id(AuthenticatorID, OldConfig) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
{ok, Part1, [Found | Part2]} ->
|
||||
case Position of
|
||||
top ->
|
||||
{ok, [Found | Part1] ++ Part2};
|
||||
bottom ->
|
||||
{ok, Part1 ++ Part2 ++ [Found]};
|
||||
{before, Before} ->
|
||||
case split_by_id(Before, Part1 ++ Part2) of
|
||||
{error, Reason} ->
|
||||
{error, Reason};
|
||||
{ok, NPart1, [NFound | NPart2]} ->
|
||||
{ok, NPart1 ++ [Found, NFound | NPart2]}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
-spec post_config_update(update_request(), map() | list(), emqx_config:raw_config(), emqx_config:app_envs())
|
||||
-> ok | {ok, map()} | {error, term()}.
|
||||
post_config_update(UpdateReq, NewConfig, OldConfig, AppEnvs) ->
|
||||
do_post_config_update(UpdateReq, check_config(to_list(NewConfig)), OldConfig, AppEnvs).
|
||||
|
||||
do_post_config_update({create_authenticator, ChainName, Config}, _NewConfig, _OldConfig, _AppEnvs) ->
|
||||
NConfig = check_config(Config),
|
||||
_ = create_chain(ChainName),
|
||||
create_authenticator(ChainName, NConfig);
|
||||
|
||||
do_post_config_update({delete_authenticator, ChainName, AuthenticatorID}, _NewConfig, OldConfig, _AppEnvs) ->
|
||||
case delete_authenticator(ChainName, AuthenticatorID) of
|
||||
ok ->
|
||||
[Config] = [Config0 || Config0 <- to_list(OldConfig), AuthenticatorID == generate_id(Config0)],
|
||||
CertsDir = certs_dir([to_bin(ChainName), AuthenticatorID]),
|
||||
clear_certs(CertsDir, Config),
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
|
||||
do_post_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, _NewConfig, _OldConfig, _AppEnvs) ->
|
||||
NConfig = check_config(Config),
|
||||
update_authenticator(ChainName, AuthenticatorID, NConfig);
|
||||
|
||||
do_post_config_update({move_authenticator, ChainName, AuthenticatorID, Position}, _NewConfig, _OldConfig, _AppEnvs) ->
|
||||
move_authenticator(ChainName, AuthenticatorID, Position).
|
||||
|
||||
check_config(Config) ->
|
||||
#{authentication := CheckedConfig} =
|
||||
hocon_schema:check_plain(?MODULE, #{<<"authentication">> => Config}, #{atom_key => true}),
|
||||
CheckedConfig.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Authenticate
|
||||
%%------------------------------------------------------------------------------
|
||||
|
@ -338,12 +232,30 @@ get_enabled(Authenticators) ->
|
|||
%% APIs
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-spec initialize_authentication(chain_name(), [#{binary() => term()}]) -> ok.
|
||||
initialize_authentication(_, []) ->
|
||||
ok;
|
||||
pre_config_update(UpdateReq, OldConfig) ->
|
||||
emqx_authentication_config:pre_config_update(UpdateReq, OldConfig).
|
||||
|
||||
post_config_update(UpdateReq, NewConfig, OldConfig, AppEnvs) ->
|
||||
emqx_authentication_config:post_config_update(UpdateReq, NewConfig, OldConfig, AppEnvs).
|
||||
|
||||
%% @doc Get all registered authentication providers.
|
||||
get_providers() ->
|
||||
call(get_providers).
|
||||
|
||||
%% @doc Get authenticator identifier from its config.
|
||||
%% The authenticator config must contain a 'mechanism' key
|
||||
%% and maybe a 'backend' key.
|
||||
%% This function works with both parsed (atom keys) and raw (binary keys)
|
||||
%% configurations.
|
||||
authenticator_id(Config) ->
|
||||
emqx_authentication_config:authenticator_id(Config).
|
||||
|
||||
%% @doc Call this API to initialize authenticators implemented in another APP.
|
||||
-spec initialize_authentication(chain_name(), config()) -> ok.
|
||||
initialize_authentication(_, []) -> ok;
|
||||
initialize_authentication(ChainName, AuthenticatorsConfig) ->
|
||||
_ = create_chain(ChainName),
|
||||
CheckedConfig = check_config(to_list(AuthenticatorsConfig)),
|
||||
CheckedConfig = to_list(AuthenticatorsConfig),
|
||||
lists:foreach(fun(AuthenticatorConfig) ->
|
||||
case create_authenticator(ChainName, AuthenticatorConfig) of
|
||||
{ok, _} ->
|
||||
|
@ -351,7 +263,7 @@ initialize_authentication(ChainName, AuthenticatorsConfig) ->
|
|||
{error, Reason} ->
|
||||
?SLOG(error, #{
|
||||
msg => "failed_to_create_authenticator",
|
||||
authenticator => generate_id(AuthenticatorConfig),
|
||||
authenticator => authenticator_id(AuthenticatorConfig),
|
||||
reason => Reason
|
||||
})
|
||||
end
|
||||
|
@ -365,10 +277,6 @@ start_link() ->
|
|||
stop() ->
|
||||
gen_server:stop(?MODULE).
|
||||
|
||||
-spec get_refs() -> {ok, Refs} when Refs :: [{authn_type(), module()}].
|
||||
get_refs() ->
|
||||
call(get_refs).
|
||||
|
||||
%% @doc Register authentication providers.
|
||||
%% A provider is a tuple of `AuthNType' the module which implements
|
||||
%% the authenticator callbacks.
|
||||
|
@ -472,20 +380,6 @@ lookup_user(ChainName, AuthenticatorID, UserID) ->
|
|||
list_users(ChainName, AuthenticatorID) ->
|
||||
call({list_users, ChainName, AuthenticatorID}).
|
||||
|
||||
-spec generate_id(config()) -> authenticator_id().
|
||||
generate_id(#{mechanism := Mechanism0, backend := Backend0}) ->
|
||||
Mechanism = to_bin(Mechanism0),
|
||||
Backend = to_bin(Backend0),
|
||||
<<Mechanism/binary, ":", Backend/binary>>;
|
||||
generate_id(#{mechanism := Mechanism}) ->
|
||||
to_bin(Mechanism);
|
||||
generate_id(#{<<"mechanism">> := Mechanism, <<"backend">> := Backend}) ->
|
||||
<<Mechanism/binary, ":", Backend/binary>>;
|
||||
generate_id(#{<<"mechanism">> := Mechanism}) ->
|
||||
Mechanism;
|
||||
generate_id(_) ->
|
||||
error({missing_parameter, mechanism}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -498,6 +392,8 @@ init(_Opts) ->
|
|||
ok = emqx_config_handler:add_handler([listeners, '?', '?', authentication], ?MODULE),
|
||||
{ok, #{hooked => false, providers => #{}}}.
|
||||
|
||||
handle_call(get_providers, _From, #{providers := Providers} = State) ->
|
||||
reply(Providers, State);
|
||||
handle_call({register_providers, Providers}, _From,
|
||||
#{providers := Reg0} = State) ->
|
||||
case lists:filter(fun({T, _}) -> maps:is_key(T, Reg0) end, Providers) of
|
||||
|
@ -513,12 +409,6 @@ handle_call({register_providers, Providers}, _From,
|
|||
handle_call({deregister_providers, AuthNTypes}, _From, #{providers := Providers} = State) ->
|
||||
reply(ok, State#{providers := maps:without(AuthNTypes, Providers)});
|
||||
|
||||
handle_call(get_refs, _From, #{providers := Providers} = State) ->
|
||||
Refs = lists:foldl(fun({_, Provider}, Acc) ->
|
||||
Acc ++ Provider:refs()
|
||||
end, [], maps:to_list(Providers)),
|
||||
reply({ok, Refs}, State);
|
||||
|
||||
handle_call({create_chain, Name}, _From, State) ->
|
||||
case ets:member(?CHAINS_TAB, Name) of
|
||||
true ->
|
||||
|
@ -549,9 +439,9 @@ handle_call({lookup_chain, Name}, _From, State) ->
|
|||
end;
|
||||
|
||||
handle_call({create_authenticator, ChainName, Config}, _From, #{providers := Providers} = State) ->
|
||||
UpdateFun =
|
||||
UpdateFun =
|
||||
fun(#chain{authenticators = Authenticators} = Chain) ->
|
||||
AuthenticatorID = generate_id(Config),
|
||||
AuthenticatorID = authenticator_id(Config),
|
||||
case lists:keymember(AuthenticatorID, #authenticator.id, Authenticators) of
|
||||
true ->
|
||||
{error, {already_exists, {authenticator, AuthenticatorID}}};
|
||||
|
@ -570,7 +460,7 @@ handle_call({create_authenticator, ChainName, Config}, _From, #{providers := Pro
|
|||
reply(Reply, maybe_hook(State));
|
||||
|
||||
handle_call({delete_authenticator, ChainName, AuthenticatorID}, _From, State) ->
|
||||
UpdateFun =
|
||||
UpdateFun =
|
||||
fun(#chain{authenticators = Authenticators} = Chain) ->
|
||||
case lists:keytake(AuthenticatorID, #authenticator.id, Authenticators) of
|
||||
false ->
|
||||
|
@ -592,7 +482,7 @@ handle_call({update_authenticator, ChainName, AuthenticatorID, Config}, _From, S
|
|||
{error, {not_found, {authenticator, AuthenticatorID}}};
|
||||
#authenticator{provider = Provider,
|
||||
state = #{version := Version} = ST} = Authenticator ->
|
||||
case AuthenticatorID =:= generate_id(Config) of
|
||||
case AuthenticatorID =:= authenticator_id(Config) of
|
||||
true ->
|
||||
Unique = unique(ChainName, AuthenticatorID, Version),
|
||||
case Provider:update(Config#{'_unique' => Unique}, ST) of
|
||||
|
@ -614,7 +504,7 @@ handle_call({update_authenticator, ChainName, AuthenticatorID, Config}, _From, S
|
|||
reply(Reply, State);
|
||||
|
||||
handle_call({move_authenticator, ChainName, AuthenticatorID, Position}, _From, State) ->
|
||||
UpdateFun =
|
||||
UpdateFun =
|
||||
fun(#chain{authenticators = Authenticators} = Chain) ->
|
||||
case do_move_authenticator(AuthenticatorID, Authenticators, Position) of
|
||||
{ok, NAuthenticators} ->
|
||||
|
@ -663,9 +553,9 @@ handle_info(Info, State) ->
|
|||
?SLOG(error, #{msg => "unexpected_info", info => Info}),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
emqx_config_handler:remove_handler([authentication]),
|
||||
emqx_config_handler:remove_handler([listeners, '?', '?', authentication]),
|
||||
terminate(Reason, _State) ->
|
||||
?SLOG(error, #{msg => "emqx_authentication_terminating",
|
||||
reason => Reason}),
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
|
@ -674,128 +564,6 @@ code_change(_OldVsn, State, _Extra) ->
|
|||
reply(Reply, State) ->
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
certs_dir(Dirs) when is_list(Dirs) ->
|
||||
to_bin(filename:join([emqx:get_config([node, data_dir]), "certs/authn"] ++ Dirs)).
|
||||
|
||||
convert_certs(CertsDir, Config) ->
|
||||
case maps:get(<<"ssl">>, Config, undefined) of
|
||||
undefined ->
|
||||
Config;
|
||||
SSLOpts ->
|
||||
NSSLOPts = lists:foldl(fun(K, Acc) ->
|
||||
case maps:get(K, Acc, undefined) of
|
||||
undefined -> Acc;
|
||||
PemBin ->
|
||||
CertFile = generate_filename(CertsDir, K),
|
||||
ok = save_cert_to_file(CertFile, PemBin),
|
||||
Acc#{K => CertFile}
|
||||
end
|
||||
end, SSLOpts, [<<"certfile">>, <<"keyfile">>, <<"cacertfile">>]),
|
||||
Config#{<<"ssl">> => NSSLOPts}
|
||||
end.
|
||||
|
||||
convert_certs(CertsDir, NewConfig, OldConfig) ->
|
||||
case maps:get(<<"ssl">>, NewConfig, undefined) of
|
||||
undefined ->
|
||||
NewConfig;
|
||||
NewSSLOpts ->
|
||||
OldSSLOpts = maps:get(<<"ssl">>, OldConfig, #{}),
|
||||
Diff = diff_certs(NewSSLOpts, OldSSLOpts),
|
||||
NSSLOpts = lists:foldl(fun({identical, K}, Acc) ->
|
||||
Acc#{K => maps:get(K, OldSSLOpts)};
|
||||
({_, K}, Acc) ->
|
||||
CertFile = generate_filename(CertsDir, K),
|
||||
ok = save_cert_to_file(CertFile, maps:get(K, NewSSLOpts)),
|
||||
Acc#{K => CertFile}
|
||||
end, NewSSLOpts, Diff),
|
||||
NewConfig#{<<"ssl">> => NSSLOpts}
|
||||
end.
|
||||
|
||||
clear_certs(CertsDir, Config) ->
|
||||
case maps:get(<<"ssl">>, Config, undefined) of
|
||||
undefined ->
|
||||
ok;
|
||||
SSLOpts ->
|
||||
lists:foreach(
|
||||
fun({_, Filename}) ->
|
||||
_ = file:delete(filename:join([CertsDir, Filename]))
|
||||
end,
|
||||
maps:to_list(maps:with([<<"certfile">>, <<"keyfile">>, <<"cacertfile">>], SSLOpts)))
|
||||
end.
|
||||
|
||||
save_cert_to_file(Filename, PemBin) ->
|
||||
case public_key:pem_decode(PemBin) =/= [] of
|
||||
true ->
|
||||
case filelib:ensure_dir(Filename) of
|
||||
ok ->
|
||||
case file:write_file(Filename, PemBin) of
|
||||
ok -> ok;
|
||||
{error, Reason} -> error({save_cert_to_file, {write_file, Reason}})
|
||||
end;
|
||||
{error, Reason} ->
|
||||
error({save_cert_to_file, {ensure_dir, Reason}})
|
||||
end;
|
||||
false ->
|
||||
error({save_cert_to_file, invalid_certificate})
|
||||
end.
|
||||
|
||||
generate_filename(CertsDir, Key) ->
|
||||
Prefix = case Key of
|
||||
<<"keyfile">> -> "key-";
|
||||
<<"certfile">> -> "cert-";
|
||||
<<"cacertfile">> -> "cacert-"
|
||||
end,
|
||||
to_bin(filename:join([CertsDir, Prefix ++ emqx_misc:gen_id() ++ ".pem"])).
|
||||
|
||||
diff_certs(NewSSLOpts, OldSSLOpts) ->
|
||||
Keys = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>],
|
||||
CertPems = maps:with(Keys, NewSSLOpts),
|
||||
CertFiles = maps:with(Keys, OldSSLOpts),
|
||||
Diff = lists:foldl(fun({K, CertFile}, Acc) ->
|
||||
case maps:find(K, CertPems) of
|
||||
error -> Acc;
|
||||
{ok, PemBin1} ->
|
||||
{ok, PemBin2} = file:read_file(CertFile),
|
||||
case diff_cert(PemBin1, PemBin2) of
|
||||
true ->
|
||||
[{changed, K} | Acc];
|
||||
false ->
|
||||
[{identical, K} | Acc]
|
||||
end
|
||||
end
|
||||
end,
|
||||
[], maps:to_list(CertFiles)),
|
||||
Added = [{added, K} || K <- maps:keys(maps:without(maps:keys(CertFiles), CertPems))],
|
||||
Diff ++ Added.
|
||||
|
||||
diff_cert(Pem1, Pem2) ->
|
||||
cal_md5_for_cert(Pem1) =/= cal_md5_for_cert(Pem2).
|
||||
|
||||
cal_md5_for_cert(Pem) ->
|
||||
crypto:hash(md5, term_to_binary(public_key:pem_decode(Pem))).
|
||||
|
||||
split_by_id(ID, AuthenticatorsConfig) ->
|
||||
case lists:foldl(
|
||||
fun(C, {P1, P2, F0}) ->
|
||||
F = case ID =:= generate_id(C) of
|
||||
true -> true;
|
||||
false -> F0
|
||||
end,
|
||||
case F of
|
||||
false -> {[C | P1], P2, F};
|
||||
true -> {P1, [C | P2], F}
|
||||
end
|
||||
end, {[], [], false}, AuthenticatorsConfig) of
|
||||
{_, _, false} ->
|
||||
{error, {not_found, {authenticator, ID}}};
|
||||
{Part1, Part2, true} ->
|
||||
{ok, lists:reverse(Part1), lists:reverse(Part2)}
|
||||
end.
|
||||
|
||||
global_chain(mqtt) ->
|
||||
'mqtt:global';
|
||||
global_chain('mqtt-sn') ->
|
||||
|
@ -942,22 +710,9 @@ authn_type(#{mechanism := Mechanism, backend := Backend}) ->
|
|||
authn_type(#{mechanism := Mechanism}) ->
|
||||
Mechanism.
|
||||
|
||||
may_to_map([L]) ->
|
||||
L;
|
||||
may_to_map(L) ->
|
||||
L.
|
||||
|
||||
to_list(undefined) ->
|
||||
[];
|
||||
to_list(M) when M =:= #{} ->
|
||||
[];
|
||||
to_list(M) when is_map(M) ->
|
||||
[M];
|
||||
to_list(L) when is_list(L) ->
|
||||
L.
|
||||
|
||||
to_bin(B) when is_binary(B) -> B;
|
||||
to_bin(L) when is_list(L) -> list_to_binary(L);
|
||||
to_bin(A) when is_atom(A) -> atom_to_binary(A).
|
||||
to_list(undefined) -> [];
|
||||
to_list(M) when M =:= #{} -> [];
|
||||
to_list(M) when is_map(M) -> [M];
|
||||
to_list(L) when is_list(L) -> L.
|
||||
|
||||
call(Call) -> gen_server:call(?MODULE, Call, infinity).
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2021 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.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% @doc Authenticator configuration management module.
|
||||
-module(emqx_authentication_config).
|
||||
|
||||
-behaviour(emqx_config_handler).
|
||||
|
||||
-export([ pre_config_update/2
|
||||
, post_config_update/4
|
||||
]).
|
||||
|
||||
-export([ authenticator_id/1
|
||||
, authn_type/1
|
||||
]).
|
||||
|
||||
-export_type([config/0]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
-type parsed_config() :: #{mechanism := atom(),
|
||||
backend => atom(),
|
||||
atom() => term()}.
|
||||
-type raw_config() :: #{binary() => term()}.
|
||||
-type config() :: parsed_config() | raw_config().
|
||||
|
||||
-type authenticator_id() :: emqx_authentication:authenticator_id().
|
||||
-type position() :: emqx_authentication:position().
|
||||
-type chain_name() :: emqx_authentication:chain_name().
|
||||
-type update_request() :: {create_authenticator, chain_name(), map()}
|
||||
| {delete_authenticator, chain_name(), authenticator_id()}
|
||||
| {update_authenticator, chain_name(), authenticator_id(), map()}
|
||||
| {move_authenticator, chain_name(), authenticator_id(), position()}.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Callbacks of config handler
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-spec pre_config_update(update_request(), emqx_config:raw_config())
|
||||
-> {ok, map() | list()} | {error, term()}.
|
||||
pre_config_update(UpdateReq, OldConfig) ->
|
||||
case do_pre_config_update(UpdateReq, to_list(OldConfig)) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
{ok, NewConfig} -> {ok, return_map(NewConfig)}
|
||||
end.
|
||||
|
||||
do_pre_config_update({create_authenticator, ChainName, Config}, OldConfig) ->
|
||||
try
|
||||
CertsDir = certs_dir([to_bin(ChainName), authenticator_id(Config)]),
|
||||
NConfig = convert_certs(CertsDir, Config),
|
||||
{ok, OldConfig ++ [NConfig]}
|
||||
catch
|
||||
error:{save_cert_to_file, _} = Reason ->
|
||||
{error, Reason};
|
||||
error:{missing_parameter, _} = Reason ->
|
||||
{error, Reason}
|
||||
end;
|
||||
do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) ->
|
||||
NewConfig = lists:filter(fun(OldConfig0) ->
|
||||
AuthenticatorID =/= authenticator_id(OldConfig0)
|
||||
end, OldConfig),
|
||||
{ok, NewConfig};
|
||||
do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) ->
|
||||
try
|
||||
CertsDir = certs_dir([to_bin(ChainName), AuthenticatorID]),
|
||||
NewConfig = lists:map(
|
||||
fun(OldConfig0) ->
|
||||
case AuthenticatorID =:= authenticator_id(OldConfig0) of
|
||||
true -> convert_certs(CertsDir, Config, OldConfig0);
|
||||
false -> OldConfig0
|
||||
end
|
||||
end, OldConfig),
|
||||
{ok, NewConfig}
|
||||
catch
|
||||
error:{save_cert_to_file, _} = Reason ->
|
||||
{error, Reason};
|
||||
error:{missing_parameter, _} = Reason ->
|
||||
{error, Reason}
|
||||
end;
|
||||
do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) ->
|
||||
case split_by_id(AuthenticatorID, OldConfig) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
{ok, Part1, [Found | Part2]} ->
|
||||
case Position of
|
||||
top ->
|
||||
{ok, [Found | Part1] ++ Part2};
|
||||
bottom ->
|
||||
{ok, Part1 ++ Part2 ++ [Found]};
|
||||
{before, Before} ->
|
||||
case split_by_id(Before, Part1 ++ Part2) of
|
||||
{error, Reason} ->
|
||||
{error, Reason};
|
||||
{ok, NPart1, [NFound | NPart2]} ->
|
||||
{ok, NPart1 ++ [Found, NFound | NPart2]}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
-spec post_config_update(update_request(), map() | list(), emqx_config:raw_config(), emqx_config:app_envs())
|
||||
-> ok | {ok, map()} | {error, term()}.
|
||||
post_config_update(UpdateReq, NewConfig, OldConfig, AppEnvs) ->
|
||||
do_post_config_update(UpdateReq, check_configs(to_list(NewConfig)), OldConfig, AppEnvs).
|
||||
|
||||
do_post_config_update({create_authenticator, ChainName, Config}, _NewConfig, _OldConfig, _AppEnvs) ->
|
||||
NConfig = check_config(Config),
|
||||
_ = emqx_authentication:create_chain(ChainName),
|
||||
emqx_authentication:create_authenticator(ChainName, NConfig);
|
||||
do_post_config_update({delete_authenticator, ChainName, AuthenticatorID}, _NewConfig, OldConfig, _AppEnvs) ->
|
||||
case emqx_authentication:delete_authenticator(ChainName, AuthenticatorID) of
|
||||
ok ->
|
||||
[Config] = [Config0 || Config0 <- to_list(OldConfig), AuthenticatorID == authenticator_id(Config0)],
|
||||
CertsDir = certs_dir([to_bin(ChainName), AuthenticatorID]),
|
||||
clear_certs(CertsDir, Config),
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
do_post_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, _NewConfig, _OldConfig, _AppEnvs) ->
|
||||
NConfig = check_config(Config),
|
||||
emqx_authentication:update_authenticator(ChainName, AuthenticatorID, NConfig);
|
||||
do_post_config_update({move_authenticator, ChainName, AuthenticatorID, Position}, _NewConfig, _OldConfig, _AppEnvs) ->
|
||||
emqx_authentication:move_authenticator(ChainName, AuthenticatorID, Position).
|
||||
|
||||
check_config(Config) ->
|
||||
[Checked] = check_configs([Config]),
|
||||
Checked.
|
||||
|
||||
check_configs(Configs) ->
|
||||
Providers = emqx_authentication:get_providers(),
|
||||
lists:map(fun(C) -> do_check_conifg(C, Providers) end, Configs).
|
||||
|
||||
do_check_conifg(Config, Providers) ->
|
||||
Type = authn_type(Config),
|
||||
case maps:get(Type, Providers, false) of
|
||||
false ->
|
||||
?SLOG(warning, #{msg => "unknown_authn_type",
|
||||
type => Type,
|
||||
providers => Providers}),
|
||||
throw(unknown_authn_type);
|
||||
Module ->
|
||||
%% TODO: check if Module:check_config/1 is exported
|
||||
%% so we do not force all providers to implement hocon schema
|
||||
try hocon_schema:check_plain(Module, #{<<"config">> => Config},
|
||||
#{atom_key => true}) of
|
||||
#{config := Result} ->
|
||||
Result
|
||||
catch
|
||||
C : E : S ->
|
||||
?SLOG(warning, #{msg => "failed_to_check_config", config => Config}),
|
||||
erlang:raise(C, E, S)
|
||||
end
|
||||
end.
|
||||
|
||||
return_map([L]) -> L;
|
||||
return_map(L) -> L.
|
||||
|
||||
to_list(undefined) -> [];
|
||||
to_list(M) when M =:= #{} -> [];
|
||||
to_list(M) when is_map(M) -> [M];
|
||||
to_list(L) when is_list(L) -> L.
|
||||
|
||||
certs_dir(Dirs) when is_list(Dirs) ->
|
||||
to_bin(filename:join([emqx:get_config([node, data_dir]), "certs", "authn"] ++ Dirs)).
|
||||
|
||||
convert_certs(CertsDir, Config) ->
|
||||
case maps:get(<<"ssl">>, Config, undefined) of
|
||||
undefined ->
|
||||
Config;
|
||||
SSLOpts ->
|
||||
NSSLOPts = lists:foldl(fun(K, Acc) ->
|
||||
case maps:get(K, Acc, undefined) of
|
||||
undefined -> Acc;
|
||||
PemBin ->
|
||||
CertFile = generate_filename(CertsDir, K),
|
||||
ok = save_cert_to_file(CertFile, PemBin),
|
||||
Acc#{K => CertFile}
|
||||
end
|
||||
end, SSLOpts, [<<"certfile">>, <<"keyfile">>, <<"cacertfile">>]),
|
||||
Config#{<<"ssl">> => NSSLOPts}
|
||||
end.
|
||||
|
||||
convert_certs(CertsDir, NewConfig, OldConfig) ->
|
||||
case maps:get(<<"ssl">>, NewConfig, undefined) of
|
||||
undefined ->
|
||||
NewConfig;
|
||||
NewSSLOpts ->
|
||||
OldSSLOpts = maps:get(<<"ssl">>, OldConfig, #{}),
|
||||
Diff = diff_certs(NewSSLOpts, OldSSLOpts),
|
||||
NSSLOpts = lists:foldl(fun({identical, K}, Acc) ->
|
||||
Acc#{K => maps:get(K, OldSSLOpts)};
|
||||
({_, K}, Acc) ->
|
||||
CertFile = generate_filename(CertsDir, K),
|
||||
ok = save_cert_to_file(CertFile, maps:get(K, NewSSLOpts)),
|
||||
Acc#{K => CertFile}
|
||||
end, NewSSLOpts, Diff),
|
||||
NewConfig#{<<"ssl">> => NSSLOpts}
|
||||
end.
|
||||
|
||||
clear_certs(CertsDir, Config) ->
|
||||
case maps:get(<<"ssl">>, Config, undefined) of
|
||||
undefined ->
|
||||
ok;
|
||||
SSLOpts ->
|
||||
lists:foreach(
|
||||
fun({_, Filename}) ->
|
||||
_ = file:delete(filename:join([CertsDir, Filename]))
|
||||
end,
|
||||
maps:to_list(maps:with([<<"certfile">>, <<"keyfile">>, <<"cacertfile">>], SSLOpts)))
|
||||
end.
|
||||
|
||||
save_cert_to_file(Filename, PemBin) ->
|
||||
case public_key:pem_decode(PemBin) =/= [] of
|
||||
true ->
|
||||
case filelib:ensure_dir(Filename) of
|
||||
ok ->
|
||||
case file:write_file(Filename, PemBin) of
|
||||
ok -> ok;
|
||||
{error, Reason} -> error({save_cert_to_file, {write_file, Reason}})
|
||||
end;
|
||||
{error, Reason} ->
|
||||
error({save_cert_to_file, {ensure_dir, Reason}})
|
||||
end;
|
||||
false ->
|
||||
error({save_cert_to_file, invalid_certificate})
|
||||
end.
|
||||
|
||||
generate_filename(CertsDir, Key) ->
|
||||
Prefix = case Key of
|
||||
<<"keyfile">> -> "key-";
|
||||
<<"certfile">> -> "cert-";
|
||||
<<"cacertfile">> -> "cacert-"
|
||||
end,
|
||||
to_bin(filename:join([CertsDir, Prefix ++ emqx_misc:gen_id() ++ ".pem"])).
|
||||
|
||||
diff_certs(NewSSLOpts, OldSSLOpts) ->
|
||||
Keys = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>],
|
||||
CertPems = maps:with(Keys, NewSSLOpts),
|
||||
CertFiles = maps:with(Keys, OldSSLOpts),
|
||||
Diff = lists:foldl(fun({K, CertFile}, Acc) ->
|
||||
case maps:find(K, CertPems) of
|
||||
error -> Acc;
|
||||
{ok, PemBin1} ->
|
||||
{ok, PemBin2} = file:read_file(CertFile),
|
||||
case diff_cert(PemBin1, PemBin2) of
|
||||
true ->
|
||||
[{changed, K} | Acc];
|
||||
false ->
|
||||
[{identical, K} | Acc]
|
||||
end
|
||||
end
|
||||
end,
|
||||
[], maps:to_list(CertFiles)),
|
||||
Added = [{added, K} || K <- maps:keys(maps:without(maps:keys(CertFiles), CertPems))],
|
||||
Diff ++ Added.
|
||||
|
||||
diff_cert(Pem1, Pem2) ->
|
||||
cal_md5_for_cert(Pem1) =/= cal_md5_for_cert(Pem2).
|
||||
|
||||
cal_md5_for_cert(Pem) ->
|
||||
crypto:hash(md5, term_to_binary(public_key:pem_decode(Pem))).
|
||||
|
||||
split_by_id(ID, AuthenticatorsConfig) ->
|
||||
case lists:foldl(
|
||||
fun(C, {P1, P2, F0}) ->
|
||||
F = case ID =:= authenticator_id(C) of
|
||||
true -> true;
|
||||
false -> F0
|
||||
end,
|
||||
case F of
|
||||
false -> {[C | P1], P2, F};
|
||||
true -> {P1, [C | P2], F}
|
||||
end
|
||||
end, {[], [], false}, AuthenticatorsConfig) of
|
||||
{_, _, false} ->
|
||||
{error, {not_found, {authenticator, ID}}};
|
||||
{Part1, Part2, true} ->
|
||||
{ok, lists:reverse(Part1), lists:reverse(Part2)}
|
||||
end.
|
||||
|
||||
to_bin(B) when is_binary(B) -> B;
|
||||
to_bin(L) when is_list(L) -> list_to_binary(L);
|
||||
to_bin(A) when is_atom(A) -> atom_to_binary(A).
|
||||
|
||||
%% @doc Make an authenticator ID from authenticator's config.
|
||||
%% The authenticator config must contain a 'mechanism' key
|
||||
%% and maybe a 'backend' key.
|
||||
%% This function works with both parsed (atom keys) and raw (binary keys)
|
||||
%% configurations.
|
||||
-spec authenticator_id(config()) -> authenticator_id().
|
||||
authenticator_id(#{mechanism := Mechanism0, backend := Backend0}) ->
|
||||
Mechanism = to_bin(Mechanism0),
|
||||
Backend = to_bin(Backend0),
|
||||
<<Mechanism/binary, ":", Backend/binary>>;
|
||||
authenticator_id(#{mechanism := Mechanism}) ->
|
||||
to_bin(Mechanism);
|
||||
authenticator_id(#{<<"mechanism">> := Mechanism, <<"backend">> := Backend}) ->
|
||||
<<Mechanism/binary, ":", Backend/binary>>;
|
||||
authenticator_id(#{<<"mechanism">> := Mechanism}) ->
|
||||
Mechanism;
|
||||
authenticator_id(C) ->
|
||||
error({missing_parameter, mechanism, C}).
|
||||
|
||||
%% @doc Make the authentication type.
|
||||
authn_type(#{mechanism := M, backend := B}) -> {atom(M), atom(B)};
|
||||
authn_type(#{mechanism := M}) -> atom(M);
|
||||
authn_type(#{<<"mechanism">> := M, <<"backend">> := B}) -> {atom(M), atom(B)};
|
||||
authn_type(#{<<"mechanism">> := M}) -> atom(M).
|
||||
|
||||
atom(Bin) ->
|
||||
binary_to_existing_atom(Bin, utf8).
|
|
@ -50,7 +50,7 @@ init([]) ->
|
|||
shutdown => infinity,
|
||||
type => supervisor,
|
||||
modules => [emqx_authentication_sup]},
|
||||
|
||||
|
||||
%% Broker helper
|
||||
Helper = #{id => helper,
|
||||
start => {emqx_broker_helper, start_link, []},
|
||||
|
|
|
@ -71,15 +71,15 @@ stop() ->
|
|||
update_config(SchemaModule, ConfKeyPath, UpdateArgs) ->
|
||||
%% force covert the path to a list of atoms, as there maybe some wildcard names/ids in the path
|
||||
AtomKeyPath = [atom(Key) || Key <- ConfKeyPath],
|
||||
gen_server:call(?MODULE, {change_config, SchemaModule, AtomKeyPath, UpdateArgs}).
|
||||
gen_server:call(?MODULE, {change_config, SchemaModule, AtomKeyPath, UpdateArgs}, infinity).
|
||||
|
||||
-spec add_handler(emqx_config:config_key_path(), handler_name()) -> ok.
|
||||
add_handler(ConfKeyPath, HandlerName) ->
|
||||
gen_server:call(?MODULE, {add_handler, ConfKeyPath, HandlerName}).
|
||||
gen_server:call(?MODULE, {add_handler, ConfKeyPath, HandlerName}, infinity).
|
||||
|
||||
-spec remove_handler(emqx_config:config_key_path()) -> ok.
|
||||
remove_handler(ConfKeyPath) ->
|
||||
gen_server:call(?MODULE, {remove_handler, ConfKeyPath}).
|
||||
gen_server:call(?MODULE, {remove_handler, ConfKeyPath}, infinity).
|
||||
|
||||
%%============================================================================
|
||||
|
||||
|
@ -247,7 +247,8 @@ call_post_config_update(Handlers, OldConf, NewConf, AppEnvs, UpdateReq, Result)
|
|||
true ->
|
||||
case HandlerName:post_config_update(UpdateReq, NewConf, OldConf, AppEnvs) of
|
||||
ok -> {ok, Result};
|
||||
{ok, Result1} -> {ok, Result#{HandlerName => Result1}};
|
||||
{ok, Result1} ->
|
||||
{ok, Result#{HandlerName => Result1}};
|
||||
{error, Reason} -> {error, {post_config_update, HandlerName, Reason}}
|
||||
end;
|
||||
false -> {ok, Result}
|
||||
|
|
|
@ -15,3 +15,50 @@
|
|||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_authn).
|
||||
|
||||
-export([ providers/0
|
||||
, check_config/1
|
||||
, check_config/2
|
||||
, check_configs/1
|
||||
]).
|
||||
|
||||
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-server'}, emqx_authn_http}
|
||||
, {{'password-based', 'http'}, emqx_authn_http} %% TODO: resolve one
|
||||
, {jwt, emqx_authn_jwt}
|
||||
, {{scram, 'built-in-database'}, emqx_enhanced_authn_scram_mnesia}
|
||||
].
|
||||
|
||||
check_configs([]) -> [];
|
||||
check_configs([Config | Configs]) ->
|
||||
[check_config(Config) | check_configs(Configs)].
|
||||
|
||||
check_config(Config) ->
|
||||
check_config(Config, #{}).
|
||||
|
||||
check_config(Config, Opts) ->
|
||||
case do_check_config(Config, Opts) of
|
||||
#{config := Checked} -> Checked;
|
||||
#{<<"config">> := WithDefaults} -> WithDefaults
|
||||
end.
|
||||
|
||||
do_check_config(#{<<"mechanism">> := Mec} = Config, Opts) ->
|
||||
Key = case maps:get(<<"backend">>, Config, false) of
|
||||
false -> atom(Mec);
|
||||
Backend -> {atom(Mec), atom(Backend)}
|
||||
end,
|
||||
case lists:keyfind(Key, 1, providers()) of
|
||||
false ->
|
||||
throw({unknown_handler, Key});
|
||||
{_, Provider} ->
|
||||
hocon_schema:check_plain(Provider, #{<<"config">> => Config},
|
||||
Opts#{atom_key => true})
|
||||
end.
|
||||
|
||||
atom(Bin) ->
|
||||
binary_to_existing_atom(Bin, utf8).
|
||||
|
|
|
@ -313,7 +313,7 @@ create_authenticator_api_spec() ->
|
|||
},
|
||||
<<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
|
||||
<<"409">> => ?ERR_RESPONSE(<<"Conflict">>)
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
create_authenticator_api_spec2() ->
|
||||
|
@ -1852,7 +1852,7 @@ create_authenticator(ConfKeyPath, ChainName, Config) ->
|
|||
|
||||
list_authenticators(ConfKeyPath) ->
|
||||
AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath),
|
||||
NAuthenticators = [maps:put(id, ?AUTHN:generate_id(AuthenticatorConfig), convert_certs(AuthenticatorConfig))
|
||||
NAuthenticators = [maps:put(id, ?AUTHN:authenticator_id(AuthenticatorConfig), convert_certs(AuthenticatorConfig))
|
||||
|| AuthenticatorConfig <- AuthenticatorsConfig],
|
||||
{200, NAuthenticators}.
|
||||
|
||||
|
@ -1961,19 +1961,18 @@ update_config(Path, ConfigRequest) ->
|
|||
get_raw_config_with_defaults(ConfKeyPath) ->
|
||||
NConfKeyPath = [atom_to_binary(Key, utf8) || Key <- ConfKeyPath],
|
||||
RawConfig = emqx_map_lib:deep_get(NConfKeyPath, emqx_config:get_raw([]), []),
|
||||
to_list(fill_defaults(RawConfig)).
|
||||
ensure_list(fill_defaults(RawConfig)).
|
||||
|
||||
find_config(AuthenticatorID, AuthenticatorsConfig) ->
|
||||
case [AC || AC <- to_list(AuthenticatorsConfig), AuthenticatorID =:= ?AUTHN:generate_id(AC)] of
|
||||
case [AC || AC <- ensure_list(AuthenticatorsConfig), AuthenticatorID =:= ?AUTHN:authenticator_id(AC)] of
|
||||
[] -> {error, {not_found, {authenticator, AuthenticatorID}}};
|
||||
[AuthenticatorConfig] -> {ok, AuthenticatorConfig}
|
||||
end.
|
||||
|
||||
fill_defaults(Configs) when is_list(Configs) ->
|
||||
lists:map(fun fill_defaults/1, Configs);
|
||||
fill_defaults(Config) ->
|
||||
#{<<"authentication">> := CheckedConfig} =
|
||||
hocon_schema:check_plain(?AUTHN, #{<<"authentication">> => Config},
|
||||
#{only_fill_defaults => true}),
|
||||
CheckedConfig.
|
||||
emqx_authn:check_config(Config, #{only_fill_defaults => true}).
|
||||
|
||||
convert_certs(#{<<"ssl">> := SSLOpts} = Config) ->
|
||||
NSSLOpts = lists:foldl(fun(K, Acc) ->
|
||||
|
@ -2063,10 +2062,8 @@ parse_position(<<"before:", Before/binary>>) ->
|
|||
parse_position(_) ->
|
||||
{error, {invalid_parameter, position}}.
|
||||
|
||||
to_list(M) when is_map(M) ->
|
||||
[M];
|
||||
to_list(L) when is_list(L) ->
|
||||
L.
|
||||
ensure_list(M) when is_map(M) -> [M];
|
||||
ensure_list(L) when is_list(L) -> L.
|
||||
|
||||
to_atom(B) when is_binary(B) ->
|
||||
binary_to_atom(B);
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
start(_StartType, _StartArgs) ->
|
||||
ok = ekka_rlog:wait_for_shards([?AUTH_SHARD], infinity),
|
||||
{ok, Sup} = emqx_authn_sup:start_link(),
|
||||
ok = ?AUTHN:register_providers(providers()),
|
||||
ok = ?AUTHN:register_providers(emqx_authn:providers()),
|
||||
ok = initialize(),
|
||||
{ok, Sup}.
|
||||
|
||||
|
@ -45,21 +45,12 @@ stop(_State) ->
|
|||
%%------------------------------------------------------------------------------
|
||||
|
||||
initialize() ->
|
||||
?AUTHN:initialize_authentication(?GLOBAL, emqx:get_raw_config([authentication], [])),
|
||||
RawConfigs = emqx:get_raw_config([authentication], []),
|
||||
Config = emqx_authn:check_configs(RawConfigs),
|
||||
?AUTHN:initialize_authentication(?GLOBAL, Config),
|
||||
lists:foreach(fun({ListenerID, ListenerConfig}) ->
|
||||
?AUTHN:initialize_authentication(ListenerID, maps:get(authentication, ListenerConfig, []))
|
||||
end, emqx_listeners:list()).
|
||||
|
||||
provider_types() ->
|
||||
lists:map(fun({Type, _Module}) -> Type end, providers()).
|
||||
|
||||
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-server'}, emqx_authn_http}
|
||||
, {jwt, emqx_authn_jwt}
|
||||
, {{scram, 'built-in-database'}, emqx_enhanced_authn_scram_mnesia}
|
||||
].
|
||||
lists:map(fun({Type, _Module}) -> Type end, emqx_authn:providers()).
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
]).
|
||||
|
||||
common_fields() ->
|
||||
[ {enable, fun enable/1}
|
||||
[ {enable, fun enable/1}
|
||||
].
|
||||
|
||||
enable(type) -> boolean();
|
||||
|
|
|
@ -62,7 +62,7 @@ fields(post) ->
|
|||
|
||||
common_fields() ->
|
||||
[ {mechanism, {enum, ['password-based']}}
|
||||
, {backend, {enum, ['http-server']}}
|
||||
, {backend, {enum, ['http-server', 'http']}} %% TODO: delete http
|
||||
, {url, fun url/1}
|
||||
, {body, fun body/1}
|
||||
, {request_timeout, fun request_timeout/1}
|
||||
|
@ -78,6 +78,7 @@ validations() ->
|
|||
|
||||
url(type) -> binary();
|
||||
url(validator) -> [fun check_url/1];
|
||||
url(nullable) -> false;
|
||||
url(_) -> undefined.
|
||||
|
||||
headers(type) -> map();
|
||||
|
@ -214,11 +215,19 @@ transform_header_name(Headers) ->
|
|||
end, #{}, Headers).
|
||||
|
||||
check_ssl_opts(Conf) ->
|
||||
emqx_connector_http:check_ssl_opts("url", Conf).
|
||||
case parse_url(hocon_schema:get_value("config.url", Conf)) of
|
||||
#{scheme := https} ->
|
||||
case hocon_schema:get_value("config.ssl.enable", Conf) of
|
||||
true -> ok;
|
||||
false -> false
|
||||
end;
|
||||
#{scheme := http} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
check_headers(Conf) ->
|
||||
Method = hocon_schema:get_value("method", Conf),
|
||||
Headers = hocon_schema:get_value("headers", Conf),
|
||||
Method = hocon_schema:get_value("config.method", Conf),
|
||||
Headers = hocon_schema:get_value("config.headers", Conf),
|
||||
case Method =:= get andalso maps:get(<<"content-type">>, Headers, undefined) =/= undefined of
|
||||
true -> false;
|
||||
false -> true
|
||||
|
|
Loading…
Reference in New Issue