feat(auth): support hot config

This commit is contained in:
zhouzb 2021-08-18 18:24:52 +08:00
parent e6f9767066
commit e5892d16e5
4 changed files with 82 additions and 79 deletions

View File

@ -26,7 +26,6 @@
{ id :: binary() { id :: binary()
, name :: binary() , name :: binary()
, provider :: module() , provider :: module()
, config :: map()
, state :: map() , state :: map()
}). }).

View File

@ -76,60 +76,63 @@
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
pre_config_update({enable, Enable}, _OldConfig) -> pre_config_update({enable, Enable}, _OldConfig) ->
Enable; {ok, Enable};
pre_config_update({create_authenticator, Config}, OldConfig) -> pre_config_update({create_authenticator, Config}, OldConfig) ->
OldConfig ++ [Config]; {ok, OldConfig ++ [Config]};
pre_config_update({delete_authenticator, ID}, OldConfig) -> pre_config_update({delete_authenticator, ID}, OldConfig) ->
case lookup_authenticator(?CHAIN, ID) of case lookup_authenticator(?CHAIN, ID) of
{error, Reason} -> error(Reason); {error, Reason} -> {error, Reason};
{ok, #{name := Name}} -> {ok, #{name := Name}} ->
lists:filter(fun(#{<<"name">> := N}) -> NewConfig = lists:filter(fun(#{<<"name">> := N}) ->
N =/= Name N =/= Name
end, OldConfig) end, OldConfig),
{ok, NewConfig}
end; end;
pre_config_update({update_authenticator, ID, Config}, OldConfig) -> pre_config_update({update_authenticator, ID, Config}, OldConfig) ->
case lookup_authenticator(?CHAIN, ID) of case lookup_authenticator(?CHAIN, ID) of
{error, Reason} -> error(Reason); {error, Reason} -> {error, Reason};
{ok, #{name := Name}} -> {ok, #{name := Name}} ->
lists:map(fun(#{<<"name">> := N} = C) -> NewConfig = lists:map(fun(#{<<"name">> := N} = C) ->
case N =:= Name of case N =:= Name of
true -> Config; true -> Config;
false -> C false -> C
end end
end, OldConfig) end, OldConfig),
{ok, NewConfig}
end; end;
pre_config_update({update_or_create_authenticator, ID, Config}, OldConfig) -> pre_config_update({update_or_create_authenticator, ID, Config}, OldConfig) ->
case lookup_authenticator(?CHAIN, ID) of case lookup_authenticator(?CHAIN, ID) of
{error, _Reason} -> OldConfig ++ [Config]; {error, _Reason} -> OldConfig ++ [Config];
{ok, #{name := Name}} -> {ok, #{name := Name}} ->
lists:map(fun(#{<<"name">> := N} = C) -> NewConfig = lists:map(fun(#{<<"name">> := N} = C) ->
case N =:= Name of case N =:= Name of
true -> Config; true -> Config;
false -> C false -> C
end end
end, OldConfig) end, OldConfig),
{ok, NewConfig}
end; end;
pre_config_update({move, ID, Position}, OldConfig) -> pre_config_update({move_authenticator, ID, Position}, OldConfig) ->
case lookup_authenticator(?CHAIN, ID) of case lookup_authenticator(?CHAIN, ID) of
{error, Reason} -> error(Reason); {error, Reason} -> {error, Reason};
{ok, #{name := Name}} -> {ok, #{name := Name}} ->
{ok, Found, Part1, Part2} = split_by_name(Name, OldConfig), {ok, Found, Part1, Part2} = split_by_name(Name, OldConfig),
case Position of case Position of
<<"top">> -> <<"top">> ->
[Found | Part1] ++ Part2; {ok, [Found | Part1] ++ Part2};
<<"bottom">> -> <<"bottom">> ->
Part1 ++ Part2 ++ [Found]; {ok, Part1 ++ Part2 ++ [Found]};
Before -> Before ->
case binary:split(Before, <<":">>, [global]) of case binary:split(Before, <<":">>, [global]) of
[<<"before">>, ID0] -> [<<"before">>, ID0] ->
case lookup_authenticator(?CHAIN, ID0) of case lookup_authenticator(?CHAIN, ID0) of
{error, Reason} -> error(Reason); {error, Reason} -> {error, Reason};
{ok, #{name := Name1}} -> {ok, #{name := Name1}} ->
{ok, NFound, NPart1, NPart2} = split_by_name(Name1, Part1 + Part2), {ok, NFound, NPart1, NPart2} = split_by_name(Name1, Part1 ++ Part2),
NPart1 ++ [Found, NFound | NPart2] {ok, NPart1 ++ [Found, NFound | NPart2]}
end; end;
_ -> _ ->
error({invalid_parameter, position}) {error, {invalid_parameter, position}}
end end
end end
end. end.
@ -144,12 +147,9 @@ post_config_update({create_authenticator, #{<<"name">> := Name}}, NewConfig, _Ol
N =:= Name N =:= Name
end, NewConfig) of end, NewConfig) of
[Config] -> [Config] ->
case create_authenticator(?CHAIN, Config) of create_authenticator(?CHAIN, Config);
{ok, _} -> ok;
{error, Reason} -> throw(Reason)
end;
[_Config | _] -> [_Config | _] ->
error(name_has_be_used) {error, name_has_be_used}
end; end;
post_config_update({delete_authenticator, ID}, _NewConfig, _OldConfig) -> post_config_update({delete_authenticator, ID}, _NewConfig, _OldConfig) ->
case delete_authenticator(?CHAIN, ID) of case delete_authenticator(?CHAIN, ID) of
@ -162,12 +162,9 @@ post_config_update({update_authenticator, ID, #{<<"name">> := Name}}, NewConfig,
N =:= Name N =:= Name
end, NewConfig) of end, NewConfig) of
[Config] -> [Config] ->
case update_authenticator(?CHAIN, ID, Config) of update_authenticator(?CHAIN, ID, Config);
{ok, _} -> ok;
{error, Reason} -> throw(Reason)
end;
[_Config | _] -> [_Config | _] ->
error(name_has_be_used) {error, name_has_be_used}
end; end;
post_config_update({update_or_create_authenticator, ID, #{<<"name">> := Name}}, NewConfig, _OldConfig) -> post_config_update({update_or_create_authenticator, ID, #{<<"name">> := Name}}, NewConfig, _OldConfig) ->
case lists:filter( case lists:filter(
@ -175,14 +172,11 @@ post_config_update({update_or_create_authenticator, ID, #{<<"name">> := Name}},
N =:= Name N =:= Name
end, NewConfig) of end, NewConfig) of
[Config] -> [Config] ->
case update_or_create_authenticator(?CHAIN, ID, Config) of update_or_create_authenticator(?CHAIN, ID, Config);
{ok, _} -> ok;
{error, Reason} -> throw(Reason)
end;
[_Config | _] -> [_Config | _] ->
error(name_has_be_used) {error, name_has_be_used}
end; end;
post_config_update({move, ID, Position}, _NewConfig, _OldConfig) -> post_config_update({move_authenticator, ID, Position}, _NewConfig, _OldConfig) ->
NPosition = case Position of NPosition = case Position of
<<"top">> -> top; <<"top">> -> top;
<<"bottom">> -> bottom; <<"bottom">> -> bottom;
@ -191,16 +185,13 @@ post_config_update({move, ID, Position}, _NewConfig, _OldConfig) ->
[<<"before">>, ID0] -> [<<"before">>, ID0] ->
{before, ID0}; {before, ID0};
_ -> _ ->
error({invalid_parameter, position}) {error, {invalid_parameter, position}}
end end
end, end,
case move_authenticator(?CHAIN, ID, NPosition) of move_authenticator(?CHAIN, ID, NPosition).
ok -> ok;
{error, Reason} -> throw(Reason)
end.
update_config(Path, ConfigRequest) -> update_config(Path, ConfigRequest) ->
emqx_config:update(emqx_authn_schema, Path, ConfigRequest). emqx:update_config(Path, ConfigRequest, #{rawconf_with_defaults => true}).
enable() -> enable() ->
case emqx:hook('client.authenticate', {?MODULE, authenticate, []}) of case emqx:hook('client.authenticate', {?MODULE, authenticate, []}) of
@ -522,7 +513,6 @@ do_create_authenticator(ChainID, AuthenticatorID, #{name := Name} = Config) ->
Authenticator = #authenticator{id = AuthenticatorID, Authenticator = #authenticator{id = AuthenticatorID,
name = Name, name = Name,
provider = Provider, provider = Provider,
config = Config,
state = switch_version(State)}, state = switch_version(State)},
{ok, Authenticator}; {ok, Authenticator};
{error, Reason} -> {error, Reason} ->
@ -570,8 +560,7 @@ update_or_create_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Co
case Provider:update(Config#{'_unique' => Unique}, State) of case Provider:update(Config#{'_unique' => Unique}, State) of
{ok, NewState} -> {ok, NewState} ->
NewAuthenticator = Authenticator#authenticator{name = NewName, NewAuthenticator = Authenticator#authenticator{name = NewName,
config = Config, state = switch_version(NewState)},
state = switch_version(NewState)},
NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators), NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
true = ets:insert(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}), true = ets:insert(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}),
{ok, serialize_authenticator(NewAuthenticator)}; {ok, serialize_authenticator(NewAuthenticator)};
@ -583,9 +572,8 @@ update_or_create_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Co
case NewProvider:create(Config#{'_unique' => Unique}) of case NewProvider:create(Config#{'_unique' => Unique}) of
{ok, NewState} -> {ok, NewState} ->
NewAuthenticator = Authenticator#authenticator{name = NewName, NewAuthenticator = Authenticator#authenticator{name = NewName,
provider = NewProvider, provider = NewProvider,
config = Config, state = switch_version(NewState)},
state = switch_version(NewState)},
NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators), NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
true = ets:insert(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}), true = ets:insert(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}),
_ = Provider:destroy(State), _ = Provider:destroy(State),
@ -660,5 +648,7 @@ serialize_authenticators(Authenticators) ->
[serialize_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators]. [serialize_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators].
serialize_authenticator(#authenticator{id = ID, serialize_authenticator(#authenticator{id = ID,
config = Config}) -> name = Name,
Config#{id => ID}. provider = Provider,
state = State}) ->
#{id => ID, name => Name, provider => Provider, state => State}.

