feat(authn hot config): initial support for hot config

This commit is contained in:
zhouzb 2021-08-10 14:47:22 +08:00
parent ef59309ed0
commit 61da3a4fd7
4 changed files with 118 additions and 35 deletions

View File

@ -16,8 +16,17 @@
-module(emqx_authn). -module(emqx_authn).
-behaviour(emqx_config_handler).
-include("emqx_authn.hrl"). -include("emqx_authn.hrl").
-export([mnesia/1]).
-export([ pre_config_update/2
, post_config_update/3
, update_config/2
]).
-export([ enable/0 -export([ enable/0
, disable/0 , disable/0
, is_enabled/0 , is_enabled/0
@ -46,8 +55,6 @@
, list_users/2 , list_users/2
]). ]).
-export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}). -copy_mnesia({mnesia, [copy]}).
@ -75,6 +82,97 @@ mnesia(boot) ->
mnesia(copy) -> mnesia(copy) ->
ok = ekka_mnesia:copy_table(?CHAIN_TAB, ram_copies). 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() -> enable() ->
case emqx:hook('client.authenticate', {?MODULE, authenticate, []}) of case emqx:hook('client.authenticate', {?MODULE, authenticate, []}) of
ok -> ok; ok -> ok;

View File

@ -1253,14 +1253,9 @@ definitions() ->
authentication(post, Request) -> authentication(post, Request) ->
{ok, Body, _} = cowboy_req:read_body(Request), {ok, Body, _} = cowboy_req:read_body(Request),
case emqx_json:decode(Body, [return_maps]) of case emqx_json:decode(Body, [return_maps]) of
#{<<"enable">> := true} -> #{<<"enable">> := Enable} ->
ok = emqx_authn:enable(), emqx_authn:update_config([authentication, enable], {enable, Enable}),
{204}; {204};
#{<<"enable">> := false} ->
ok = emqx_authn:disable(),
{204};
#{<<"enable">> := _} ->
serialize_error({invalid_parameter, enable});
_ -> _ ->
serialize_error({missing_parameter, enable}) serialize_error({missing_parameter, enable})
end; end;
@ -1270,16 +1265,10 @@ authentication(get, _Request) ->
authenticators(post, Request) -> authenticators(post, Request) ->
{ok, Body, _} = cowboy_req:read_body(Request), {ok, Body, _} = cowboy_req:read_body(Request),
AuthenticatorConfig = emqx_json:decode(Body, [return_maps]), Config = emqx_json:decode(Body, [return_maps]),
Config = #{<<"authentication">> => #{ case emqx_authn:update_config([authentication, authenticators], {create_authenticator, Config}) of
<<"authenticators">> => [AuthenticatorConfig] ok ->
}}, {204};
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};
{error, Reason} -> {error, Reason} ->
serialize_error(Reason) serialize_error(Reason)
end; end;
@ -1298,22 +1287,17 @@ authenticators2(get, Request) ->
authenticators2(put, Request) -> authenticators2(put, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request), AuthenticatorID = cowboy_req:binding(id, Request),
{ok, Body, _} = cowboy_req:read_body(Request), {ok, Body, _} = cowboy_req:read_body(Request),
AuthenticatorConfig = emqx_json:decode(Body, [return_maps]), Config = emqx_json:decode(Body, [return_maps]),
Config = #{<<"authentication">> => #{ case emqx_authn:update_config([authentication, authenticators],
<<"authenticators">> => [AuthenticatorConfig] {update_or_create_authenticator, AuthenticatorID, Config}) of
}}, ok ->
NConfig = hocon_schema:check_plain(emqx_authn_schema, Config, {204};
#{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};
{error, Reason} -> {error, Reason} ->
serialize_error(Reason) serialize_error(Reason)
end; end;
authenticators2(delete, Request) -> authenticators2(delete, Request) ->
AuthenticatorID = cowboy_req:binding(id, 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 -> ok ->
{204}; {204};
{error, Reason} -> {error, Reason} ->
@ -1324,7 +1308,7 @@ position(post, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request), AuthenticatorID = cowboy_req:binding(id, Request),
{ok, Body, _} = cowboy_req:read_body(Request), {ok, Body, _} = cowboy_req:read_body(Request),
NBody = emqx_json:decode(Body, [return_maps]), 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"]), #{nullable => true}, ["position"]),
#{position := #{position := Position}} = emqx_map_lib:unsafe_atom_key_map(Config), #{position := #{position := Position}} = emqx_map_lib:unsafe_atom_key_map(Config),
case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of 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), AuthenticatorID = cowboy_req:binding(id, Request),
{ok, Body, _} = cowboy_req:read_body(Request), {ok, Body, _} = cowboy_req:read_body(Request),
NBody = emqx_json:decode(Body, [return_maps]), 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"]), #{nullable => true}, ["filename"]),
#{filename := #{filename := Filename}} = emqx_map_lib:unsafe_atom_key_map(Config), #{filename := #{filename := Filename}} = emqx_map_lib:unsafe_atom_key_map(Config),
case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of
@ -1352,7 +1336,7 @@ users(post, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request), AuthenticatorID = cowboy_req:binding(id, Request),
{ok, Body, _} = cowboy_req:read_body(Request), {ok, Body, _} = cowboy_req:read_body(Request),
NBody = emqx_json:decode(Body, [return_maps]), 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"]), #{nullable => true}, ["user_info"]),
#{user_info := UserInfo} = emqx_map_lib:unsafe_atom_key_map(Config), #{user_info := UserInfo} = emqx_map_lib:unsafe_atom_key_map(Config),
case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of
@ -1375,7 +1359,7 @@ users2(patch, Request) ->
UserID = cowboy_req:binding(user_id, Request), UserID = cowboy_req:binding(user_id, Request),
{ok, Body, _} = cowboy_req:read_body(Request), {ok, Body, _} = cowboy_req:read_body(Request),
NBody = emqx_json:decode(Body, [return_maps]), 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"]), #{nullable => true}, ["new_user_info"]),
#{new_user_info := NewUserInfo} = emqx_map_lib:unsafe_atom_key_map(Config), #{new_user_info := NewUserInfo} = emqx_map_lib:unsafe_atom_key_map(Config),
case emqx_authn:update_user(?CHAIN, AuthenticatorID, UserID, NewUserInfo) of case emqx_authn:update_user(?CHAIN, AuthenticatorID, UserID, NewUserInfo) of

View File

@ -29,6 +29,7 @@
start(_StartType, _StartArgs) -> start(_StartType, _StartArgs) ->
ok = ekka_rlog:wait_for_shards([?AUTH_SHARD], infinity), ok = ekka_rlog:wait_for_shards([?AUTH_SHARD], infinity),
{ok, Sup} = emqx_authn_sup:start_link(), {ok, Sup} = emqx_authn_sup:start_link(),
emqx_config_handler:add_handler([authentication, authenticators], emqx_authn),
initialize(), initialize(),
{ok, Sup}. {ok, Sup}.

View File

@ -14,7 +14,7 @@
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-module(emqx_authn_other_schema). -module(emqx_authn_implied_schema).
-include("emqx_authn.hrl"). -include("emqx_authn.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").