From 7272ef25d48a35b9fb7c88bb94a5a1c3944fadeb Mon Sep 17 00:00:00 2001 From: Serge Tupchii Date: Tue, 13 Feb 2024 22:07:33 +0200 Subject: [PATCH] feat(emqx_auth): implement API to re-order all authenticators/authz sources Fixes: EMQX-11770 --- apps/emqx_auth/include/emqx_authz.hrl | 1 + .../src/emqx_authn/emqx_authn_api.erl | 66 ++++++++- .../src/emqx_authn/emqx_authn_config.erl | 29 ++++ apps/emqx_auth/src/emqx_authz/emqx_authz.erl | 41 ++++++ .../src/emqx_authz/emqx_authz_api_sources.erl | 59 +++++++- .../test/emqx_authn/emqx_authn_api_SUITE.erl | 105 ++++++++++++++ .../emqx_authz_api_sources_SUITE.erl | 131 +++++++++++++++++- changes/ce/feat-12509.en.md | 1 + rel/i18n/emqx_authn_api.hocon | 5 + rel/i18n/emqx_authz_api_sources.hocon | 5 + 10 files changed, 429 insertions(+), 14 deletions(-) create mode 100644 changes/ce/feat-12509.en.md diff --git a/apps/emqx_auth/include/emqx_authz.hrl b/apps/emqx_auth/include/emqx_authz.hrl index 9af795a82..4d3fc75dc 100644 --- a/apps/emqx_auth/include/emqx_authz.hrl +++ b/apps/emqx_auth/include/emqx_authz.hrl @@ -28,6 +28,7 @@ -define(CMD_APPEND, append). -define(CMD_MOVE, move). -define(CMD_MERGE, merge). +-define(CMD_REORDER, reorder). -define(CMD_MOVE_FRONT, front). -define(CMD_MOVE_REAR, rear). diff --git a/apps/emqx_auth/src/emqx_authn/emqx_authn_api.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_api.erl index 1b299fa64..7f0413fbb 100644 --- a/apps/emqx_auth/src/emqx_authn/emqx_authn_api.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_api.erl @@ -32,6 +32,8 @@ -define(INTERNAL_ERROR, 'INTERNAL_ERROR'). -define(CONFIG, emqx_authn_config). +-define(join(List), lists:join(", ", List)). + % Swagger -define(API_TAGS_GLOBAL, [<<"Authentication">>]). @@ -56,6 +58,7 @@ listener_authenticator/2, listener_authenticator_status/2, authenticator_position/2, + authenticators_order/2, listener_authenticator_position/2, authenticator_users/2, authenticator_user/2, @@ -102,7 +105,8 @@ paths() -> "/authentication/:id/status", "/authentication/:id/position/:position", "/authentication/:id/users", - "/authentication/:id/users/:user_id" + "/authentication/:id/users/:user_id", + "/authentication/order" %% hide listener authn api since 5.1.0 %% "/listeners/:listener_id/authentication", @@ -118,7 +122,8 @@ roots() -> request_user_create, request_user_update, response_user, - response_users + response_users, + request_authn_order ]. fields(request_user_create) -> @@ -137,7 +142,16 @@ fields(response_user) -> {is_superuser, mk(boolean(), #{default => false, required => false})} ]; fields(response_users) -> - paginated_list_type(ref(response_user)). + paginated_list_type(ref(response_user)); +fields(request_authn_order) -> + [ + {id, + mk(binary(), #{ + desc => ?DESC(param_auth_id), + required => true, + example => "password_based:built_in_database" + })} + ]. schema("/authentication") -> #{ @@ -218,7 +232,7 @@ schema("/authentication/:id/status") -> parameters => [param_auth_id()], responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( - hoconsc:ref(emqx_authn_schema, "metrics_status_fields"), + ref(emqx_authn_schema, "metrics_status_fields"), status_metrics_example() ), 404 => error_codes([?NOT_FOUND], <<"Not Found">>), @@ -313,7 +327,7 @@ schema("/listeners/:listener_id/authentication/:id/status") -> parameters => [param_listener_id(), param_auth_id()], responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( - hoconsc:ref(emqx_authn_schema, "metrics_status_fields"), + ref(emqx_authn_schema, "metrics_status_fields"), status_metrics_example() ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) @@ -530,6 +544,22 @@ schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } } + }; +schema("/authentication/order") -> + #{ + 'operationId' => authenticators_order, + put => #{ + tags => ?API_TAGS_GLOBAL, + description => ?DESC(authentication_order_put), + 'requestBody' => mk( + hoconsc:array(ref(?MODULE, request_authn_order)), + #{} + ), + responses => #{ + 204 => <<"Authenticators order updated">>, + 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + } + } }. param_auth_id() -> @@ -670,6 +700,17 @@ listener_authenticator_status( end ). +authenticators_order(put, #{body := AuthnOrder}) -> + AuthnIdsOrder = [Id || #{<<"id">> := Id} <- AuthnOrder], + case update_config([authentication], {reorder_authenticators, AuthnIdsOrder}) of + {ok, _} -> + {204}; + {error, {_PrePostConfigUpdate, ?CONFIG, Reason}} -> + serialize_error(Reason); + {error, Reason} -> + serialize_error(Reason) + end. + authenticator_position( put, #{bindings := #{id := AuthenticatorID, position := Position}} @@ -1253,6 +1294,21 @@ serialize_error({unknown_authn_type, Type}) -> code => <<"BAD_REQUEST">>, message => binfmt("Unknown type '~p'", [Type]) }}; +serialize_error(#{not_found := NotFound, not_reordered := NotReordered}) -> + NotFoundFmt = "Authenticators: ~ts are not found", + NotReorderedFmt = "No positions are specified for authenticators: ~ts", + Msg = + case {NotFound, NotReordered} of + {[_ | _], []} -> + binfmt(NotFoundFmt, [?join(NotFound)]); + {[], [_ | _]} -> + binfmt(NotReorderedFmt, [?join(NotReordered)]); + _ -> + binfmt(NotFoundFmt ++ ", " ++ NotReorderedFmt, [ + ?join(NotFound), ?join(NotReordered) + ]) + end, + {400, #{code => <<"BAD_REQUEST">>, message => Msg}}; serialize_error(Reason) -> {400, #{ code => <<"BAD_REQUEST">>, diff --git a/apps/emqx_auth/src/emqx_authn/emqx_authn_config.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_config.erl index 70f0f31a4..cb55428b2 100644 --- a/apps/emqx_auth/src/emqx_authn/emqx_authn_config.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_config.erl @@ -135,6 +135,8 @@ do_pre_config_update(_, {move_authenticator, _ChainName, AuthenticatorID, Positi do_pre_config_update(ConfPath, {merge_authenticators, NewConfig}, OldConfig) -> MergeConfig = merge_authenticators(OldConfig, NewConfig), do_pre_config_update(ConfPath, MergeConfig, OldConfig); +do_pre_config_update(_ConfPath, {reorder_authenticators, NewOrder}, OldConfig) -> + reorder_authenticators(NewOrder, OldConfig); do_pre_config_update(_, OldConfig, OldConfig) -> {ok, OldConfig}; do_pre_config_update(ConfPath, NewConfig, _OldConfig) -> @@ -194,6 +196,15 @@ do_post_config_update( _AppEnvs ) -> emqx_authn_chains:move_authenticator(ChainName, AuthenticatorID, Position); +do_post_config_update( + ConfPath, + {reorder_authenticators, NewOrder}, + _NewConfig, + _OldConfig, + _AppEnvs +) -> + ChainName = chain_name(ConfPath), + ok = emqx_authn_chains:reorder_authenticator(ChainName, NewOrder); do_post_config_update(_, _UpdateReq, OldConfig, OldConfig, _AppEnvs) -> ok; do_post_config_update(ConfPath, _UpdateReq, NewConfig0, OldConfig0, _AppEnvs) -> @@ -389,6 +400,24 @@ merge_authenticators(OriginConf0, NewConf0) -> ), lists:reverse(OriginConf1) ++ NewConf1. +reorder_authenticators(NewOrder, OldConfig) -> + OldConfigWithIds = [{authenticator_id(Auth), Auth} || Auth <- OldConfig], + reorder_authenticators(NewOrder, OldConfigWithIds, [], []). + +reorder_authenticators([], [] = _RemConfigWithIds, ReorderedConfig, [] = _NotFoundIds) -> + {ok, lists:reverse(ReorderedConfig)}; +reorder_authenticators([], RemConfigWithIds, _ReorderedConfig, NotFoundIds) -> + {error, #{not_found => NotFoundIds, not_reordered => [Id || {Id, _} <- RemConfigWithIds]}}; +reorder_authenticators([Id | RemOrder], RemConfigWithIds, ReorderedConfig, NotFoundIds) -> + case lists:keytake(Id, 1, RemConfigWithIds) of + {value, {_Id, Auth}, RemConfigWithIds1} -> + reorder_authenticators( + RemOrder, RemConfigWithIds1, [Auth | ReorderedConfig], NotFoundIds + ); + false -> + reorder_authenticators(RemOrder, RemConfigWithIds, ReorderedConfig, [Id | NotFoundIds]) + end. + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -compile(nowarn_export_all). diff --git a/apps/emqx_auth/src/emqx_authz/emqx_authz.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz.erl index 5bc5e88df..d1253b516 100644 --- a/apps/emqx_auth/src/emqx_authz/emqx_authz.erl +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz.erl @@ -36,6 +36,7 @@ lookup/0, lookup/1, move/2, + reorder/1, update/2, merge/1, merge_local/2, @@ -64,6 +65,8 @@ maybe_write_files/1 ]). +-import(emqx_utils_conv, [bin/1]). + -type default_result() :: allow | deny. -type authz_result_value() :: #{result := allow | deny, from => _}. @@ -181,6 +184,9 @@ move(Type, Position) -> ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), Position} ). +reorder(SourcesOrder) -> + emqx_authz_utils:update_config(?CONF_KEY_PATH, {?CMD_REORDER, SourcesOrder}). + update({?CMD_REPLACE, Type}, Sources) -> emqx_authz_utils:update_config(?CONF_KEY_PATH, {{?CMD_REPLACE, type(Type)}, Sources}); update({?CMD_DELETE, Type}, Sources) -> @@ -258,6 +264,8 @@ do_pre_config_update({?CMD_REPLACE, Sources}, _OldSources) -> NSources = lists:map(fun maybe_write_source_files/1, Sources), ok = check_dup_types(NSources), NSources; +do_pre_config_update({?CMD_REORDER, NewSourcesOrder}, OldSources) -> + reorder_sources(NewSourcesOrder, OldSources); do_pre_config_update({Op, Source}, Sources) -> throw({bad_request, #{op => Op, source => Source, sources => Sources}}). @@ -290,6 +298,16 @@ do_post_config_update(?CONF_KEY_PATH, {{?CMD_DELETE, Type}, _RawNewSource}, _Sou Front ++ Rear; do_post_config_update(?CONF_KEY_PATH, {?CMD_REPLACE, _RawNewSources}, Sources) -> overwrite_entire_sources(Sources); +do_post_config_update(?CONF_KEY_PATH, {?CMD_REORDER, NewSourcesOrder}, _Sources) -> + OldSources = lookup(), + lists:map( + fun(Type) -> + Type1 = type(Type), + {value, Val} = lists:search(fun(S) -> type(S) =:= Type1 end, OldSources), + Val + end, + NewSourcesOrder + ); do_post_config_update(?ROOT_KEY, Conf, Conf) -> #{sources := Sources} = Conf, Sources; @@ -729,6 +747,29 @@ type_take(Type, Sources) -> throw:{not_found_source, Type} -> not_found end. +reorder_sources(NewOrder, OldSources) -> + NewOrder1 = lists:map(fun type/1, NewOrder), + OldSourcesWithTypes = [{type(Source), Source} || Source <- OldSources], + reorder_sources(NewOrder1, OldSourcesWithTypes, [], []). + +reorder_sources([], [] = _RemSourcesWithTypes, ReorderedSources, [] = _NotFoundTypes) -> + lists:reverse(ReorderedSources); +reorder_sources([], RemSourcesWithTypes, _ReorderedSources, NotFoundTypes) -> + {error, #{ + not_found => NotFoundTypes, not_reordered => [bin(Type) || {Type, _} <- RemSourcesWithTypes] + }}; +reorder_sources([Type | RemOrder], RemSourcesWithTypes, ReorderedSources, NotFoundTypes) -> + case lists:keytake(Type, 1, RemSourcesWithTypes) of + {value, {_Type, Source}, RemSourcesWithTypes1} -> + reorder_sources( + RemOrder, RemSourcesWithTypes1, [Source | ReorderedSources], NotFoundTypes + ); + false -> + reorder_sources(RemOrder, RemSourcesWithTypes, ReorderedSources, [ + bin(Type) | NotFoundTypes + ]) + end. + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -compile(nowarn_export_all). diff --git a/apps/emqx_auth/src/emqx_authz/emqx_authz_api_sources.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_api_sources.erl index 00345a108..c2296f129 100644 --- a/apps/emqx_auth/src/emqx_authz/emqx_authz_api_sources.erl +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_api_sources.erl @@ -27,6 +27,8 @@ -define(BAD_REQUEST, 'BAD_REQUEST'). -define(NOT_FOUND, 'NOT_FOUND'). +-define(join(List), lists:join(", ", List)). + -export([ get_raw_sources/0, get_raw_source/1, @@ -46,6 +48,7 @@ sources/2, source/2, source_move/2, + sources_order/2, aggregate_metrics/1 ]). @@ -61,7 +64,8 @@ paths() -> "/authorization/sources", "/authorization/sources/:type", "/authorization/sources/:type/status", - "/authorization/sources/:type/move" + "/authorization/sources/:type/move", + "/authorization/sources/order" ]. fields(sources) -> @@ -77,6 +81,15 @@ fields(position) -> in => body } )} + ]; +fields(request_sources_order) -> + [ + {type, + mk(enum(emqx_authz_schema:source_types()), #{ + desc => ?DESC(source_type), + required => true, + example => "file" + })} ]. %%-------------------------------------------------------------------- @@ -196,6 +209,22 @@ schema("/authorization/sources/:type/move") -> 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) } } + }; +schema("/authorization/sources/order") -> + #{ + 'operationId' => sources_order, + put => #{ + tags => ?TAGS, + description => ?DESC(authorization_sources_order_put), + 'requestBody' => mk( + hoconsc:array(ref(?MODULE, request_sources_order)), + #{} + ), + responses => #{ + 204 => <<"Authorization sources order updated">>, + 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) + } + } }. %%-------------------------------------------------------------------- @@ -317,6 +346,30 @@ source_move(post, #{bindings := #{type := Type}, body := #{<<"position">> := Pos end ). +sources_order(put, #{body := AuthzOrder}) -> + SourcesOrder = [Type || #{<<"type">> := Type} <- AuthzOrder], + case emqx_authz:reorder(SourcesOrder) of + {ok, _} -> + {204}; + {error, {_PrePostConfUpd, _, #{not_found := NotFound, not_reordered := NotReordered}}} -> + NotFoundFmt = "Authorization sources: ~ts are not found", + NotReorderedFmt = "No positions are specified for authorization sources: ~ts", + Msg = + case {NotFound, NotReordered} of + {[_ | _], []} -> + binfmt(NotFoundFmt, [?join(NotFound)]); + {[], [_ | _]} -> + binfmt(NotReorderedFmt, [?join(NotReordered)]); + _ -> + binfmt(NotFoundFmt ++ ", " ++ NotReorderedFmt, [ + ?join(NotFound), ?join(NotReordered) + ]) + end, + {400, #{code => <<"BAD_REQUEST">>, message => Msg}}; + {error, Reason} -> + {400, #{code => <<"BAD_REQUEST">>, message => bin(Reason)}} + end. + %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- @@ -556,7 +609,9 @@ position_example() -> } }. -bin(Term) -> erlang:iolist_to_binary(io_lib:format("~p", [Term])). +bin(Term) -> binfmt("~p", [Term]). + +binfmt(Fmt, Args) -> iolist_to_binary(io_lib:format(Fmt, Args)). status_metrics_example() -> #{ diff --git a/apps/emqx_auth/test/emqx_authn/emqx_authn_api_SUITE.erl b/apps/emqx_auth/test/emqx_authn/emqx_authn_api_SUITE.erl index cceab0d54..fbd0bb9e4 100644 --- a/apps/emqx_auth/test/emqx_authn/emqx_authn_api_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authn/emqx_authn_api_SUITE.erl @@ -124,6 +124,111 @@ t_authenticator_fail(_) -> t_authenticator_position(_) -> test_authenticator_position([]). +t_authenticators_reorder(_) -> + AuthenticatorConfs = [ + emqx_authn_test_lib:http_example(), + %% Disabling an authenticator must not affect the requested order + (emqx_authn_test_lib:jwt_example())#{enable => false}, + emqx_authn_test_lib:built_in_database_example() + ], + lists:foreach( + fun(Conf) -> + {ok, 200, _} = request( + post, + uri([?CONF_NS]), + Conf + ) + end, + AuthenticatorConfs + ), + ?assertAuthenticatorsMatch( + [ + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"jwt">>, <<"enable">> := false}, + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} + ], + [?CONF_NS] + ), + + OrderUri = uri([?CONF_NS, "order"]), + + %% Invalid moves + + %% Bad schema + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"not-id">> => <<"password_based:http">>}, + #{<<"not-id">> => <<"jwt">>} + ] + ), + + %% Partial order + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"id">> => <<"password_based:http">>}, + #{<<"id">> => <<"jwt">>} + ] + ), + + %% Not found authenticators + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"id">> => <<"password_based:http">>}, + #{<<"id">> => <<"jwt">>}, + #{<<"id">> => <<"password_based:built_in_database">>}, + #{<<"id">> => <<"password_based:mongodb">>} + ] + ), + + %% Both partial and not found errors + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"id">> => <<"password_based:http">>}, + #{<<"id">> => <<"password_based:built_in_database">>}, + #{<<"id">> => <<"password_based:mongodb">>} + ] + ), + + %% Duplicates + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"id">> => <<"password_based:http">>}, + #{<<"id">> => <<"password_based:built_in_database">>}, + #{<<"id">> => <<"jwt">>}, + #{<<"id">> => <<"password_based:http">>} + ] + ), + + %% Valid moves + {ok, 204, _} = request( + put, + OrderUri, + [ + #{<<"id">> => <<"password_based:built_in_database">>}, + #{<<"id">> => <<"jwt">>}, + #{<<"id">> => <<"password_based:http">>} + ] + ), + + ?assertAuthenticatorsMatch( + [ + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}, + #{<<"mechanism">> := <<"jwt">>, <<"enable">> := false}, + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>} + ], + [?CONF_NS] + ). + %t_listener_authenticators(_) -> % test_authenticators(["listeners", ?TCP_DEFAULT]). diff --git a/apps/emqx_auth/test/emqx_authz/emqx_authz_api_sources_SUITE.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_api_sources_SUITE.erl index e9dfa6a7b..246594777 100644 --- a/apps/emqx_auth/test/emqx_authz/emqx_authz_api_sources_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authz/emqx_authz_api_sources_SUITE.erl @@ -29,7 +29,7 @@ -define(PGSQL_HOST, "pgsql"). -define(REDIS_SINGLE_HOST, "redis"). --define(SOURCE_REDIS1, #{ +-define(SOURCE_HTTP, #{ <<"type">> => <<"http">>, <<"enable">> => true, <<"url">> => <<"https://fake.com:443/acl?username=", ?PH_USERNAME/binary>>, @@ -74,7 +74,7 @@ <<"ssl">> => #{<<"enable">> => false}, <<"query">> => <<"abcb">> }). --define(SOURCE_REDIS2, #{ +-define(SOURCE_REDIS, #{ <<"type">> => <<"redis">>, <<"enable">> => true, <<"servers">> => <>, @@ -188,10 +188,10 @@ t_api(_) -> {ok, 204, _} = request(post, uri(["authorization", "sources"]), Source) end || Source <- lists:reverse([ - ?SOURCE_MONGODB, ?SOURCE_MYSQL, ?SOURCE_POSTGRESQL, ?SOURCE_REDIS2, ?SOURCE_FILE + ?SOURCE_MONGODB, ?SOURCE_MYSQL, ?SOURCE_POSTGRESQL, ?SOURCE_REDIS, ?SOURCE_FILE ]) ], - {ok, 204, _} = request(post, uri(["authorization", "sources"]), ?SOURCE_REDIS1), + {ok, 204, _} = request(post, uri(["authorization", "sources"]), ?SOURCE_HTTP), {ok, 200, Result2} = request(get, uri(["authorization", "sources"]), []), Sources = get_sources(Result2), @@ -211,7 +211,7 @@ t_api(_) -> {ok, 204, _} = request( put, uri(["authorization", "sources", "http"]), - ?SOURCE_REDIS1#{<<"enable">> := false} + ?SOURCE_HTTP#{<<"enable">> := false} ), {ok, 200, Result3} = request(get, uri(["authorization", "sources", "http"]), []), ?assertMatch( @@ -338,7 +338,7 @@ t_api(_) -> {ok, 204, _} = request( put, uri(["authorization", "sources", "redis"]), - ?SOURCE_REDIS2#{ + ?SOURCE_REDIS#{ <<"servers">> := [ <<"192.168.1.100:6379">>, <<"192.168.1.100:6380">> @@ -503,7 +503,7 @@ t_api(_) -> t_source_move(_) -> {ok, _} = emqx_authz:update(replace, [ - ?SOURCE_REDIS1, ?SOURCE_MONGODB, ?SOURCE_MYSQL, ?SOURCE_POSTGRESQL, ?SOURCE_REDIS2 + ?SOURCE_HTTP, ?SOURCE_MONGODB, ?SOURCE_MYSQL, ?SOURCE_POSTGRESQL, ?SOURCE_REDIS ]), ?assertMatch( [ @@ -582,6 +582,123 @@ t_source_move(_) -> ok. +t_sources_reorder(_) -> + %% Disabling an auth source must not affect the requested order + MongoDbDisabled = (?SOURCE_MONGODB)#{<<"enable">> => false}, + {ok, _} = emqx_authz:update(replace, [ + ?SOURCE_HTTP, MongoDbDisabled, ?SOURCE_MYSQL, ?SOURCE_POSTGRESQL, ?SOURCE_REDIS + ]), + ?assertMatch( + [ + #{type := http}, + #{type := mongodb}, + #{type := mysql}, + #{type := postgresql}, + #{type := redis} + ], + emqx_authz:lookup() + ), + + OrderUri = uri(["authorization", "sources", "order"]), + + %% Valid moves + {ok, 204, _} = request( + put, + OrderUri, + [ + #{<<"type">> => <<"redis">>}, + #{<<"type">> => <<"http">>}, + #{<<"type">> => <<"postgresql">>}, + #{<<"type">> => <<"mysql">>}, + #{<<"type">> => <<"mongodb">>} + ] + ), + ?assertMatch( + [ + #{type := redis}, + #{type := http}, + #{type := postgresql}, + #{type := mysql}, + #{type := mongodb, enable := false} + ], + emqx_authz:lookup() + ), + + %% Invalid moves + + %% Bad schema + {ok, 400, _} = request( + put, + OrderUri, + [#{<<"not-type">> => <<"redis">>}] + ), + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"type">> => <<"unkonw">>}, + #{<<"type">> => <<"redis">>}, + #{<<"type">> => <<"http">>}, + #{<<"type">> => <<"postgresql">>}, + #{<<"type">> => <<"mysql">>}, + #{<<"type">> => <<"mongodb">>} + ] + ), + + %% Partial order + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"type">> => <<"redis">>}, + #{<<"type">> => <<"http">>}, + #{<<"type">> => <<"postgresql">>}, + #{<<"type">> => <<"mysql">>} + ] + ), + + %% Not found authenticators + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"type">> => <<"redis">>}, + #{<<"type">> => <<"http">>}, + #{<<"type">> => <<"postgresql">>}, + #{<<"type">> => <<"mysql">>}, + #{<<"type">> => <<"mongodb">>}, + #{<<"type">> => <<"built_in_database">>}, + #{<<"type">> => <<"file">>} + ] + ), + + %% Both partial and not found errors + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"type">> => <<"redis">>}, + #{<<"type">> => <<"http">>}, + #{<<"type">> => <<"postgresql">>}, + #{<<"type">> => <<"mysql">>}, + #{<<"type">> => <<"built_in_database">>} + ] + ), + + %% Duplicates + {ok, 400, _} = request( + put, + OrderUri, + [ + #{<<"type">> => <<"redis">>}, + #{<<"type">> => <<"http">>}, + #{<<"type">> => <<"postgresql">>}, + #{<<"type">> => <<"mysql">>}, + #{<<"type">> => <<"mongodb">>}, + #{<<"type">> => <<"http">>} + ] + ). + t_aggregate_metrics(_) -> Metrics = #{ 'emqx@node1.emqx.io' => #{ diff --git a/changes/ce/feat-12509.en.md b/changes/ce/feat-12509.en.md new file mode 100644 index 000000000..c7e2a5e5b --- /dev/null +++ b/changes/ce/feat-12509.en.md @@ -0,0 +1 @@ +Implement API to re-order all authenticators / authorization sources. diff --git a/rel/i18n/emqx_authn_api.hocon b/rel/i18n/emqx_authn_api.hocon index 90240c4f4..9ab689723 100644 --- a/rel/i18n/emqx_authn_api.hocon +++ b/rel/i18n/emqx_authn_api.hocon @@ -60,6 +60,11 @@ authentication_post.desc: authentication_post.label: """Create authenticator""" +authentication_order_put.desc: +"""Reorder all authenticators in global authentication chain.""" +authentication_order_put.label: +"""Reorder Authenticators""" + is_superuser.desc: """Is superuser""" is_superuser.label: diff --git a/rel/i18n/emqx_authz_api_sources.hocon b/rel/i18n/emqx_authz_api_sources.hocon index 1257d9eb8..fdb13e6f5 100644 --- a/rel/i18n/emqx_authz_api_sources.hocon +++ b/rel/i18n/emqx_authz_api_sources.hocon @@ -35,6 +35,11 @@ authorization_sources_type_status_get.desc: authorization_sources_type_status_get.label: """Get a authorization source""" +authorization_sources_order_put.desc: +"""Reorder all authorization sources.""" +authorization_sources_order_put.label: +"""Reorder Authorization Sources""" + source.desc: """Authorization source""" source.label: