test(rbac): add test cases for RBAC && update changes
This commit is contained in:
parent
b9fb243ac3
commit
4b97d3f57d
|
@ -56,10 +56,11 @@
|
|||
default_username/0
|
||||
]).
|
||||
|
||||
-export([role/1]).
|
||||
|
||||
-export([backup_tables/0]).
|
||||
|
||||
-type emqx_admin() :: #?ADMIN{}.
|
||||
-define(BOOTSTRAP_USER_TAG, <<"bootstrap user">>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Mnesia bootstrap
|
||||
|
@ -228,7 +229,20 @@ remove_user(Username) when is_binary(Username) ->
|
|||
update_user(Username, Role, Desc) when is_binary(Username) ->
|
||||
case legal_role(Role) of
|
||||
ok ->
|
||||
return(mria:transaction(?DASHBOARD_SHARD, fun update_user_/3, [Username, Role, Desc]));
|
||||
case
|
||||
return(
|
||||
mria:transaction(?DASHBOARD_SHARD, fun update_user_/3, [Username, Role, Desc])
|
||||
)
|
||||
of
|
||||
{ok, {true, Result}} ->
|
||||
{ok, Result};
|
||||
{ok, {false, Result}} ->
|
||||
%% role has changed, destroy the related token
|
||||
_ = emqx_dashboard_token:destroy_by_username(Username),
|
||||
{ok, Result};
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
@ -258,7 +272,7 @@ update_user_(Username, Role, Desc) ->
|
|||
mnesia:abort(<<"username_not_found">>);
|
||||
[Admin] ->
|
||||
mnesia:write(Admin#?ADMIN{role = Role, description = Desc}),
|
||||
#{username => Username, role => Role, description => Desc}
|
||||
{role(Admin) =:= Role, #{username => Username, role => Role, description => Desc}}
|
||||
end.
|
||||
|
||||
change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) ->
|
||||
|
@ -381,8 +395,9 @@ add_default_user(Username, Password) ->
|
|||
_ -> {ok, default_user_exists}
|
||||
end.
|
||||
|
||||
%% ensure the `role` is correct when it directly read from the table
|
||||
%% ensure the `role` is correct when it is directly read from the table
|
||||
%% this value in old data is `undefined`
|
||||
-dialyzer({no_match, ensure_role/1}).
|
||||
ensure_role(undefined) ->
|
||||
?ROLE_SUPERUSER;
|
||||
ensure_role(Role) when is_binary(Role) ->
|
||||
|
@ -392,6 +407,9 @@ ensure_role(Role) when is_binary(Role) ->
|
|||
legal_role(Role) ->
|
||||
emqx_dashboard_rbac:legal_role(Role).
|
||||
|
||||
role(Data) ->
|
||||
emqx_dashboard_rbac:role(Data).
|
||||
|
||||
-else.
|
||||
|
||||
-dialyzer({no_match, [add_user/4, update_user/3]}).
|
||||
|
@ -399,6 +417,8 @@ legal_role(Role) ->
|
|||
legal_role(_) ->
|
||||
ok.
|
||||
|
||||
role(_) ->
|
||||
?ROLE_DEFAULT.
|
||||
-endif.
|
||||
|
||||
-ifdef(TEST).
|
||||
|
|
|
@ -117,7 +117,7 @@ do_sign(#?ADMIN{username = Username} = User, Password) ->
|
|||
},
|
||||
Signed = jose_jwt:sign(JWK, JWS, JWT),
|
||||
{_, Token} = jose_jws:compact(Signed),
|
||||
Role = role(User),
|
||||
Role = emqx_dashboard_admin:role(User),
|
||||
JWTRec = format(Token, Username, Role, ExpTime),
|
||||
_ = mria:transaction(?DASHBOARD_SHARD, fun mnesia:write/1, [JWTRec]),
|
||||
{ok, Token}.
|
||||
|
@ -249,9 +249,6 @@ clean_expired_jwt(Now) ->
|
|||
check_rbac(Req, Extra) ->
|
||||
emqx_dashboard_rbac:check_rbac(Req, Extra).
|
||||
|
||||
role(Data) ->
|
||||
emqx_dashboard_rbac:role(Data).
|
||||
|
||||
-else.
|
||||
|
||||
-dialyzer({nowarn_function, [check_rbac/2]}).
|
||||
|
@ -260,7 +257,4 @@ role(Data) ->
|
|||
check_rbac(_Req, _Extra) ->
|
||||
true.
|
||||
|
||||
role(_) ->
|
||||
?ROLE_DEFAULT.
|
||||
|
||||
-endif.
|
||||
|
|
|
@ -119,10 +119,11 @@ t_rest_api(_Config) ->
|
|||
{ok, 200, Res0} = http_get(["users"]),
|
||||
?assertEqual(
|
||||
[
|
||||
#{
|
||||
filter_req(#{
|
||||
<<"username">> => <<"admin">>,
|
||||
<<"description">> => <<"administrator">>
|
||||
}
|
||||
<<"description">> => <<"administrator">>,
|
||||
<<"role">> => ?ROLE_SUPERUSER
|
||||
})
|
||||
],
|
||||
get_http_data(Res0)
|
||||
),
|
||||
|
|
|
@ -175,7 +175,7 @@ t_clean_token(_) ->
|
|||
NewPassword = <<"public_www2">>,
|
||||
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, ?ROLE_SUPERUSER, <<"desc">>),
|
||||
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
|
||||
FakeReq = #{method => <<"get">>},
|
||||
FakeReq = #{method => <<"GET">>},
|
||||
ok = emqx_dashboard_admin:verify_token(FakeReq, Token),
|
||||
%% change password
|
||||
{ok, _} = emqx_dashboard_admin:change_password(Username, Password, NewPassword),
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
request/2,
|
||||
request/3,
|
||||
request/4,
|
||||
request/5,
|
||||
multipart_formdata_request/3,
|
||||
multipart_formdata_request/4,
|
||||
host/0,
|
||||
|
@ -73,6 +74,9 @@ request(Method, Url, Body) ->
|
|||
request(<<"admin">>, Method, Url, Body).
|
||||
|
||||
request(Username, Method, Url, Body) ->
|
||||
request(Username, <<"public">>, Method, Url, Body).
|
||||
|
||||
request(Username, Password, Method, Url, Body) ->
|
||||
Request =
|
||||
case Body of
|
||||
[] when
|
||||
|
@ -80,9 +84,10 @@ request(Username, Method, Url, Body) ->
|
|||
Method =:= head orelse Method =:= delete orelse
|
||||
Method =:= trace
|
||||
->
|
||||
{Url, [auth_header(Username)]};
|
||||
{Url, [auth_header(Username, Password)]};
|
||||
_ ->
|
||||
{Url, [auth_header(Username)], "application/json", emqx_utils_json:encode(Body)}
|
||||
{Url, [auth_header(Username, Password)], "application/json",
|
||||
emqx_utils_json:encode(Body)}
|
||||
end,
|
||||
ct:pal("Method: ~p, Request: ~p", [Method, Request]),
|
||||
case httpc:request(Method, Request, [], [{body_format, binary}]) of
|
||||
|
@ -108,7 +113,9 @@ uri(Host, Parts) when is_list(Host), is_list(Parts) ->
|
|||
Host ++ "/" ++ to_list(filename:join([?BASE_PATH, ?API_VERSION | NParts])).
|
||||
|
||||
auth_header(Username) ->
|
||||
Password = <<"public">>,
|
||||
auth_header(Username, <<"public">>).
|
||||
|
||||
auth_header(Username, Password) ->
|
||||
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
|
||||
{"Authorization", "Bearer " ++ binary_to_list(Token)}.
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
src/emqx_ldap_filter_lexer.erl
|
||||
src/emqx_ldap_filter_parser.erl
|
|
@ -0,0 +1,157 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_dashboard_rbac_SUITE).
|
||||
|
||||
-compile(nowarn_export_all).
|
||||
-compile(export_all).
|
||||
|
||||
-include("emqx_dashboard.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-import(emqx_dashboard_api_test_helpers, [request/4, uri/1]).
|
||||
|
||||
-define(DEFAULT_SUPERUSER, <<"admin_user">>).
|
||||
-define(DEFAULT_SUPERUSER_PASS, <<"admin_password">>).
|
||||
-define(ADD_DESCRIPTION, <<>>).
|
||||
|
||||
all() ->
|
||||
emqx_common_test_helpers:all(?MODULE).
|
||||
|
||||
init_per_suite(Config) ->
|
||||
emqx_mgmt_api_test_util:init_suite([emqx_conf]),
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
emqx_mgmt_api_test_util:end_suite([emqx_conf]).
|
||||
|
||||
end_per_testcase(_, _Config) ->
|
||||
All = emqx_dashboard_admin:all_users(),
|
||||
[emqx_dashboard_admin:remove_user(Name) || #{username := Name} <- All].
|
||||
|
||||
t_create_bad_role(_) ->
|
||||
?assertEqual(
|
||||
{error, <<"Role does not exist">>},
|
||||
emqx_dashboard_admin:add_user(
|
||||
?DEFAULT_SUPERUSER,
|
||||
?DEFAULT_SUPERUSER_PASS,
|
||||
<<"bad_role">>,
|
||||
?ADD_DESCRIPTION
|
||||
)
|
||||
).
|
||||
|
||||
t_permission(_) ->
|
||||
add_default_superuser(),
|
||||
|
||||
ViewerUser = <<"viewer_user">>,
|
||||
ViewerPassword = <<"add_password">>,
|
||||
|
||||
%% add by superuser
|
||||
{ok, 200, Payload} = emqx_dashboard_api_test_helpers:request(
|
||||
?DEFAULT_SUPERUSER,
|
||||
?DEFAULT_SUPERUSER_PASS,
|
||||
post,
|
||||
uri([users]),
|
||||
#{
|
||||
username => ViewerUser,
|
||||
password => ViewerPassword,
|
||||
role => ?ROLE_VIEWER,
|
||||
description => ?ADD_DESCRIPTION
|
||||
}
|
||||
),
|
||||
|
||||
?assertEqual(
|
||||
#{
|
||||
<<"username">> => ViewerUser,
|
||||
<<"role">> => ?ROLE_VIEWER,
|
||||
<<"description">> => ?ADD_DESCRIPTION
|
||||
},
|
||||
emqx_utils_json:decode(Payload, [return_maps])
|
||||
),
|
||||
|
||||
%% add by viewer
|
||||
?assertMatch(
|
||||
{ok, 403, _},
|
||||
emqx_dashboard_api_test_helpers:request(
|
||||
ViewerUser,
|
||||
ViewerPassword,
|
||||
post,
|
||||
uri([users]),
|
||||
#{
|
||||
username => ViewerUser,
|
||||
password => ViewerPassword,
|
||||
role => ?ROLE_VIEWER,
|
||||
description => ?ADD_DESCRIPTION
|
||||
}
|
||||
)
|
||||
),
|
||||
|
||||
ok.
|
||||
|
||||
t_update_role(_) ->
|
||||
add_default_superuser(),
|
||||
|
||||
%% update role by superuser
|
||||
{ok, 200, Payload} = emqx_dashboard_api_test_helpers:request(
|
||||
?DEFAULT_SUPERUSER,
|
||||
?DEFAULT_SUPERUSER_PASS,
|
||||
put,
|
||||
uri([users, ?DEFAULT_SUPERUSER]),
|
||||
#{
|
||||
role => ?ROLE_VIEWER,
|
||||
description => ?ADD_DESCRIPTION
|
||||
}
|
||||
),
|
||||
|
||||
?assertEqual(
|
||||
#{
|
||||
<<"username">> => ?DEFAULT_SUPERUSER,
|
||||
<<"role">> => ?ROLE_VIEWER,
|
||||
<<"description">> => ?ADD_DESCRIPTION
|
||||
},
|
||||
emqx_utils_json:decode(Payload, [return_maps])
|
||||
),
|
||||
|
||||
%% update role by viewer
|
||||
?assertMatch(
|
||||
{ok, 403, _},
|
||||
emqx_dashboard_api_test_helpers:request(
|
||||
?DEFAULT_SUPERUSER,
|
||||
?DEFAULT_SUPERUSER_PASS,
|
||||
put,
|
||||
uri([users, ?DEFAULT_SUPERUSER]),
|
||||
#{
|
||||
role => ?ROLE_SUPERUSER,
|
||||
description => ?ADD_DESCRIPTION
|
||||
}
|
||||
)
|
||||
),
|
||||
ok.
|
||||
|
||||
t_clean_token(_) ->
|
||||
Username = <<"admin_token">>,
|
||||
Password = <<"public_www1">>,
|
||||
Desc = <<"desc">>,
|
||||
NewDesc = <<"new desc">>,
|
||||
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, ?ROLE_SUPERUSER, Desc),
|
||||
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
|
||||
FakeReq = #{method => <<"GET">>},
|
||||
ok = emqx_dashboard_admin:verify_token(FakeReq, Token),
|
||||
%% change description
|
||||
{ok, _} = emqx_dashboard_admin:update_user(Username, ?ROLE_SUPERUSER, NewDesc),
|
||||
timer:sleep(5),
|
||||
ok = emqx_dashboard_admin:verify_token(FakeReq, Token),
|
||||
%% change role
|
||||
{ok, _} = emqx_dashboard_admin:update_user(Username, ?ROLE_VIEWER, NewDesc),
|
||||
timer:sleep(5),
|
||||
{error, not_found} = emqx_dashboard_admin:verify_token(FakeReq, Token),
|
||||
ok.
|
||||
|
||||
add_default_superuser() ->
|
||||
{ok, _NewUser} = emqx_dashboard_admin:add_user(
|
||||
?DEFAULT_SUPERUSER,
|
||||
?DEFAULT_SUPERUSER_PASS,
|
||||
?ROLE_SUPERUSER,
|
||||
?ADD_DESCRIPTION
|
||||
).
|
|
@ -0,0 +1,6 @@
|
|||
Implemented a preliminary Role-Based Access Control for the Dashboard.
|
||||
In this version, there are two predefined roles:
|
||||
- superuser
|
||||
This role could access all resources.
|
||||
- viewer
|
||||
This role only can access the `GET` resource.
|
2
mix.exs
2
mix.exs
|
@ -58,7 +58,7 @@ defmodule EMQXUmbrella.MixProject do
|
|||
{:ekka, github: "emqx/ekka", tag: "0.15.13", override: true},
|
||||
{:gen_rpc, github: "emqx/gen_rpc", tag: "2.8.1", override: true},
|
||||
{:grpc, github: "emqx/grpc-erl", tag: "0.6.8", override: true},
|
||||
{:minirest, github: "emqx/minirest", tag: "1.3.11", override: true},
|
||||
{:minirest, github: "emqx/minirest", tag: "1.3.12", override: true},
|
||||
{:ecpool, github: "emqx/ecpool", tag: "0.5.4", override: true},
|
||||
{:replayq, github: "emqx/replayq", tag: "0.3.7", override: true},
|
||||
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},
|
||||
|
|
Loading…
Reference in New Issue