View File

@ -790,6 +790,7 @@ definitions() ->
, minirest:ref(<<"password_based_mysql">>) , minirest:ref(<<"password_based_mysql">>)
, minirest:ref(<<"password_based_pgsql">>) , minirest:ref(<<"password_based_pgsql">>)
, minirest:ref(<<"password_based_mongodb">>) , minirest:ref(<<"password_based_mongodb">>)
, minirest:ref(<<"password_based_redis">>)
, minirest:ref(<<"password_based_http_server">>) , minirest:ref(<<"password_based_http_server">>)
] ]
} }
@ -1292,7 +1293,7 @@ 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">> := Enable} -> #{<<"enable">> := Enable} ->
emqx_authn:update_config([authentication, enable], {enable, Enable}), {ok, _} = emqx_authn:update_config([authentication, enable], {enable, Enable}),
{204}; {204};
_ -> _ ->
serialize_error({missing_parameter, enable}) serialize_error({missing_parameter, enable})
@ -1305,20 +1306,28 @@ authenticators(post, Request) ->
{ok, Body, _} = cowboy_req:read_body(Request), {ok, Body, _} = cowboy_req:read_body(Request),
Config = emqx_json:decode(Body, [return_maps]), Config = emqx_json:decode(Body, [return_maps]),
case emqx_authn:update_config([authentication, authenticators], {create_authenticator, Config}) of case emqx_authn:update_config([authentication, authenticators], {create_authenticator, Config}) of
ok -> {ok, #{post_config_update := #{emqx_authn := #{id := ID, name := Name}},
{204}; raw_config := RawConfig}} ->
{error, Reason} -> [RawConfig1] = [RC || #{<<"name">> := N} = RC <- RawConfig, N =:= Name],
{200, RawConfig1#{id => ID}};
{error, {_, _, Reason}} ->
serialize_error(Reason) serialize_error(Reason)
end; end;
authenticators(get, _Request) -> authenticators(get, _Request) ->
RawConfig = get_raw_config([authentication, authenticators]),
{ok, Authenticators} = emqx_authn:list_authenticators(?CHAIN), {ok, Authenticators} = emqx_authn:list_authenticators(?CHAIN),
{200, Authenticators}. NAuthenticators = lists:zipwith(fun(#{<<"name">> := Name} = Config, #{id := ID, name := Name}) ->
Config#{id => ID}
end, RawConfig, Authenticators),
{200, NAuthenticators}.
authenticators2(get, Request) -> authenticators2(get, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request), AuthenticatorID = cowboy_req:binding(id, Request),
case emqx_authn:lookup_authenticator(?CHAIN, AuthenticatorID) of case emqx_authn:lookup_authenticator(?CHAIN, AuthenticatorID) of
{ok, Authenticator} -> {ok, #{id := ID, name := Name}} ->
{200, Authenticator}; RawConfig = get_raw_config([authentication, authenticators]),
[RawConfig1] = [RC || #{<<"name">> := N} = RC <- RawConfig, N =:= Name],
{200, RawConfig1#{id => ID}};
{error, Reason} -> {error, Reason} ->
serialize_error(Reason) serialize_error(Reason)
end; end;
@ -1328,17 +1337,19 @@ authenticators2(put, Request) ->
Config = emqx_json:decode(Body, [return_maps]), Config = emqx_json:decode(Body, [return_maps]),
case emqx_authn:update_config([authentication, authenticators], case emqx_authn:update_config([authentication, authenticators],
{update_or_create_authenticator, AuthenticatorID, Config}) of {update_or_create_authenticator, AuthenticatorID, Config}) of
ok -> {ok, #{post_config_update := #{emqx_authn := #{id := ID, name := Name}},
{204}; raw_config := RawConfig}} ->
{error, Reason} -> [RawConfig0] = [RC || #{<<"name">> := N} = RC <- RawConfig, N =:= Name],
{200, RawConfig0#{id => ID}};
{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:update_config([authentication, authenticators], {delete_authenticator, AuthenticatorID}) of case emqx_authn:update_config([authentication, authenticators], {delete_authenticator, AuthenticatorID}) of
ok -> {ok, _} ->
{204}; {204};
{error, Reason} -> {error, {_, _, Reason}} ->
serialize_error(Reason) serialize_error(Reason)
end. end.
@ -1348,8 +1359,8 @@ move(post, Request) ->
case emqx_json:decode(Body, [return_maps]) of case emqx_json:decode(Body, [return_maps]) of
#{<<"position">> := Position} -> #{<<"position">> := Position} ->
case emqx_authn:update_config([authentication, authenticators], {move_authenticator, AuthenticatorID, Position}) of case emqx_authn:update_config([authentication, authenticators], {move_authenticator, AuthenticatorID, Position}) of
ok -> {204}; {ok, _} -> {204};
{error, Reason} -> serialize_error(Reason) {error, {_, _, Reason}} -> serialize_error(Reason)
end; end;
_ -> _ ->
serialize_error({missing_parameter, position}) serialize_error({missing_parameter, position})
@ -1361,10 +1372,8 @@ import_users(post, Request) ->
case emqx_json:decode(Body, [return_maps]) of case emqx_json:decode(Body, [return_maps]) of
#{<<"filename">> := Filename} -> #{<<"filename">> := Filename} ->
case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of
ok -> ok -> {204};
{204}; {error, Reason} -> serialize_error(Reason)
{error, Reason} ->
serialize_error(Reason)
end; end;
_ -> _ ->
serialize_error({missing_parameter, filename}) serialize_error({missing_parameter, filename})
@ -1435,6 +1444,11 @@ users2(delete, Request) ->
serialize_error(Reason) serialize_error(Reason)
end. end.
get_raw_config(ConfKeyPath) ->
%% TODO: call emqx_config:get_raw(ConfKeyPath) directly
NConfKeyPath = [atom_to_binary(Key, utf8) || Key <- ConfKeyPath],
emqx_map_lib:deep_get(NConfKeyPath, emqx_config:fill_defaults(emqx_config:get_raw([]))).
serialize_error({not_found, {authenticator, ID}}) -> serialize_error({not_found, {authenticator, ID}}) ->
{404, #{code => <<"NOT_FOUND">>, {404, #{code => <<"NOT_FOUND">>,
message => list_to_binary(io_lib:format("Authenticator '~s' does not exist", [ID]))}}; message => list_to_binary(io_lib:format("Authenticator '~s' does not exist", [ID]))}};

View File

@ -71,7 +71,7 @@ t_authenticator(_) ->
secret => <<"abcdef">>, secret => <<"abcdef">>,
secret_base64_encoded => false, secret_base64_encoded => false,
verify_claims => []}, verify_claims => []},
{ok, #{name := AuthenticatorName1, id := ID1, mechanism := jwt}} = ?AUTH:update_authenticator(?CHAIN, ID1, AuthenticatorConfig2), {ok, #{name := AuthenticatorName1, id := ID1}} = ?AUTH:update_authenticator(?CHAIN, ID1, AuthenticatorConfig2),
ID2 = <<"random">>, ID2 = <<"random">>,
?assertEqual({error, {not_found, {authenticator, ID2}}}, ?AUTH:update_authenticator(?CHAIN, ID2, AuthenticatorConfig2)), ?assertEqual({error, {not_found, {authenticator, ID2}}}, ?AUTH:update_authenticator(?CHAIN, ID2, AuthenticatorConfig2)),
@ -79,9 +79,9 @@ t_authenticator(_) ->
AuthenticatorName2 = <<"myauthenticator2">>, AuthenticatorName2 = <<"myauthenticator2">>,
AuthenticatorConfig3 = AuthenticatorConfig2#{name => AuthenticatorName2}, AuthenticatorConfig3 = AuthenticatorConfig2#{name => AuthenticatorName2},
{ok, #{name := AuthenticatorName2, id := ID2, secret := <<"abcdef">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3), {ok, #{name := AuthenticatorName2, id := ID2}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3),
?assertMatch({ok, #{name := AuthenticatorName2}}, ?AUTH:lookup_authenticator(?CHAIN, ID2)), ?assertMatch({ok, #{name := AuthenticatorName2}}, ?AUTH:lookup_authenticator(?CHAIN, ID2)),
{ok, #{name := AuthenticatorName2, id := ID2, secret := <<"fedcba">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3#{secret := <<"fedcba">>}), {ok, #{name := AuthenticatorName2, id := ID2}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3#{secret := <<"fedcba">>}),
?assertMatch({ok, #{id := ?CHAIN, authenticators := [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}}, ?AUTH:lookup_chain(?CHAIN)), ?assertMatch({ok, #{id := ?CHAIN, authenticators := [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}}, ?AUTH:lookup_chain(?CHAIN)),
?assertMatch({ok, [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}, ?AUTH:list_authenticators(?CHAIN)), ?assertMatch({ok, [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}, ?AUTH:list_authenticators(?CHAIN)),