diff --git a/apps/emqx/test/emqx_common_test_http.erl b/apps/emqx/test/emqx_common_test_http.erl index 7f50db92b..1034e310f 100644 --- a/apps/emqx/test/emqx_common_test_http.erl +++ b/apps/emqx/test/emqx_common_test_http.erl @@ -17,6 +17,7 @@ -module(emqx_common_test_http). -include_lib("common_test/include/ct.hrl"). +-include_lib("emqx_dashboard/include/emqx_dashboard_rbac.hrl"). -export([ request_api/3, @@ -90,7 +91,12 @@ create_default_app() -> Now = erlang:system_time(second), ExpiredAt = Now + timer:minutes(10), emqx_mgmt_auth:create( - ?DEFAULT_APP_ID, ?DEFAULT_APP_SECRET, true, ExpiredAt, <<"default app key for test">> + ?DEFAULT_APP_ID, + ?DEFAULT_APP_SECRET, + true, + ExpiredAt, + <<"default app key for test">>, + ?ROLE_API_SUPERUSER ). delete_default_app() -> diff --git a/apps/emqx_dashboard/include/emqx_dashboard.hrl b/apps/emqx_dashboard/include/emqx_dashboard.hrl index 9013436e7..c41dbb71c 100644 --- a/apps/emqx_dashboard/include/emqx_dashboard.hrl +++ b/apps/emqx_dashboard/include/emqx_dashboard.hrl @@ -13,16 +13,9 @@ %% See the License for the specific language governing permissions and %% limitations under the License. %%-------------------------------------------------------------------- --define(ADMIN, emqx_admin). +-include("emqx_dashboard_rbac.hrl"). -%% TODO: -%% The predefined roles of the preliminary RBAC implementation, -%% these may be removed when developing the full RBAC feature. -%% In full RBAC feature, the role may be customised created and deleted, -%% a predefined configuration would replace these macros. --define(ROLE_VIEWER, <<"viewer">>). --define(ROLE_SUPERUSER, <<"administrator">>). --define(ROLE_DEFAULT, ?ROLE_SUPERUSER). +-define(ADMIN, emqx_admin). -define(BACKEND_LOCAL, local). -define(SSO_USERNAME(Backend, Name), {Backend, Name}). diff --git a/apps/emqx_dashboard/include/emqx_dashboard_rbac.hrl b/apps/emqx_dashboard/include/emqx_dashboard_rbac.hrl new file mode 100644 index 000000000..8f49464a4 --- /dev/null +++ b/apps/emqx_dashboard/include/emqx_dashboard_rbac.hrl @@ -0,0 +1,33 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-ifndef(EMQX_DASHBOARD_RBAC). +-define(EMQX_DASHBOARD_RBAC, true). + +%% TODO: +%% The predefined roles of the preliminary RBAC implementation, +%% these may be removed when developing the full RBAC feature. +%% In full RBAC feature, the role may be customised created and deleted, +%% a predefined configuration would replace these macros. +-define(ROLE_VIEWER, <<"viewer">>). +-define(ROLE_SUPERUSER, <<"administrator">>). +-define(ROLE_DEFAULT, ?ROLE_SUPERUSER). + +-define(ROLE_API_VIEWER, <<"api_viewer">>). +-define(ROLE_API_SUPERUSER, <<"api_administrator">>). +-define(ROLE_API_PUBLISHER, <<"api_publisher">>). +-define(ROLE_API_DEFAULT, ?ROLE_API_SUPERUSER). + +-endif. diff --git a/apps/emqx_dashboard/src/emqx_dashboard.erl b/apps/emqx_dashboard/src/emqx_dashboard.erl index 4f9e34238..fbd801410 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard.erl @@ -251,7 +251,7 @@ listeners() -> api_key_authorize(Req, Key, Secret) -> Path = cowboy_req:path(Req), - case emqx_mgmt_auth:authorize(Path, Key, Secret) of + case emqx_mgmt_auth:authorize(Path, Req, Key, Secret) of ok -> {ok, #{auth_type => api_key, api_key => Key}}; {error, <<"not_allowed">>} -> @@ -259,6 +259,9 @@ api_key_authorize(Req, Key, Secret) -> ?BAD_API_KEY_OR_SECRET, <<"Not allowed, Check api_key/api_secret">> ); + {error, unauthorized_role} -> + {403, 'UNAUTHORIZED_ROLE', + <<"This API Key don't have permission to access this resource">>}; {error, _} -> return_unauthorized( ?BAD_API_KEY_OR_SECRET, diff --git a/apps/emqx_dashboard/src/emqx_dashboard_admin.erl b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl index e9aac164b..e2bde51bd 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_admin.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_admin.erl @@ -416,7 +416,7 @@ ensure_role(Role) when is_binary(Role) -> -if(?EMQX_RELEASE_EDITION == ee). legal_role(Role) -> - emqx_dashboard_rbac:valid_role(Role). + emqx_dashboard_rbac:valid_dashboard_role(Role). role(Data) -> emqx_dashboard_rbac:role(Data). @@ -447,8 +447,10 @@ lookup_user(Backend, Username) when is_atom(Backend) -> -dialyzer({no_match, [add_user/4, update_user/3]}). +legal_role(?ROLE_DEFAULT) -> + ok; legal_role(_) -> - ok. + {error, <<"Role does not exist">>}. role(_) -> ?ROLE_DEFAULT. diff --git a/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.app.src b/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.app.src index 190764e2f..ec8e6cd3f 100644 --- a/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.app.src +++ b/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.app.src @@ -1,6 +1,6 @@ {application, emqx_dashboard_rbac, [ {description, "EMQX Dashboard RBAC"}, - {vsn, "0.1.0"}, + {vsn, "0.1.1"}, {registered, []}, {applications, [ kernel, diff --git a/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.erl b/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.erl index 28bd8960e..2bc6a5bf9 100644 --- a/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.erl +++ b/apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.erl @@ -6,7 +6,12 @@ -include_lib("emqx_dashboard/include/emqx_dashboard.hrl"). --export([check_rbac/2, role/1, valid_role/1]). +-export([ + check_rbac/2, + role/1, + valid_dashboard_role/1, + valid_api_role/1 +]). -dialyzer({nowarn_function, role/1}). %%===================================================================== @@ -31,25 +36,44 @@ role(#?ADMIN{role = Role}) -> role([]) -> ?ROLE_SUPERUSER; role(#{role := Role}) -> + Role; +role(Role) when is_binary(Role) -> Role. -valid_role(Role) -> - case lists:member(Role, role_list()) of +valid_dashboard_role(Role) -> + valid_role(dashboard, Role). + +valid_api_role(Role) -> + valid_role(api, Role). + +%% =================================================================== +check_rbac(?ROLE_SUPERUSER, _, _) -> + true; +check_rbac(?ROLE_API_SUPERUSER, _, _) -> + true; +check_rbac(?ROLE_VIEWER, <<"GET">>, _) -> + true; +check_rbac(?ROLE_API_VIEWER, <<"GET">>, _) -> + true; +%% this API is a special case +check_rbac(?ROLE_VIEWER, <<"POST">>, <<"/logout">>) -> + true; +check_rbac(?ROLE_API_PUBLISHER, <<"POST">>, <<"/publish">>) -> + true; +check_rbac(?ROLE_API_PUBLISHER, <<"POST">>, <<"/publish/bulk">>) -> + true; +check_rbac(_, _, _) -> + false. + +valid_role(Type, Role) -> + case lists:member(Role, role_list(Type)) of true -> ok; _ -> {error, <<"Role does not exist">>} end. -%% =================================================================== -check_rbac(?ROLE_SUPERUSER, _, _) -> - true; -check_rbac(?ROLE_VIEWER, <<"GET">>, _) -> - true; -%% this API is a special case -check_rbac(?ROLE_VIEWER, <<"POST">>, <<"/logout">>) -> - true; -check_rbac(_, _, _) -> - false. -role_list() -> - [?ROLE_VIEWER, ?ROLE_SUPERUSER]. +role_list(dashboard) -> + [?ROLE_VIEWER, ?ROLE_SUPERUSER]; +role_list(api) -> + [?ROLE_API_VIEWER, ?ROLE_API_PUBLISHER, ?ROLE_API_SUPERUSER]. diff --git a/apps/emqx_management/src/emqx_mgmt_api_api_keys.erl b/apps/emqx_management/src/emqx_mgmt_api_api_keys.erl index 78bbef540..0523fd244 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_api_keys.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_api_keys.erl @@ -19,6 +19,7 @@ -include_lib("typerefl/include/types.hrl"). -include_lib("hocon/include/hoconsc.hrl"). +-include_lib("emqx_dashboard/include/emqx_dashboard_rbac.hrl"). -export([api_spec/0, fields/1, paths/0, schema/1, namespace/0]). -export([api_key/2, api_key_by_name/2]). @@ -150,7 +151,7 @@ fields(app) -> )}, {enable, hoconsc:mk(boolean(), #{desc => "Enable/Disable", required => false})}, {expired, hoconsc:mk(boolean(), #{desc => "Expired", required => false})} - ]; + ] ++ app_extend_fields(); fields(name) -> [ {name, @@ -192,7 +193,8 @@ api_key(post, #{body := App}) -> } = App, ExpiredAt = ensure_expired_at(App), Desc = unicode:characters_to_binary(Desc0, unicode), - case emqx_mgmt_auth:create(Name, Enable, ExpiredAt, Desc) of + Role = maps:get(<<"role">>, App, ?ROLE_API_DEFAULT), + case emqx_mgmt_auth:create(Name, Enable, ExpiredAt, Desc, Role) of {ok, NewApp} -> {200, emqx_mgmt_auth:format(NewApp)}; {error, Reason} -> @@ -218,10 +220,38 @@ api_key_by_name(put, #{bindings := #{name := Name}, body := Body}) -> Enable = maps:get(<<"enable">>, Body, undefined), ExpiredAt = ensure_expired_at(Body), Desc = maps:get(<<"desc">>, Body, undefined), - case emqx_mgmt_auth:update(Name, Enable, ExpiredAt, Desc) of - {ok, App} -> {200, emqx_mgmt_auth:format(App)}; - {error, not_found} -> {404, ?NOT_FOUND_RESPONSE} + Role = maps:get(<<"role">>, Body, ?ROLE_API_DEFAULT), + case emqx_mgmt_auth:update(Name, Enable, ExpiredAt, Desc, Role) of + {ok, App} -> + {200, emqx_mgmt_auth:format(App)}; + {error, not_found} -> + {404, ?NOT_FOUND_RESPONSE}; + {error, Reason} -> + {400, #{ + code => 'BAD_REQUEST', + message => iolist_to_binary(io_lib:format("~p", [Reason])) + }} end. ensure_expired_at(#{<<"expired_at">> := ExpiredAt}) when is_integer(ExpiredAt) -> ExpiredAt; ensure_expired_at(_) -> infinity. + +-if(?EMQX_RELEASE_EDITION == ee). + +app_extend_fields() -> + [ + {role, + hoconsc:mk(binary(), #{ + desc => ?DESC(role), + default => ?ROLE_API_DEFAULT, + example => ?ROLE_API_DEFAULT, + validator => fun emqx_dashboard_rbac:valid_api_role/1 + })} + ]. + +-else. + +app_extend_fields() -> + []. + +-endif. diff --git a/apps/emqx_management/src/emqx_mgmt_auth.erl b/apps/emqx_management/src/emqx_mgmt_auth.erl index 3d32afc19..c39e3888d 100644 --- a/apps/emqx_management/src/emqx_mgmt_auth.erl +++ b/apps/emqx_management/src/emqx_mgmt_auth.erl @@ -16,6 +16,7 @@ -module(emqx_mgmt_auth). -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). +-include_lib("emqx_dashboard/include/emqx_dashboard_rbac.hrl"). -behaviour(emqx_db_backup). @@ -25,16 +26,16 @@ -behaviour(emqx_config_handler). -export([ - create/4, + create/5, read/1, - update/4, + update/5, delete/1, list/0, init_bootstrap_file/0, format/1 ]). --export([authorize/3]). +-export([authorize/4]). -export([post_config_update/5]). -export([backup_tables/0]). @@ -48,10 +49,11 @@ ]). -ifdef(TEST). --export([create/5]). +-export([create/6]). -endif. -define(APP, emqx_app). +-type api_user_role() :: binary(). -record(?APP, { name = <<>> :: binary() | '_', @@ -60,17 +62,21 @@ enable = true :: boolean() | '_', desc = <<>> :: binary() | '_', expired_at = 0 :: integer() | undefined | infinity | '_', - created_at = 0 :: integer() | '_' + created_at = 0 :: integer() | '_', + role = ?ROLE_DEFAULT :: api_user_role() | '_', + extra = #{} :: map() | '_' }). mnesia(boot) -> + Fields = record_info(fields, ?APP), ok = mria:create_table(?APP, [ {type, set}, {rlog_shard, ?COMMON_SHARD}, {storage, disc_copies}, {record_name, ?APP}, - {attributes, record_info(fields, ?APP)} - ]). + {attributes, Fields} + ]), + maybe_migrate_table(Fields). %%-------------------------------------------------------------------- %% Data backup @@ -95,13 +101,13 @@ init_bootstrap_file() -> ?SLOG(debug, #{msg => "init_bootstrap_api_keys_from_file", file => File}), init_bootstrap_file(File). -create(Name, Enable, ExpiredAt, Desc) -> +create(Name, Enable, ExpiredAt, Desc, Role) -> ApiSecret = generate_api_secret(), - create(Name, ApiSecret, Enable, ExpiredAt, Desc). + create(Name, ApiSecret, Enable, ExpiredAt, Desc, Role). -create(Name, ApiSecret, Enable, ExpiredAt, Desc) -> +create(Name, ApiSecret, Enable, ExpiredAt, Desc, Role) -> case mnesia:table_info(?APP, size) < 100 of - true -> create_app(Name, ApiSecret, Enable, ExpiredAt, Desc); + true -> create_app(Name, ApiSecret, Enable, ExpiredAt, Desc, Role); false -> {error, "Maximum ApiKey"} end. @@ -111,8 +117,13 @@ read(Name) -> [] -> {error, not_found} end. -update(Name, Enable, ExpiredAt, Desc) -> - trans(fun ?MODULE:do_update/4, [Name, Enable, ExpiredAt, Desc]). +update(Name, Enable, ExpiredAt, Desc, Role) -> + case valid_role(Role) of + ok -> + trans(fun ?MODULE:do_update/4, [Name, Enable, ExpiredAt, Desc]); + Error -> + Error + end. do_update(Name, Enable, ExpiredAt, Desc) -> case mnesia:read(?APP, Name, write) of @@ -138,37 +149,37 @@ do_delete(Name) -> [_App] -> mnesia:delete({?APP, Name}) end. -format(App = #{expired_at := ExpiredAt0, created_at := CreateAt}) -> - ExpiredAt = - case ExpiredAt0 of - infinity -> <<"infinity">>; - _ -> emqx_utils_calendar:epoch_to_rfc3339(ExpiredAt0, second) - end, - App#{ - expired_at => ExpiredAt, - created_at => emqx_utils_calendar:epoch_to_rfc3339(CreateAt, second) - }. +format(App = #{expired_at := ExpiredAt, created_at := CreateAt}) -> + format_app_extend(App#{ + expired_at => format_epoch(ExpiredAt), + created_at => format_epoch(CreateAt) + }). + +format_epoch(infinity) -> + <<"infinity">>; +format_epoch(Epoch) -> + emqx_utils_calendar:epoch_to_rfc3339(Epoch, second). list() -> to_map(ets:match_object(?APP, #?APP{_ = '_'})). -authorize(<<"/api/v5/users", _/binary>>, _ApiKey, _ApiSecret) -> +authorize(<<"/api/v5/users", _/binary>>, _Req, _ApiKey, _ApiSecret) -> {error, <<"not_allowed">>}; -authorize(<<"/api/v5/api_key", _/binary>>, _ApiKey, _ApiSecret) -> +authorize(<<"/api/v5/api_key", _/binary>>, _Req, _ApiKey, _ApiSecret) -> {error, <<"not_allowed">>}; -authorize(<<"/api/v5/logout", _/binary>>, _ApiKey, _ApiSecret) -> +authorize(<<"/api/v5/logout", _/binary>>, _Req, _ApiKey, _ApiSecret) -> {error, <<"not_allowed">>}; -authorize(_Path, ApiKey, ApiSecret) -> +authorize(_Path, Req, ApiKey, ApiSecret) -> Now = erlang:system_time(second), case find_by_api_key(ApiKey) of - {ok, true, ExpiredAt, SecretHash} when ExpiredAt >= Now -> + {ok, true, ExpiredAt, SecretHash, Role} when ExpiredAt >= Now -> case emqx_dashboard_admin:verify_hash(ApiSecret, SecretHash) of - ok -> ok; + ok -> check_rbac(Req, Role); error -> {error, "secret_error"} end; - {ok, true, _ExpiredAt, _SecretHash} -> + {ok, true, _ExpiredAt, _SecretHash, _Role} -> {error, "secret_expired"}; - {ok, false, _ExpiredAt, _SecretHash} -> + {ok, false, _ExpiredAt, _SecretHash, _Role} -> {error, "secret_disable"}; {error, Reason} -> {error, Reason} @@ -177,8 +188,12 @@ authorize(_Path, ApiKey, ApiSecret) -> find_by_api_key(ApiKey) -> Fun = fun() -> mnesia:match_object(#?APP{api_key = ApiKey, _ = '_'}) end, case mria:ro_transaction(?COMMON_SHARD, Fun) of - {atomic, [#?APP{api_secret_hash = SecretHash, enable = Enable, expired_at = ExpiredAt}]} -> - {ok, Enable, ExpiredAt, SecretHash}; + {atomic, [ + #?APP{ + api_secret_hash = SecretHash, enable = Enable, expired_at = ExpiredAt, role = Role + } + ]} -> + {ok, Enable, ExpiredAt, SecretHash, Role}; _ -> {error, "not_found"} end. @@ -202,7 +217,7 @@ to_map(#?APP{name = N, api_key = K, enable = E, expired_at = ET, created_at = CT is_expired(undefined) -> false; is_expired(ExpiredTime) -> ExpiredTime < erlang:system_time(second). -create_app(Name, ApiSecret, Enable, ExpiredAt, Desc) -> +create_app(Name, ApiSecret, Enable, ExpiredAt, Desc, Role) -> App = #?APP{ name = Name, @@ -211,7 +226,8 @@ create_app(Name, ApiSecret, Enable, ExpiredAt, Desc) -> desc = Desc, created_at = erlang:system_time(second), api_secret_hash = emqx_dashboard_admin:hash(ApiSecret), - api_key = list_to_binary(emqx_utils:gen_id(16)) + api_key = list_to_binary(emqx_utils:gen_id(16)), + role = Role }, case create_app(App) of {ok, Res} -> @@ -220,8 +236,13 @@ create_app(Name, ApiSecret, Enable, ExpiredAt, Desc) -> Error end. -create_app(App = #?APP{api_key = ApiKey, name = Name}) -> - trans(fun ?MODULE:do_create_app/3, [App, ApiKey, Name]). +create_app(App = #?APP{api_key = ApiKey, name = Name, role = Role}) -> + case valid_role(Role) of + ok -> + trans(fun ?MODULE:do_create_app/3, [App, ApiKey, Name]); + Error -> + Error + end. force_create_app(NamePrefix, App = #?APP{api_key = ApiKey}) -> trans(fun ?MODULE:do_force_create_app/3, [App, ApiKey, NamePrefix]). @@ -340,3 +361,60 @@ add_bootstrap_file(File, Dev, MP, Line) -> {error, Reason} -> throw(#{file => File, line => Line, reason => Reason}) end. + +-if(?EMQX_RELEASE_EDITION == ee). +check_rbac(Req, Role) -> + case emqx_dashboard_rbac:check_rbac(Req, Role) of + true -> + ok; + _ -> + {error, unauthorized_role} + end. + +format_app_extend(App) -> + App. + +valid_role(Role) -> + emqx_dashboard_rbac:valid_api_role(Role). + +-else. + +check_rbac(_Req, _Role) -> + ok. + +format_app_extend(App) -> + maps:remove(role, App). + +valid_role(?ROLE_API_DEFAULT) -> + ok; +valid_role(_) -> + {error, <<"Role does not exist">>}. + +-endif. + +maybe_migrate_table(Fields) -> + case mnesia:table_info(?APP, attributes) =:= Fields of + true -> + ok; + false -> + TransFun = fun(App) -> + case App of + {?APP, Name, Key, Hash, Enable, Desc, ExpiredAt, CreatedAt} -> + #?APP{ + name = Name, + api_key = Key, + api_secret_hash = Hash, + enable = Enable, + desc = Desc, + expired_at = ExpiredAt, + created_at = CreatedAt, + role = ?ROLE_API_VIEWER, + extra = #{} + }; + #?APP{} -> + App + end + end, + {atomic, ok} = mnesia:transform_table(?APP, TransFun, Fields, ?APP), + ok + end. diff --git a/apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl index 2a78f76fc..b04fbf270 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl @@ -41,37 +41,42 @@ t_bootstrap_file(_) -> File = "./bootstrap_api_keys.txt", ok = file:write_file(File, Bin), update_file(File), - ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-1">>, <<"secret-1">>)), - ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-2">>, <<"secret-2">>)), - ?assertMatch({error, _}, emqx_mgmt_auth:authorize(TestPath, <<"test-2">>, <<"secret-1">>)), + ?assertEqual(ok, auth_authorize(TestPath, <<"test-1">>, <<"secret-1">>)), + ?assertEqual(ok, auth_authorize(TestPath, <<"test-2">>, <<"secret-2">>)), + ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-2">>, <<"secret-1">>)), %% relaunch to check if the table is changed. Bin1 = <<"test-1:new-secret-1\ntest-2:new-secret-2">>, ok = file:write_file(File, Bin1), update_file(File), - ?assertMatch({error, _}, emqx_mgmt_auth:authorize(TestPath, <<"test-1">>, <<"secret-1">>)), - ?assertMatch({error, _}, emqx_mgmt_auth:authorize(TestPath, <<"test-2">>, <<"secret-2">>)), - ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-1">>, <<"new-secret-1">>)), - ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-2">>, <<"new-secret-2">>)), + ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-1">>, <<"secret-1">>)), + ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-2">>, <<"secret-2">>)), + ?assertEqual(ok, auth_authorize(TestPath, <<"test-1">>, <<"new-secret-1">>)), + ?assertEqual(ok, auth_authorize(TestPath, <<"test-2">>, <<"new-secret-2">>)), %% not error when bootstrap_file is empty update_file(<<>>), update_file("./bootstrap_apps_not_exist.txt"), - ?assertMatch({error, _}, emqx_mgmt_auth:authorize(TestPath, <<"test-1">>, <<"secret-1">>)), - ?assertMatch({error, _}, emqx_mgmt_auth:authorize(TestPath, <<"test-2">>, <<"secret-2">>)), - ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-1">>, <<"new-secret-1">>)), - ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-2">>, <<"new-secret-2">>)), + ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-1">>, <<"secret-1">>)), + ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-2">>, <<"secret-2">>)), + ?assertEqual(ok, auth_authorize(TestPath, <<"test-1">>, <<"new-secret-1">>)), + ?assertEqual(ok, auth_authorize(TestPath, <<"test-2">>, <<"new-secret-2">>)), %% bad format BadBin = <<"test-1:secret-11\ntest-2 secret-12">>, ok = file:write_file(File, BadBin), update_file(File), ?assertMatch({error, #{reason := "invalid_format"}}, emqx_mgmt_auth:init_bootstrap_file()), - ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-1">>, <<"secret-11">>)), - ?assertMatch({error, _}, emqx_mgmt_auth:authorize(TestPath, <<"test-2">>, <<"secret-12">>)), + ?assertEqual(ok, auth_authorize(TestPath, <<"test-1">>, <<"secret-11">>)), + ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-2">>, <<"secret-12">>)), update_file(<<>>), ok. +auth_authorize(Path, Key, Secret) -> + FakePath = erlang:list_to_binary(emqx_dashboard_swagger:relative_uri("/fake")), + FakeReq = #{method => <<"GET">>, path => FakePath}, + emqx_mgmt_auth:authorize(Path, FakeReq, Key, Secret). + update_file(File) -> ?assertMatch({ok, _}, emqx:update_config([<<"api_key">>], #{<<"bootstrap_file">> => File})). diff --git a/rel/i18n/emqx_mgmt_api_api_keys.hocon b/rel/i18n/emqx_mgmt_api_api_keys.hocon index 85d5c4ec7..8acbe60d0 100644 --- a/rel/i18n/emqx_mgmt_api_api_keys.hocon +++ b/rel/i18n/emqx_mgmt_api_api_keys.hocon @@ -30,4 +30,7 @@ format.desc: format.label: """Unique and format by [a-zA-Z0-9-_]""" +role.desc: +"""Role for this API""" + }