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()
, name :: binary()
, provider :: module()
, config :: map()
, state :: map()
}).

View File

@ -76,60 +76,63 @@
%%------------------------------------------------------------------------------
pre_config_update({enable, Enable}, _OldConfig) ->
Enable;
{ok, Enable};
pre_config_update({create_authenticator, Config}, OldConfig) ->
OldConfig ++ [Config];
{ok, OldConfig ++ [Config]};
pre_config_update({delete_authenticator, ID}, OldConfig) ->
case lookup_authenticator(?CHAIN, ID) of
{error, Reason} -> error(Reason);
{error, Reason} -> {error, Reason};
{ok, #{name := Name}} ->
lists:filter(fun(#{<<"name">> := N}) ->
NewConfig = lists:filter(fun(#{<<"name">> := N}) ->
N =/= Name
end, OldConfig)
end, OldConfig),
{ok, NewConfig}
end;
pre_config_update({update_authenticator, ID, Config}, OldConfig) ->
case lookup_authenticator(?CHAIN, ID) of
{error, Reason} -> error(Reason);
{error, Reason} -> {error, Reason};
{ok, #{name := Name}} ->
lists:map(fun(#{<<"name">> := N} = C) ->
NewConfig = lists:map(fun(#{<<"name">> := N} = C) ->
case N =:= Name of
true -> Config;
false -> C
end
end, OldConfig)
end, OldConfig),
{ok, NewConfig}
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) ->
NewConfig = lists:map(fun(#{<<"name">> := N} = C) ->
case N =:= Name of
true -> Config;
false -> C
end
end, OldConfig)
end, OldConfig),
{ok, NewConfig}
end;
pre_config_update({move, ID, Position}, OldConfig) ->
pre_config_update({move_authenticator, ID, Position}, OldConfig) ->
case lookup_authenticator(?CHAIN, ID) of
{error, Reason} -> error(Reason);
{error, Reason} -> {error, Reason};
{ok, #{name := Name}} ->
{ok, Found, Part1, Part2} = split_by_name(Name, OldConfig),
case Position of
<<"top">> ->
[Found | Part1] ++ Part2;
{ok, [Found | Part1] ++ Part2};
<<"bottom">> ->
Part1 ++ Part2 ++ [Found];
{ok, Part1 ++ Part2 ++ [Found]};
Before ->
case binary:split(Before, <<":">>, [global]) of
[<<"before">>, ID0] ->
case lookup_authenticator(?CHAIN, ID0) of
{error, Reason} -> error(Reason);
{error, Reason} -> {error, Reason};
{ok, #{name := Name1}} ->
{ok, NFound, NPart1, NPart2} = split_by_name(Name1, Part1 + Part2),
NPart1 ++ [Found, NFound | NPart2]
{ok, NFound, NPart1, NPart2} = split_by_name(Name1, Part1 ++ Part2),
{ok, NPart1 ++ [Found, NFound | NPart2]}
end;
_ ->
error({invalid_parameter, position})
{error, {invalid_parameter, position}}
end
end
end.
@ -144,12 +147,9 @@ post_config_update({create_authenticator, #{<<"name">> := Name}}, NewConfig, _Ol
N =:= Name
end, NewConfig) of
[Config] ->
case create_authenticator(?CHAIN, Config) of
{ok, _} -> ok;
{error, Reason} -> throw(Reason)
end;
create_authenticator(?CHAIN, Config);
[_Config | _] ->
error(name_has_be_used)
{error, name_has_be_used}
end;
post_config_update({delete_authenticator, ID}, _NewConfig, _OldConfig) ->
case delete_authenticator(?CHAIN, ID) of
@ -162,12 +162,9 @@ post_config_update({update_authenticator, ID, #{<<"name">> := Name}}, NewConfig,
N =:= Name
end, NewConfig) of
[Config] ->
case update_authenticator(?CHAIN, ID, Config) of
{ok, _} -> ok;
{error, Reason} -> throw(Reason)
end;
update_authenticator(?CHAIN, ID, Config);
[_Config | _] ->
error(name_has_be_used)
{error, name_has_be_used}
end;
post_config_update({update_or_create_authenticator, ID, #{<<"name">> := Name}}, NewConfig, _OldConfig) ->
case lists:filter(
@ -175,14 +172,11 @@ post_config_update({update_or_create_authenticator, ID, #{<<"name">> := Name}},
N =:= Name
end, NewConfig) of
[Config] ->
case update_or_create_authenticator(?CHAIN, ID, Config) of
{ok, _} -> ok;
{error, Reason} -> throw(Reason)
end;
update_or_create_authenticator(?CHAIN, ID, Config);
[_Config | _] ->
error(name_has_be_used)
{error, name_has_be_used}
end;
post_config_update({move, ID, Position}, _NewConfig, _OldConfig) ->
post_config_update({move_authenticator, ID, Position}, _NewConfig, _OldConfig) ->
NPosition = case Position of
<<"top">> -> top;
<<"bottom">> -> bottom;
@ -191,16 +185,13 @@ post_config_update({move, ID, Position}, _NewConfig, _OldConfig) ->
[<<"before">>, ID0] ->
{before, ID0};
_ ->
error({invalid_parameter, position})
{error, {invalid_parameter, position}}
end
end,
case move_authenticator(?CHAIN, ID, NPosition) of
ok -> ok;
{error, Reason} -> throw(Reason)
end.
move_authenticator(?CHAIN, ID, NPosition).
update_config(Path, ConfigRequest) ->
emqx_config:update(emqx_authn_schema, Path, ConfigRequest).
emqx:update_config(Path, ConfigRequest, #{rawconf_with_defaults => true}).
enable() ->
case emqx:hook('client.authenticate', {?MODULE, authenticate, []}) of
@ -522,7 +513,6 @@ do_create_authenticator(ChainID, AuthenticatorID, #{name := Name} = Config) ->
Authenticator = #authenticator{id = AuthenticatorID,
name = Name,
provider = Provider,
config = Config,
state = switch_version(State)},
{ok, Authenticator};
{error, Reason} ->
@ -570,7 +560,6 @@ update_or_create_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Co
case Provider:update(Config#{'_unique' => Unique}, State) of
{ok, NewState} ->
NewAuthenticator = Authenticator#authenticator{name = NewName,
config = Config,
state = switch_version(NewState)},
NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
true = ets:insert(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}),
@ -584,7 +573,6 @@ update_or_create_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Co
{ok, NewState} ->
NewAuthenticator = Authenticator#authenticator{name = NewName,
provider = NewProvider,
config = Config,
state = switch_version(NewState)},
NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
true = ets:insert(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}),
@ -660,5 +648,7 @@ serialize_authenticators(Authenticators) ->
[serialize_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators].
serialize_authenticator(#authenticator{id = ID,
config = Config}) ->
Config#{id => ID}.
name = Name,
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_pgsql">>)
, minirest:ref(<<"password_based_mongodb">>)
, minirest:ref(<<"password_based_redis">>)
, minirest:ref(<<"password_based_http_server">>)
]
}
@ -1292,7 +1293,7 @@ authentication(post, Request) ->
{ok, Body, _} = cowboy_req:read_body(Request),
case emqx_json:decode(Body, [return_maps]) of
#{<<"enable">> := Enable} ->
emqx_authn:update_config([authentication, enable], {enable, Enable}),
{ok, _} = emqx_authn:update_config([authentication, enable], {enable, Enable}),
{204};
_ ->
serialize_error({missing_parameter, enable})
@ -1305,20 +1306,28 @@ authenticators(post, Request) ->
{ok, Body, _} = cowboy_req:read_body(Request),
Config = emqx_json:decode(Body, [return_maps]),
case emqx_authn:update_config([authentication, authenticators], {create_authenticator, Config}) of
ok ->
{204};
{error, Reason} ->
{ok, #{post_config_update := #{emqx_authn := #{id := ID, name := Name}},
raw_config := RawConfig}} ->
[RawConfig1] = [RC || #{<<"name">> := N} = RC <- RawConfig, N =:= Name],
{200, RawConfig1#{id => ID}};
{error, {_, _, Reason}} ->
serialize_error(Reason)
end;
authenticators(get, _Request) ->
RawConfig = get_raw_config([authentication, authenticators]),
{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) ->
AuthenticatorID = cowboy_req:binding(id, Request),
case emqx_authn:lookup_authenticator(?CHAIN, AuthenticatorID) of
{ok, Authenticator} ->
{200, Authenticator};
{ok, #{id := ID, name := Name}} ->
RawConfig = get_raw_config([authentication, authenticators]),
[RawConfig1] = [RC || #{<<"name">> := N} = RC <- RawConfig, N =:= Name],
{200, RawConfig1#{id => ID}};
{error, Reason} ->
serialize_error(Reason)
end;
@ -1328,17 +1337,19 @@ authenticators2(put, Request) ->
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} ->
{ok, #{post_config_update := #{emqx_authn := #{id := ID, name := Name}},
raw_config := RawConfig}} ->
[RawConfig0] = [RC || #{<<"name">> := N} = RC <- RawConfig, N =:= Name],
{200, RawConfig0#{id => ID}};
{error, {_, _, Reason}} ->
serialize_error(Reason)
end;
authenticators2(delete, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
case emqx_authn:update_config([authentication, authenticators], {delete_authenticator, AuthenticatorID}) of
ok ->
{ok, _} ->
{204};
{error, Reason} ->
{error, {_, _, Reason}} ->
serialize_error(Reason)
end.
@ -1348,8 +1359,8 @@ move(post, Request) ->
case emqx_json:decode(Body, [return_maps]) of
#{<<"position">> := Position} ->
case emqx_authn:update_config([authentication, authenticators], {move_authenticator, AuthenticatorID, Position}) of
ok -> {204};
{error, Reason} -> serialize_error(Reason)
{ok, _} -> {204};
{error, {_, _, Reason}} -> serialize_error(Reason)
end;
_ ->
serialize_error({missing_parameter, position})
@ -1361,10 +1372,8 @@ import_users(post, Request) ->
case emqx_json:decode(Body, [return_maps]) of
#{<<"filename">> := Filename} ->
case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of
ok ->
{204};
{error, Reason} ->
serialize_error(Reason)
ok -> {204};
{error, Reason} -> serialize_error(Reason)
end;
_ ->
serialize_error({missing_parameter, filename})
@ -1435,6 +1444,11 @@ users2(delete, Request) ->
serialize_error(Reason)
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}}) ->
{404, #{code => <<"NOT_FOUND">>,
message => list_to_binary(io_lib:format("Authenticator '~s' does not exist", [ID]))}};

View File

@ -71,7 +71,7 @@ t_authenticator(_) ->
secret => <<"abcdef">>,
secret_base64_encoded => false,
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">>,
?assertEqual({error, {not_found, {authenticator, ID2}}}, ?AUTH:update_authenticator(?CHAIN, ID2, AuthenticatorConfig2)),
@ -79,9 +79,9 @@ t_authenticator(_) ->
AuthenticatorName2 = <<"myauthenticator2">>,
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)),
{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, [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}, ?AUTH:list_authenticators(?CHAIN)),