feat(authn): improve apis of moving authenticators

This commit is contained in:
zhouzb 2021-08-12 13:34:45 +08:00
parent 60f0e8e5a5
commit b7bc8b8cac
5 changed files with 136 additions and 64 deletions

View File

@ -49,7 +49,7 @@
, update_or_create_authenticator/3 , update_or_create_authenticator/3
, lookup_authenticator/2 , lookup_authenticator/2
, list_authenticators/1 , list_authenticators/1
, move_authenticator_to_the_nth/3 , move_authenticator/3
]). ]).
-export([ import_users/3 -export([ import_users/3
@ -108,6 +108,30 @@ pre_config_update({update_or_create_authenticator, ID, Config}, OldConfig) ->
false -> C false -> C
end end
end, OldConfig) end, OldConfig)
end;
pre_config_update({move, ID, Position}, OldConfig) ->
case lookup_authenticator(?CHAIN, ID) of
{error, Reason} -> error(Reason);
{ok, #{name := Name}} ->
{ok, Found, Part1, Part2} = split_by_name(Name, OldConfig),
case Position of
<<"top">> ->
[Found | Part1] ++ Part2;
<<"bottom">> ->
Part1 ++ Part2 ++ [Found];
Before ->
case binary:split(Before, <<":">>, [global]) of
[<<"before">>, ID0] ->
case lookup_authenticator(?CHAIN, ID0) of
{error, Reason} -> error(Reason);
{ok, #{name := Name1}} ->
{ok, NFound, NPart1, NPart2} = split_by_name(Name1, Part1 + Part2),
NPart1 ++ [Found, NFound | NPart2]
end;
_ ->
error({invalid_parameter, position})
end
end
end. end.
post_config_update({enable, true}, _NewConfig, _OldConfig) -> post_config_update({enable, true}, _NewConfig, _OldConfig) ->
@ -157,6 +181,22 @@ post_config_update({update_or_create_authenticator, ID, #{<<"name">> := Name}},
end; end;
[_Config | _] -> [_Config | _] ->
error(name_has_be_used) error(name_has_be_used)
end;
post_config_update({move, ID, Position}, _NewConfig, _OldConfig) ->
NPosition = case Position of
<<"top">> -> top;
<<"bottom">> -> bottom;
Before ->
case binary:split(Before, <<":">>, [global]) of
[<<"before">>, ID0] ->
{before, ID0};
_ ->
error({invalid_parameter, position})
end
end,
case move_authenticator(?CHAIN, ID, NPosition) of
ok -> ok;
{error, Reason} -> throw(Reason)
end. end.
update_config(Path, ConfigRequest) -> update_config(Path, ConfigRequest) ->
@ -255,8 +295,8 @@ list_authenticators(ChainID) ->
{ok, serialize_authenticators(Authenticators)} {ok, serialize_authenticators(Authenticators)}
end. end.
move_authenticator_to_the_nth(ChainID, AuthenticatorID, N) -> move_authenticator(ChainID, AuthenticatorID, Position) ->
gen_server:call(?MODULE, {move_authenticator, ChainID, AuthenticatorID, N}). gen_server:call(?MODULE, {move_authenticator, ChainID, AuthenticatorID, Position}).
import_users(ChainID, AuthenticatorID, Filename) -> import_users(ChainID, AuthenticatorID, Filename) ->
gen_server:call(?MODULE, {import_users, ChainID, AuthenticatorID, Filename}). gen_server:call(?MODULE, {import_users, ChainID, AuthenticatorID, Filename}).
@ -364,10 +404,10 @@ handle_call({update_or_create_authenticator, ChainID, AuthenticatorID, Config},
Reply = update_or_create_authenticator(ChainID, AuthenticatorID, Config, true), Reply = update_or_create_authenticator(ChainID, AuthenticatorID, Config, true),
reply(Reply, State); reply(Reply, State);
handle_call({move_authenticator, ChainID, AuthenticatorID, N}, _From, State) -> handle_call({move_authenticator, ChainID, AuthenticatorID, Position}, _From, State) ->
UpdateFun = UpdateFun =
fun(#chain{authenticators = Authenticators} = Chain) -> fun(#chain{authenticators = Authenticators} = Chain) ->
case move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N) of case do_move_authenticator(AuthenticatorID, Authenticators, Position) of
{ok, NAuthenticators} -> {ok, NAuthenticators} ->
true = ets:insert(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}), true = ets:insert(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}),
ok; ok;
@ -458,6 +498,21 @@ switch_version(State = #{version := ?VER_2}) ->
switch_version(State) -> switch_version(State) ->
State#{version => ?VER_1}. State#{version => ?VER_1}.
split_by_name(Name, Config) ->
{Part1, Part2, true} = lists:foldl(
fun(#{<<"name">> := N} = C, {P1, P2, F0}) ->
F = case N =:= Name of
true -> true;
false -> F0
end,
case F of
false -> {[C | P1], P2, F};
true -> {P1, [C | P2], F}
end
end, {[], [], false}, Config),
[Found | NPart2] = lists:reverse(Part2),
{ok, Found, lists:reverse(Part1), NPart2}.
do_create_authenticator(ChainID, AuthenticatorID, #{name := Name} = Config) -> do_create_authenticator(ChainID, AuthenticatorID, #{name := Name} = Config) ->
Provider = authenticator_provider(Config), Provider = authenticator_provider(Config),
Unique = <<ChainID/binary, "/", AuthenticatorID/binary, ":", ?VER_1/binary>>, Unique = <<ChainID/binary, "/", AuthenticatorID/binary, ":", ?VER_1/binary>>,
@ -546,23 +601,27 @@ update_or_create_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Co
replace_authenticator(ID, #authenticator{name = Name} = Authenticator, Authenticators) -> replace_authenticator(ID, #authenticator{name = Name} = Authenticator, Authenticators) ->
lists:keyreplace(ID, 1, Authenticators, {ID, Name, Authenticator}). lists:keyreplace(ID, 1, Authenticators, {ID, Name, Authenticator}).
move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N) do_move_authenticator(AuthenticatorID, Authenticators, Position) when is_binary(AuthenticatorID) ->
when N =< length(Authenticators) andalso N > 0 -> case lists:keytake(AuthenticatorID, 1, Authenticators) of
move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N, []); false ->
move_authenticator_to_the_nth_(_, _, _) ->
{error, out_of_range}.
move_authenticator_to_the_nth_(AuthenticatorID, [], _, _) ->
{error, {not_found, {authenticator, AuthenticatorID}}}; {error, {not_found, {authenticator, AuthenticatorID}}};
move_authenticator_to_the_nth_(AuthenticatorID, [{AuthenticatorID, _, _} = Authenticator | More], N, Passed) {value, Authenticator, NAuthenticators} ->
when N =< length(Passed) -> do_move_authenticator(Authenticator, NAuthenticators, Position)
{L1, L2} = lists:split(N - 1, lists:reverse(Passed)), end;
{ok, L1 ++ [Authenticator] ++ L2 ++ More};
move_authenticator_to_the_nth_(AuthenticatorID, [{AuthenticatorID, _, _} = Authenticator | More], N, Passed) -> do_move_authenticator(Authenticator, Authenticators, top) ->
{L1, L2} = lists:split(N - length(Passed) - 1, More), {ok, [Authenticator | Authenticators]};
{ok, lists:reverse(Passed) ++ L1 ++ [Authenticator] ++ L2}; do_move_authenticator(Authenticator, Authenticators, bottom) ->
move_authenticator_to_the_nth_(AuthenticatorID, [Authenticator | More], N, Passed) -> {ok, Authenticators ++ [Authenticator]};
move_authenticator_to_the_nth_(AuthenticatorID, More, N, [Authenticator | Passed]). do_move_authenticator(Authenticator, Authenticators, {before, ID}) ->
insert(Authenticator, Authenticators, ID, []).
insert(_, [], ID, _) ->
{error, {not_found, {authenticator, ID}}};
insert(Authenticator, [{ID, _, _} | _] = Authenticators, ID, Acc) ->
{ok, lists:reverse(Acc) ++ [Authenticator | Authenticators]};
insert(Authenticator, [{_, _, _} = Authenticator0 | More], ID, Acc) ->
insert(Authenticator, More, ID, [Authenticator0 | Acc]).
update_chain(ChainID, UpdateFun) -> update_chain(ChainID, UpdateFun) ->
case ets:lookup(?CHAIN_TAB, ChainID) of case ets:lookup(?CHAIN_TAB, ChainID) of

View File

@ -24,7 +24,7 @@
, authentication/2 , authentication/2
, authenticators/2 , authenticators/2
, authenticators2/2 , authenticators2/2
, position/2 , move/2
, import_users/2 , import_users/2
, users/2 , users/2
, users2/2 , users2/2
@ -109,7 +109,7 @@ api_spec() ->
{[ authentication_api() {[ authentication_api()
, authenticators_api() , authenticators_api()
, authenticators_api2() , authenticators_api2()
, position_api() , move_api()
, import_users_api() , import_users_api()
, users_api() , users_api()
, users2_api() , users2_api()
@ -405,10 +405,10 @@ authenticators_api2() ->
}, },
{"/authentication/authenticators/:id", Metadata, authenticators2}. {"/authentication/authenticators/:id", Metadata, authenticators2}.
position_api() -> move_api() ->
Metadata = #{ Metadata = #{
post => #{ post => #{
description => "Change the order of authenticators", description => "Move authenticator",
parameters => [ parameters => [
#{ #{
name => id, name => id,
@ -423,14 +423,30 @@ position_api() ->
content => #{ content => #{
'application/json' => #{ 'application/json' => #{
schema => #{ schema => #{
oneOf => [
#{
type => object, type => object,
required => [position], required => [position],
properties => #{ properties => #{
position => #{ position => #{
type => integer, type => string,
example => 1 enum => [<<"top">>, <<"bottom">>],
example => <<"top">>
} }
} }
},
#{
type => object,
required => [position],
properties => #{
position => #{
type => string,
description => <<"before:<authenticator_id>">>,
example => <<"before:67e4c9d3">>
}
}
}
]
} }
} }
} }
@ -444,7 +460,7 @@ position_api() ->
} }
} }
}, },
{"/authentication/authenticators/:id/position", Metadata, position}. {"/authentication/authenticators/:id/move", Metadata, move}.
import_users_api() -> import_users_api() ->
Metadata = #{ Metadata = #{
@ -1304,18 +1320,17 @@ authenticators2(delete, Request) ->
serialize_error(Reason) serialize_error(Reason)
end. end.
position(post, Request) -> move(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]), case emqx_json:decode(Body, [return_maps]) of
Config = hocon_schema:check_plain(emqx_authn_implied_schema, #{<<"position">> => NBody}, #{<<"position">> := Position} ->
#{nullable => true}, ["position"]), case emqx_authn:update_config([authentication, authenticators], {move_authenticator, AuthenticatorID, Position}) of
#{position := #{position := Position}} = emqx_map_lib:unsafe_atom_key_map(Config), ok -> {204};
case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of {error, Reason} -> serialize_error(Reason)
ok -> end;
{204}; _ ->
{error, Reason} -> serialize_error({missing_parameter, position})
serialize_error(Reason)
end. end.
import_users(post, Request) -> import_users(post, Request) ->
@ -1393,9 +1408,6 @@ serialize_error({not_found, {authenticator, ID}}) ->
serialize_error(name_has_be_used) -> serialize_error(name_has_be_used) ->
{409, #{code => <<"ALREADY_EXISTS">>, {409, #{code => <<"ALREADY_EXISTS">>,
message => <<"Name has be used">>}}; message => <<"Name has be used">>}};
serialize_error(out_of_range) ->
{400, #{code => <<"OUT_OF_RANGE">>,
message => <<"Out of range">>}};
serialize_error({missing_parameter, Name}) -> serialize_error({missing_parameter, Name}) ->
{400, #{code => <<"MISSING_PARAMETER">>, {400, #{code => <<"MISSING_PARAMETER">>,
message => list_to_binary( message => list_to_binary(

View File

@ -25,12 +25,10 @@
, fields/1 , fields/1
]). ]).
structs() -> [ "filename", "position", "user_info", "new_user_info"]. structs() -> [ "filename", "user_info", "new_user_info"].
fields("filename") -> fields("filename") ->
[ {filename, fun filename/1} ]; [ {filename, fun filename/1} ];
fields("position") ->
[ {position, fun position/1} ];
fields("user_info") -> fields("user_info") ->
[ {user_id, fun user_id/1} [ {user_id, fun user_id/1}
, {password, fun password/1} , {password, fun password/1}
@ -43,11 +41,6 @@ filename(type) -> string();
filename(nullable) -> false; filename(nullable) -> false;
filename(_) -> undefined. filename(_) -> undefined.
position(type) -> integer();
position(validate) -> [fun (Position) -> Position > 0 end];
position(nullable) -> false;
position(_) -> undefined.
user_id(type) -> binary(); user_id(type) -> binary();
user_id(nullable) -> false; user_id(nullable) -> false;
user_id(_) -> undefined. user_id(_) -> undefined.

View File

@ -86,10 +86,18 @@ t_authenticator(_) ->
?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)),
?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)), ?assertEqual(ok, ?AUTH:move_authenticator(?CHAIN, ID2, top)),
?assertMatch({ok, [#{name := AuthenticatorName2}, #{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)), ?assertMatch({ok, [#{name := AuthenticatorName2}, #{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 3)),
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 0)), ?assertEqual(ok, ?AUTH:move_authenticator(?CHAIN, ID2, bottom)),
?assertMatch({ok, [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual(ok, ?AUTH:move_authenticator(?CHAIN, ID2, {before, ID1})),
?assertMatch({ok, [#{name := AuthenticatorName2}, #{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual({error, {not_found, {authenticator, <<"nonexistent">>}}}, ?AUTH:move_authenticator(?CHAIN, ID2, {before, <<"nonexistent">>})),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)), ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)), ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)),
?assertEqual({ok, []}, ?AUTH:list_authenticators(?CHAIN)), ?assertEqual({ok, []}, ?AUTH:list_authenticators(?CHAIN)),

View File

@ -144,7 +144,7 @@ t_multi_mnesia_authenticator(_) ->
clientid => <<"myclient">>, clientid => <<"myclient">>,
password => <<"mypass1">>}, password => <<"mypass1">>},
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo1, ok)), ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo1, ok)),
?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)), ?assertEqual(ok, ?AUTH:move_authenticator(?CHAIN, ID2, top)),
?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo1, ok)), ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo1, ok)),
ClientInfo2 = ClientInfo1#{password => <<"mypass2">>}, ClientInfo2 = ClientInfo1#{password => <<"mypass2">>},