Merge pull request #10085 from sstrigler/EMQX-8552-authorization-sources-type-status-move-shouldnt-exist-when-authorization-sources-type-doesnt-exist

emqx_authz API: return 404 for all requests on non existent source
This commit is contained in:
Stefan Strigler 2023-03-07 16:19:28 +01:00 committed by GitHub
commit 609cd01a35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 193 additions and 115 deletions

View File

@ -47,7 +47,7 @@
-export([ -export([
sources/2, sources/2,
source/2, source/2,
move_source/2, source_move/2,
aggregate_metrics/1 aggregate_metrics/1
]). ]).
@ -164,7 +164,7 @@ schema("/authorization/sources/:type/status") ->
}; };
schema("/authorization/sources/:type/move") -> schema("/authorization/sources/:type/move") ->
#{ #{
'operationId' => move_source, 'operationId' => source_move,
post => post =>
#{ #{
description => ?DESC(authorization_sources_type_move_post), description => ?DESC(authorization_sources_type_move_post),
@ -230,8 +230,6 @@ sources(get, _) ->
get_raw_sources() get_raw_sources()
), ),
{200, #{sources => Sources}}; {200, #{sources => Sources}};
sources(post, #{body := #{<<"type">> := <<"file">>} = Body}) ->
create_authz_file(Body);
sources(post, #{body := Body}) -> sources(post, #{body := Body}) ->
update_config(?CMD_PREPEND, Body). update_config(?CMD_PREPEND, Body).
@ -240,10 +238,10 @@ source(Method, #{bindings := #{type := Type} = Bindings} = Req) when
-> ->
source(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}}); source(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}});
source(get, #{bindings := #{type := Type}}) -> source(get, #{bindings := #{type := Type}}) ->
case get_raw_source(Type) of with_source(
[] -> Type,
{404, #{code => <<"NOT_FOUND">>, message => <<"Not found: ", Type/binary>>}}; fun
[#{<<"type">> := <<"file">>, <<"enable">> := Enable, <<"path">> := Path}] -> (#{<<"type">> := <<"file">>, <<"enable">> := Enable, <<"path">> := Path}) ->
case file:read_file(Path) of case file:read_file(Path) of
{ok, Rules} -> {ok, Rules} ->
{200, #{ {200, #{
@ -257,26 +255,46 @@ source(get, #{bindings := #{type := Type}}) ->
message => bin(Reason) message => bin(Reason)
}} }}
end; end;
[Source] -> (Source) ->
{200, Source} {200, Source}
end; end
source(put, #{bindings := #{type := <<"file">>}, body := #{<<"type">> := <<"file">>} = Body}) -> );
update_authz_file(Body);
source(put, #{bindings := #{type := Type}, body := #{<<"type">> := Type} = Body}) -> source(put, #{bindings := #{type := Type}, body := #{<<"type">> := Type} = Body}) ->
update_config({?CMD_REPLACE, Type}, Body); with_source(
source(put, #{bindings := #{type := _Type}, body := #{<<"type">> := _OtherType}}) -> Type,
{400, #{code => <<"BAD_REQUEST">>, message => <<"Type mismatch">>}}; fun(_) ->
update_config({?CMD_REPLACE, Type}, Body)
end
);
source(put, #{bindings := #{type := Type}, body := #{<<"type">> := _OtherType}}) ->
with_source(
Type,
fun(_) ->
{400, #{code => <<"BAD_REQUEST">>, message => <<"Type mismatch">>}}
end
);
source(delete, #{bindings := #{type := Type}}) -> source(delete, #{bindings := #{type := Type}}) ->
update_config({?CMD_DELETE, Type}, #{}). with_source(
Type,
fun(_) ->
update_config({?CMD_DELETE, Type}, #{})
end
).
source_status(get, #{bindings := #{type := Type}}) -> source_status(get, #{bindings := #{type := Type}}) ->
lookup_from_all_nodes(Type). with_source(
atom_to_binary(Type, utf8),
fun(_) -> lookup_from_all_nodes(Type) end
).
move_source(Method, #{bindings := #{type := Type} = Bindings} = Req) when source_move(Method, #{bindings := #{type := Type} = Bindings} = Req) when
is_atom(Type) is_atom(Type)
-> ->
move_source(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}}); source_move(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}});
move_source(post, #{bindings := #{type := Type}, body := #{<<"position">> := Position}}) -> source_move(post, #{bindings := #{type := Type}, body := #{<<"position">> := Position}}) ->
with_source(
Type,
fun(_Source) ->
case parse_position(Position) of case parse_position(Position) of
{ok, NPosition} -> {ok, NPosition} ->
try emqx_authz:move(Type, NPosition) of try emqx_authz:move(Type, NPosition) of
@ -310,7 +328,9 @@ move_source(post, #{bindings := #{type := Type}, body := #{<<"position">> := Pos
code => <<"BAD_REQUEST">>, code => <<"BAD_REQUEST">>,
message => bin(Reason) message => bin(Reason)
}} }}
end. end
end
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions %% Internal functions
@ -486,6 +506,15 @@ get_raw_source(Type) ->
get_raw_sources() get_raw_sources()
). ).
-spec with_source(binary(), fun((map()) -> term())) -> term().
with_source(Type, ContF) ->
case get_raw_source(Type) of
[] ->
{404, #{code => <<"NOT_FOUND">>, message => <<"Not found: ", Type/binary>>}};
[Source] ->
ContF(Source)
end.
update_config(Cmd, Sources) -> update_config(Cmd, Sources) ->
case emqx_authz:update(Cmd, Sources) of case emqx_authz:update(Cmd, Sources) of
{ok, _} -> {ok, _} ->
@ -630,13 +659,3 @@ status_metrics_example() ->
} }
} }
}. }.
create_authz_file(Body) ->
do_update_authz_file(?CMD_PREPEND, Body).
update_authz_file(Body) ->
do_update_authz_file({?CMD_REPLACE, <<"file">>}, Body).
do_update_authz_file(Cmd, Body) ->
%% API update will placed in `authz` subdirectory inside EMQX's `data_dir`
update_config(Cmd, Body).

