feat(authn): improve apis of moving authenticators
This commit is contained in:
parent
60f0e8e5a5
commit
b7bc8b8cac
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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.
|
|
@ -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)),
|
||||||
|
|
|
@ -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">>},
|
||||||
|
|
Loading…
Reference in New Issue