feat(api_key): add RBAC feature for the API key
This commit is contained in:
parent
96c546c187
commit
e095de7367
|
@ -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() ->
|
||||
|
|
|
@ -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}).
|
||||
|
|
|
@ -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.
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{application, emqx_dashboard_rbac, [
|
||||
{description, "EMQX Dashboard RBAC"},
|
||||
{vsn, "0.1.0"},
|
||||
{vsn, "0.1.1"},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
|
|
|
@ -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].
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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})).
|
||||
|
||||
|
|
|
@ -30,4 +30,7 @@ format.desc:
|
|||
format.label:
|
||||
"""Unique and format by [a-zA-Z0-9-_]"""
|
||||
|
||||
role.desc:
|
||||
"""Role for this API"""
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue