diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx/src/emqx_authentication.erl index 5ee8b2815..c3bfe7912 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx/src/emqx_authentication.erl @@ -116,7 +116,9 @@ -type user_info() :: #{user_id := binary(), atom() => term()}. --callback refs() -> [{ref, Module, Name}] when Module::module(), Name::atom(). +%% @doc check_config takes raw config from config file, +%% parse and validate it, and reutrn parsed result. +-callback check_config(config()) -> config(). -callback create(Config) -> {ok, State} @@ -176,6 +178,7 @@ , update_user/3 , lookup_user/3 , list_users/1 + , check_config/1 ]). %%------------------------------------------------------------------------------ diff --git a/apps/emqx/src/emqx_authentication_config.erl b/apps/emqx/src/emqx_authentication_config.erl index 526d9d6ff..86182143a 100644 --- a/apps/emqx/src/emqx_authentication_config.erl +++ b/apps/emqx/src/emqx_authentication_config.erl @@ -27,6 +27,11 @@ , authn_type/1 ]). +%% TODO: certs handling should be moved out of emqx app +-ifdef(TEST). +-export([convert_certs/2, convert_certs/3, diff_cert/2, clear_certs/2]). +-endif. + -export_type([config/0]). -include("logger.hrl"). @@ -151,17 +156,33 @@ do_check_conifg(Config, Providers) -> 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 + do_check_conifg(Type, Config, Module) + end. + +do_check_conifg(Type, Config, Module) -> + F = case erlang:function_exported(Module, check_config, 1) of + true -> + fun Module:check_config/1; + false -> + fun(C) -> + #{config := R} = + hocon_schema:check_plain(Module, #{<<"config">> => C}, + #{atom_key => true}), + R + end + end, + try + F(Config) + catch + C : E : S -> + ?SLOG(warning, #{msg => "failed_to_check_config", + config => Config, + type => Type, + exception => C, + reason => E, + stacktrace => S + }), + throw(bad_authenticator_config) end. return_map([L]) -> L; diff --git a/apps/emqx/test/emqx_authentication_SUITE.erl b/apps/emqx/test/emqx_authentication_SUITE.erl index c4ed85125..cf1514918 100644 --- a/apps/emqx/test/emqx_authentication_SUITE.erl +++ b/apps/emqx/test/emqx_authentication_SUITE.erl @@ -26,13 +26,13 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("typerefl/include/types.hrl"). --export([ fields/1 ]). +-export([ roots/0, fields/1 ]). --export([ refs/0 - , create/1 +-export([ create/1 , update/2 , authenticate/2 , destroy/1 + , check_config/1 ]). -define(AUTHN, emqx_authentication). @@ -42,6 +42,8 @@ %% Hocon Schema %%------------------------------------------------------------------------------ +roots() -> [{config, #{type => hoconsc:union([hoconsc:ref(type1), hoconsc:ref(type2)])}}]. + fields(type1) -> [ {mechanism, {enum, ['password-based']}} , {backend, {enum, ['built-in-database']}} @@ -62,10 +64,11 @@ enable(_) -> undefined. %% Callbacks %%------------------------------------------------------------------------------ -refs() -> - [ hoconsc:ref(?MODULE, type1) - , hoconsc:ref(?MODULE, type2) - ]. +check_config(C) -> + #{config := R} = + hocon_schema:check_plain(?MODULE, #{<<"config">> => C}, + #{atom_key => true}), + R. create(_Config) -> {ok, #{mark => 1}}. @@ -268,14 +271,14 @@ t_convert_certs(Config) when is_list(Config) -> , {<<"cacertfile">>, "cacert.pem"} ]), - CertsDir = ?AUTHN:certs_dir([Global, <<"password-based:built-in-database">>]), - #{<<"ssl">> := NCerts} = ?AUTHN:convert_certs(CertsDir, #{<<"ssl">> => Certs}), + CertsDir = certs_dir(Config, [Global, <<"password-based:built-in-database">>]), + #{<<"ssl">> := NCerts} = convert_certs(CertsDir, #{<<"ssl">> => Certs}), ?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts), maps:get(<<"keyfile">>, Certs))), Certs2 = certs([ {<<"keyfile">>, "key.pem"} , {<<"certfile">>, "cert.pem"} ]), - #{<<"ssl">> := NCerts2} = ?AUTHN:convert_certs(CertsDir, #{<<"ssl">> => Certs2}, #{<<"ssl">> => NCerts}), + #{<<"ssl">> := NCerts2} = convert_certs(CertsDir, #{<<"ssl">> => Certs2}, #{<<"ssl">> => NCerts}), ?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts2), maps:get(<<"keyfile">>, Certs2))), ?assertEqual(maps:get(<<"keyfile">>, NCerts), maps:get(<<"keyfile">>, NCerts2)), ?assertEqual(maps:get(<<"certfile">>, NCerts), maps:get(<<"certfile">>, NCerts2)), @@ -284,13 +287,13 @@ t_convert_certs(Config) when is_list(Config) -> , {<<"certfile">>, "client-cert.pem"} , {<<"cacertfile">>, "cacert.pem"} ]), - #{<<"ssl">> := NCerts3} = ?AUTHN:convert_certs(CertsDir, #{<<"ssl">> => Certs3}, #{<<"ssl">> => NCerts2}), + #{<<"ssl">> := NCerts3} = convert_certs(CertsDir, #{<<"ssl">> => Certs3}, #{<<"ssl">> => NCerts2}), ?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts3), maps:get(<<"keyfile">>, Certs3))), ?assertNotEqual(maps:get(<<"keyfile">>, NCerts2), maps:get(<<"keyfile">>, NCerts3)), ?assertNotEqual(maps:get(<<"certfile">>, NCerts2), maps:get(<<"certfile">>, NCerts3)), ?assertEqual(true, filelib:is_regular(maps:get(<<"keyfile">>, NCerts3))), - ?AUTHN:clear_certs(CertsDir, #{<<"ssl">> => NCerts3}), + clear_certs(CertsDir, #{<<"ssl">> => NCerts3}), ?assertEqual(false, filelib:is_regular(maps:get(<<"keyfile">>, NCerts3))). update_config(Path, ConfigRequest) -> @@ -305,7 +308,22 @@ certs(Certs) -> diff_cert(CertFile, CertPem2) -> {ok, CertPem1} = file:read_file(CertFile), - ?AUTHN:diff_cert(CertPem1, CertPem2). + emqx_authentication_config:diff_cert(CertPem1, CertPem2). register_provider(Type, Module) -> ok = ?AUTHN:register_providers([{Type, Module}]). + +certs_dir(CtConfig, Path) -> + DataDir = proplists:get_value(data_dir, CtConfig), + Dir = filename:join([DataDir | Path]), + filelib:ensure_dir(Dir), + Dir. + +convert_certs(CertsDir, SslConfig) -> + emqx_authentication_config:convert_certs(CertsDir, SslConfig). + +convert_certs(CertsDir, New, Old) -> + emqx_authentication_config:convert_certs(CertsDir, New, Old). + +clear_certs(CertsDir, SslConfig) -> + emqx_authentication_config:clear_certs(CertsDir, SslConfig).