From 61da3a4fd70e7716dc7022347eae9d13306db69c Mon Sep 17 00:00:00 2001 From: zhouzb Date: Tue, 10 Aug 2021 14:47:22 +0800 Subject: [PATCH] feat(authn hot config): initial support for hot config --- apps/emqx_authn/src/emqx_authn.erl | 102 +++++++++++++++++- apps/emqx_authn/src/emqx_authn_api.erl | 48 +++------ apps/emqx_authn/src/emqx_authn_app.erl | 1 + ...hema.erl => emqx_authn_implied_schema.erl} | 2 +- 4 files changed, 118 insertions(+), 35 deletions(-) rename apps/emqx_authn/src/simple_authn/{emqx_authn_other_schema.erl => emqx_authn_implied_schema.erl} (97%) diff --git a/apps/emqx_authn/src/emqx_authn.erl b/apps/emqx_authn/src/emqx_authn.erl index 034e06b89..7ead53638 100644 --- a/apps/emqx_authn/src/emqx_authn.erl +++ b/apps/emqx_authn/src/emqx_authn.erl @@ -16,8 +16,17 @@ -module(emqx_authn). +-behaviour(emqx_config_handler). + -include("emqx_authn.hrl"). +-export([mnesia/1]). + +-export([ pre_config_update/2 + , post_config_update/3 + , update_config/2 + ]). + -export([ enable/0 , disable/0 , is_enabled/0 @@ -46,8 +55,6 @@ , list_users/2 ]). --export([mnesia/1]). - -boot_mnesia({mnesia, [boot]}). -copy_mnesia({mnesia, [copy]}). @@ -75,6 +82,97 @@ mnesia(boot) -> mnesia(copy) -> ok = ekka_mnesia:copy_table(?CHAIN_TAB, ram_copies). +%%------------------------------------------------------------------------------ +%% APIs +%%------------------------------------------------------------------------------ + +pre_config_update({enable, Enable}, _OldConfig) -> + Enable; +pre_config_update({create_authenticator, Config}, OldConfig) -> + OldConfig ++ [Config]; +pre_config_update({delete_authenticator, ID}, OldConfig) -> + case lookup_authenticator(?CHAIN, ID) of + {error, Reason} -> error(Reason); + {ok, #{name := Name}} -> + lists:filter(fun(#{<<"name">> := N}) -> + N =/= Name + end, OldConfig) + end; +pre_config_update({update_authenticator, ID, Config}, OldConfig) -> + case lookup_authenticator(?CHAIN, ID) of + {error, Reason} -> error(Reason); + {ok, #{name := Name}} -> + lists:map(fun(#{<<"name">> := N} = C) -> + case N =:= Name of + true -> Config; + false -> C + end + end, OldConfig) + end; +pre_config_update({update_or_create_authenticator, ID, Config}, OldConfig) -> + case lookup_authenticator(?CHAIN, ID) of + {error, _Reason} -> OldConfig ++ [Config]; + {ok, #{name := Name}} -> + lists:map(fun(#{<<"name">> := N} = C) -> + case N =:= Name of + true -> Config; + false -> C + end + end, OldConfig) + end. + +post_config_update({enable, true}, _NewConfig, _OldConfig) -> + emqx_authn:enable(); +post_config_update({enable, false}, _NewConfig, _OldConfig) -> + emqx_authn:disable(); +post_config_update({create_authenticator, #{<<"name">> := Name}}, NewConfig, _OldConfig) -> + case lists:filter( + fun(#{name := N}) -> + N =:= Name + end, NewConfig) of + [Config] -> + case create_authenticator(?CHAIN, Config) of + {ok, _} -> ok; + {error, Reason} -> throw(Reason) + end; + [_Config | _] -> + error(name_has_be_used) + end; +post_config_update({delete_authenticator, ID}, _NewConfig, _OldConfig) -> + case delete_authenticator(?CHAIN, ID) of + ok -> ok; + {error, Reason} -> throw(Reason) + end; +post_config_update({update_authenticator, ID, #{<<"name">> := Name}}, NewConfig, _OldConfig) -> + case lists:filter( + fun(#{name := N}) -> + N =:= Name + end, NewConfig) of + [Config] -> + case update_authenticator(?CHAIN, ID, Config) of + {ok, _} -> ok; + {error, Reason} -> throw(Reason) + end; + [_Config | _] -> + error(name_has_be_used) + end; +post_config_update({update_or_create_authenticator, ID, #{<<"name">> := Name}}, NewConfig, _OldConfig) -> + case lists:filter( + fun(#{name := N}) -> + N =:= Name + end, NewConfig) of + [Config] -> + case update_or_create_authenticator(?CHAIN, ID, Config) of + {ok, _} -> ok; + {error, Reason} -> throw(Reason) + end; + [_Config | _] -> + error(name_has_be_used) + end. + +update_config(Path, ConfigRequest) -> + emqx_config:update(emqx_authn_schema, Path, ConfigRequest). + enable() -> case emqx:hook('client.authenticate', {?MODULE, authenticate, []}) of ok -> ok; diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 78ef5fd35..1e232d553 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -1253,14 +1253,9 @@ definitions() -> authentication(post, Request) -> {ok, Body, _} = cowboy_req:read_body(Request), case emqx_json:decode(Body, [return_maps]) of - #{<<"enable">> := true} -> - ok = emqx_authn:enable(), + #{<<"enable">> := Enable} -> + emqx_authn:update_config([authentication, enable], {enable, Enable}), {204}; - #{<<"enable">> := false} -> - ok = emqx_authn:disable(), - {204}; - #{<<"enable">> := _} -> - serialize_error({invalid_parameter, enable}); _ -> serialize_error({missing_parameter, enable}) end; @@ -1270,16 +1265,10 @@ authentication(get, _Request) -> authenticators(post, Request) -> {ok, Body, _} = cowboy_req:read_body(Request), - AuthenticatorConfig = emqx_json:decode(Body, [return_maps]), - Config = #{<<"authentication">> => #{ - <<"authenticators">> => [AuthenticatorConfig] - }}, - NConfig = hocon_schema:check_plain(emqx_authn_schema, Config, - #{nullable => true}), - #{authentication := #{authenticators := [NAuthenticatorConfig]}} = emqx_map_lib:unsafe_atom_key_map(NConfig), - case emqx_authn:create_authenticator(?CHAIN, NAuthenticatorConfig) of - {ok, Authenticator2} -> - {201, Authenticator2}; + Config = emqx_json:decode(Body, [return_maps]), + case emqx_authn:update_config([authentication, authenticators], {create_authenticator, Config}) of + ok -> + {204}; {error, Reason} -> serialize_error(Reason) end; @@ -1298,22 +1287,17 @@ authenticators2(get, Request) -> authenticators2(put, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), {ok, Body, _} = cowboy_req:read_body(Request), - AuthenticatorConfig = emqx_json:decode(Body, [return_maps]), - Config = #{<<"authentication">> => #{ - <<"authenticators">> => [AuthenticatorConfig] - }}, - NConfig = hocon_schema:check_plain(emqx_authn_schema, Config, - #{nullable => true}), - #{authentication := #{authenticators := [NAuthenticatorConfig]}} = emqx_map_lib:unsafe_atom_key_map(NConfig), - case emqx_authn:update_or_create_authenticator(?CHAIN, AuthenticatorID, NAuthenticatorConfig) of - {ok, Authenticator} -> - {200, Authenticator}; + Config = emqx_json:decode(Body, [return_maps]), + case emqx_authn:update_config([authentication, authenticators], + {update_or_create_authenticator, AuthenticatorID, Config}) of + ok -> + {204}; {error, Reason} -> serialize_error(Reason) end; authenticators2(delete, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), - case emqx_authn:delete_authenticator(?CHAIN, AuthenticatorID) of + case emqx_authn:update_config([authentication, authenticators], {delete_authenticator, AuthenticatorID}) of ok -> {204}; {error, Reason} -> @@ -1324,7 +1308,7 @@ position(post, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), {ok, Body, _} = cowboy_req:read_body(Request), NBody = emqx_json:decode(Body, [return_maps]), - Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"position">> => NBody}, + Config = hocon_schema:check_plain(emqx_authn_implied_schema, #{<<"position">> => NBody}, #{nullable => true}, ["position"]), #{position := #{position := Position}} = emqx_map_lib:unsafe_atom_key_map(Config), case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of @@ -1338,7 +1322,7 @@ import_users(post, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), {ok, Body, _} = cowboy_req:read_body(Request), NBody = emqx_json:decode(Body, [return_maps]), - Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"filename">> => NBody}, + Config = hocon_schema:check_plain(emqx_authn_implied_schema, #{<<"filename">> => NBody}, #{nullable => true}, ["filename"]), #{filename := #{filename := Filename}} = emqx_map_lib:unsafe_atom_key_map(Config), case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of @@ -1352,7 +1336,7 @@ users(post, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), {ok, Body, _} = cowboy_req:read_body(Request), NBody = emqx_json:decode(Body, [return_maps]), - Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"user_info">> => NBody}, + Config = hocon_schema:check_plain(emqx_authn_implied_schema, #{<<"user_info">> => NBody}, #{nullable => true}, ["user_info"]), #{user_info := UserInfo} = emqx_map_lib:unsafe_atom_key_map(Config), case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of @@ -1375,7 +1359,7 @@ users2(patch, Request) -> UserID = cowboy_req:binding(user_id, Request), {ok, Body, _} = cowboy_req:read_body(Request), NBody = emqx_json:decode(Body, [return_maps]), - Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"new_user_info">> => NBody}, + Config = hocon_schema:check_plain(emqx_authn_implied_schema, #{<<"new_user_info">> => NBody}, #{nullable => true}, ["new_user_info"]), #{new_user_info := NewUserInfo} = emqx_map_lib:unsafe_atom_key_map(Config), case emqx_authn:update_user(?CHAIN, AuthenticatorID, UserID, NewUserInfo) of diff --git a/apps/emqx_authn/src/emqx_authn_app.erl b/apps/emqx_authn/src/emqx_authn_app.erl index 7518e5a01..b7f409bc9 100644 --- a/apps/emqx_authn/src/emqx_authn_app.erl +++ b/apps/emqx_authn/src/emqx_authn_app.erl @@ -29,6 +29,7 @@ start(_StartType, _StartArgs) -> ok = ekka_rlog:wait_for_shards([?AUTH_SHARD], infinity), {ok, Sup} = emqx_authn_sup:start_link(), + emqx_config_handler:add_handler([authentication, authenticators], emqx_authn), initialize(), {ok, Sup}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_other_schema.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_implied_schema.erl similarity index 97% rename from apps/emqx_authn/src/simple_authn/emqx_authn_other_schema.erl rename to apps/emqx_authn/src/simple_authn/emqx_authn_implied_schema.erl index 0f5c8abb8..1a0731e92 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_other_schema.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_implied_schema.erl @@ -14,7 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authn_other_schema). +-module(emqx_authn_implied_schema). -include("emqx_authn.hrl"). -include_lib("typerefl/include/types.hrl").