test(rbac): add test cases for RBAC && update changes

This commit is contained in:
firest 2023-09-15 18:07:43 +08:00
parent b9fb243ac3
commit 4b97d3f57d
9 changed files with 204 additions and 21 deletions

View File

@ -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).

View File

@ -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.

View File

@ -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)
),

View File

@ -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),

View File

@ -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)}.

View File

@ -1,2 +0,0 @@
src/emqx_ldap_filter_lexer.erl
src/emqx_ldap_filter_parser.erl

View File

@ -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
).

View File

@ -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.

View File

@ -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},