View File

@ -21,7 +21,6 @@
-import(emqx_mgmt_api_test_util, [request/3, uri/1]). -import(emqx_mgmt_api_test_util, [request/3, uri/1]).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl").
-define(MONGO_SINGLE_HOST, "mongo"). -define(MONGO_SINGLE_HOST, "mongo").
@ -183,7 +182,7 @@ t_api(_) ->
{ok, 404, ErrResult} = request(get, uri(["authorization", "sources", "http"]), []), {ok, 404, ErrResult} = request(get, uri(["authorization", "sources", "http"]), []),
?assertMatch( ?assertMatch(
#{<<"code">> := <<"NOT_FOUND">>, <<"message">> := <<"Not found: http">>}, #{<<"code">> := <<"NOT_FOUND">>, <<"message">> := <<"Not found: http">>},
jsx:decode(ErrResult) emqx_json:decode(ErrResult, [return_maps])
), ),
[ [
@ -215,7 +214,9 @@ t_api(_) ->
?SOURCE1#{<<"enable">> := false} ?SOURCE1#{<<"enable">> := false}
), ),
{ok, 200, Result3} = request(get, uri(["authorization", "sources", "http"]), []), {ok, 200, Result3} = request(get, uri(["authorization", "sources", "http"]), []),
?assertMatch(#{<<"type">> := <<"http">>, <<"enable">> := false}, jsx:decode(Result3)), ?assertMatch(
#{<<"type">> := <<"http">>, <<"enable">> := false}, emqx_json:decode(Result3, [return_maps])
),
Keyfile = emqx_common_test_helpers:app_path( Keyfile = emqx_common_test_helpers:app_path(
emqx, emqx,
@ -252,7 +253,7 @@ t_api(_) ->
<<"total">> := 0, <<"total">> := 0,
<<"nomatch">> := 0 <<"nomatch">> := 0
} }
} = jiffy:decode(Status4, [return_maps]), } = emqx_json:decode(Status4, [return_maps]),
?assertMatch( ?assertMatch(
#{ #{
<<"type">> := <<"mongodb">>, <<"type">> := <<"mongodb">>,
@ -264,7 +265,7 @@ t_api(_) ->
<<"verify">> := <<"verify_none">> <<"verify">> := <<"verify_none">>
} }
}, },
jsx:decode(Result4) emqx_json:decode(Result4, [return_maps])
), ),
{ok, Cacert} = file:read_file(Cacertfile), {ok, Cacert} = file:read_file(Cacertfile),
@ -296,7 +297,7 @@ t_api(_) ->
<<"verify">> := <<"verify_none">> <<"verify">> := <<"verify_none">>
} }
}, },
jsx:decode(Result5) emqx_json:decode(Result5, [return_maps])
), ),
{ok, 200, Status5_1} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []), {ok, 200, Status5_1} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []),
@ -307,7 +308,7 @@ t_api(_) ->
<<"total">> := 0, <<"total">> := 0,
<<"nomatch">> := 0 <<"nomatch">> := 0
} }
} = jiffy:decode(Status5_1, [return_maps]), } = emqx_json:decode(Status5_1, [return_maps]),
#{ #{
ssl := #{ ssl := #{
@ -354,7 +355,7 @@ t_api(_) ->
<<"code">> := <<"BAD_REQUEST">>, <<"code">> := <<"BAD_REQUEST">>,
<<"message">> := <<"Type mismatch", _/binary>> <<"message">> := <<"Type mismatch", _/binary>>
}, },
jiffy:decode(TypeMismatch, [return_maps]) emqx_json:decode(TypeMismatch, [return_maps])
), ),
lists:foreach( lists:foreach(
@ -371,6 +372,43 @@ t_api(_) ->
?assertEqual([], get_sources(Result6)), ?assertEqual([], get_sources(Result6)),
?assertEqual([], emqx:get_config([authorization, sources])), ?assertEqual([], emqx:get_config([authorization, sources])),
lists:foreach(
fun(#{<<"type">> := Type}) ->
{ok, 404, _} = request(
get,
uri(["authorization", "sources", binary_to_list(Type), "status"]),
[]
),
{ok, 404, _} = request(
post,
uri(["authorization", "sources", binary_to_list(Type), "move"]),
#{<<"position">> => <<"front">>}
),
{ok, 404, _} = request(
get,
uri(["authorization", "sources", binary_to_list(Type)]),
[]
),
{ok, 404, _} = request(
delete,
uri(["authorization", "sources", binary_to_list(Type)]),
[]
)
end,
Sources
),
{ok, 404, _TypeMismatch2} = request(
put,
uri(["authorization", "sources", "file"]),
#{<<"type">> => <<"built_in_database">>, <<"enable">> => false}
),
{ok, 404, _} = request(
put,
uri(["authorization", "sources", "built_in_database"]),
#{<<"type">> => <<"built_in_database">>, <<"enable">> => false}
),
{ok, 204, _} = request(post, uri(["authorization", "sources"]), ?SOURCE6), {ok, 204, _} = request(post, uri(["authorization", "sources"]), ?SOURCE6),
{ok, Client} = emqtt:start_link( {ok, Client} = emqtt:start_link(
@ -382,7 +420,6 @@ t_api(_) ->
] ]
), ),
emqtt:connect(Client), emqtt:connect(Client),
timer:sleep(50),
emqtt:publish( emqtt:publish(
Client, Client,
@ -392,7 +429,13 @@ t_api(_) ->
[{qos, 1}] [{qos, 1}]
), ),
{ok, 200, Status5} = request(get, uri(["authorization", "sources", "file", "status"]), []), snabbkaffe:retry(
10,
3,
fun() ->
{ok, 200, Status5} = request(
get, uri(["authorization", "sources", "file", "status"]), []
),
#{ #{
<<"metrics">> := #{ <<"metrics">> := #{
<<"allow">> := 1, <<"allow">> := 1,
@ -400,9 +443,10 @@ t_api(_) ->
<<"total">> := 1, <<"total">> := 1,
<<"nomatch">> := 0 <<"nomatch">> := 0
} }
} = jiffy:decode(Status5, [return_maps]), } = emqx_json:decode(Status5, [return_maps])
end
),
timer:sleep(50),
emqtt:publish( emqtt:publish(
Client, Client,
<<"t2">>, <<"t2">>,
@ -411,7 +455,13 @@ t_api(_) ->
[{qos, 1}] [{qos, 1}]
), ),
{ok, 200, Status6} = request(get, uri(["authorization", "sources", "file", "status"]), []), snabbkaffe:retry(
10,
3,
fun() ->
{ok, 200, Status6} = request(
get, uri(["authorization", "sources", "file", "status"]), []
),
#{ #{
<<"metrics">> := #{ <<"metrics">> := #{
<<"allow">> := 2, <<"allow">> := 2,
@ -419,9 +469,10 @@ t_api(_) ->
<<"total">> := 2, <<"total">> := 2,
<<"nomatch">> := 0 <<"nomatch">> := 0
} }
} = jiffy:decode(Status6, [return_maps]), } = emqx_json:decode(Status6, [return_maps])
end
),
timer:sleep(50),
emqtt:publish( emqtt:publish(
Client, Client,
<<"t3">>, <<"t3">>,
@ -430,8 +481,13 @@ t_api(_) ->
[{qos, 1}] [{qos, 1}]
), ),
timer:sleep(50), snabbkaffe:retry(
{ok, 200, Status7} = request(get, uri(["authorization", "sources", "file", "status"]), []), 10,
3,
fun() ->
{ok, 200, Status7} = request(
get, uri(["authorization", "sources", "file", "status"]), []
),
#{ #{
<<"metrics">> := #{ <<"metrics">> := #{
<<"allow">> := 3, <<"allow">> := 3,
@ -439,11 +495,12 @@ t_api(_) ->
<<"total">> := 3, <<"total">> := 3,
<<"nomatch">> := 0 <<"nomatch">> := 0
} }
} = jiffy:decode(Status7, [return_maps]), } = emqx_json:decode(Status7, [return_maps])
end
),
ok. ok.
t_move_source(_) -> t_source_move(_) ->
{ok, _} = emqx_authz:update(replace, [?SOURCE1, ?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5]), {ok, _} = emqx_authz:update(replace, [?SOURCE1, ?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5]),
?assertMatch( ?assertMatch(
[ [
@ -564,7 +621,7 @@ t_aggregate_metrics(_) ->
). ).
get_sources(Result) -> get_sources(Result) ->
maps:get(<<"sources">>, jsx:decode(Result), []). maps:get(<<"sources">>, emqx_json:decode(Result, [return_maps])).
data_dir() -> emqx:data_dir(). data_dir() -> emqx:data_dir().

View File

@ -0,0 +1 @@
Consistently return `404` for all requests on non existent source in `/authorization/sources/:source[/*]`.

View File

@ -0,0 +1 @@
如果向 `/authorization/sources/:source[/*]` 请求的 `source` 不存在,将一致地返回 `404`