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
|
default_username/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([role/1]).
|
||||||
|
|
||||||
-export([backup_tables/0]).
|
-export([backup_tables/0]).
|
||||||
|
|
||||||
-type emqx_admin() :: #?ADMIN{}.
|
-type emqx_admin() :: #?ADMIN{}.
|
||||||
-define(BOOTSTRAP_USER_TAG, <<"bootstrap user">>).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Mnesia bootstrap
|
%% Mnesia bootstrap
|
||||||
|
@ -228,7 +229,20 @@ remove_user(Username) when is_binary(Username) ->
|
||||||
update_user(Username, Role, Desc) when is_binary(Username) ->
|
update_user(Username, Role, Desc) when is_binary(Username) ->
|
||||||
case legal_role(Role) of
|
case legal_role(Role) of
|
||||||
ok ->
|
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 ->
|
||||||
Error
|
Error
|
||||||
end.
|
end.
|
||||||
|
@ -258,7 +272,7 @@ update_user_(Username, Role, Desc) ->
|
||||||
mnesia:abort(<<"username_not_found">>);
|
mnesia:abort(<<"username_not_found">>);
|
||||||
[Admin] ->
|
[Admin] ->
|
||||||
mnesia:write(Admin#?ADMIN{role = Role, description = Desc}),
|
mnesia:write(Admin#?ADMIN{role = Role, description = Desc}),
|
||||||
#{username => Username, role => Role, description => Desc}
|
{role(Admin) =:= Role, #{username => Username, role => Role, description => Desc}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) ->
|
change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) ->
|
||||||
|
@ -381,8 +395,9 @@ add_default_user(Username, Password) ->
|
||||||
_ -> {ok, default_user_exists}
|
_ -> {ok, default_user_exists}
|
||||||
end.
|
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`
|
%% this value in old data is `undefined`
|
||||||
|
-dialyzer({no_match, ensure_role/1}).
|
||||||
ensure_role(undefined) ->
|
ensure_role(undefined) ->
|
||||||
?ROLE_SUPERUSER;
|
?ROLE_SUPERUSER;
|
||||||
ensure_role(Role) when is_binary(Role) ->
|
ensure_role(Role) when is_binary(Role) ->
|
||||||
|
@ -392,6 +407,9 @@ ensure_role(Role) when is_binary(Role) ->
|
||||||
legal_role(Role) ->
|
legal_role(Role) ->
|
||||||
emqx_dashboard_rbac:legal_role(Role).
|
emqx_dashboard_rbac:legal_role(Role).
|
||||||
|
|
||||||
|
role(Data) ->
|
||||||
|
emqx_dashboard_rbac:role(Data).
|
||||||
|
|
||||||
-else.
|
-else.
|
||||||
|
|
||||||
-dialyzer({no_match, [add_user/4, update_user/3]}).
|
-dialyzer({no_match, [add_user/4, update_user/3]}).
|
||||||
|
@ -399,6 +417,8 @@ legal_role(Role) ->
|
||||||
legal_role(_) ->
|
legal_role(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
role(_) ->
|
||||||
|
?ROLE_DEFAULT.
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
|
|
|
@ -117,7 +117,7 @@ do_sign(#?ADMIN{username = Username} = User, Password) ->
|
||||||
},
|
},
|
||||||
Signed = jose_jwt:sign(JWK, JWS, JWT),
|
Signed = jose_jwt:sign(JWK, JWS, JWT),
|
||||||
{_, Token} = jose_jws:compact(Signed),
|
{_, Token} = jose_jws:compact(Signed),
|
||||||
Role = role(User),
|
Role = emqx_dashboard_admin:role(User),
|
||||||
JWTRec = format(Token, Username, Role, ExpTime),
|
JWTRec = format(Token, Username, Role, ExpTime),
|
||||||
_ = mria:transaction(?DASHBOARD_SHARD, fun mnesia:write/1, [JWTRec]),
|
_ = mria:transaction(?DASHBOARD_SHARD, fun mnesia:write/1, [JWTRec]),
|
||||||
{ok, Token}.
|
{ok, Token}.
|
||||||
|
@ -249,9 +249,6 @@ clean_expired_jwt(Now) ->
|
||||||
check_rbac(Req, Extra) ->
|
check_rbac(Req, Extra) ->
|
||||||
emqx_dashboard_rbac:check_rbac(Req, Extra).
|
emqx_dashboard_rbac:check_rbac(Req, Extra).
|
||||||
|
|
||||||
role(Data) ->
|
|
||||||
emqx_dashboard_rbac:role(Data).
|
|
||||||
|
|
||||||
-else.
|
-else.
|
||||||
|
|
||||||
-dialyzer({nowarn_function, [check_rbac/2]}).
|
-dialyzer({nowarn_function, [check_rbac/2]}).
|
||||||
|
@ -260,7 +257,4 @@ role(Data) ->
|
||||||
check_rbac(_Req, _Extra) ->
|
check_rbac(_Req, _Extra) ->
|
||||||
true.
|
true.
|
||||||
|
|
||||||
role(_) ->
|
|
||||||
?ROLE_DEFAULT.
|
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
|
@ -119,10 +119,11 @@ t_rest_api(_Config) ->
|
||||||
{ok, 200, Res0} = http_get(["users"]),
|
{ok, 200, Res0} = http_get(["users"]),
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
[
|
[
|
||||||
#{
|
filter_req(#{
|
||||||
<<"username">> => <<"admin">>,
|
<<"username">> => <<"admin">>,
|
||||||
<<"description">> => <<"administrator">>
|
<<"description">> => <<"administrator">>,
|
||||||
}
|
<<"role">> => ?ROLE_SUPERUSER
|
||||||
|
})
|
||||||
],
|
],
|
||||||
get_http_data(Res0)
|
get_http_data(Res0)
|
||||||
),
|
),
|
||||||
|
|
|
@ -175,7 +175,7 @@ t_clean_token(_) ->
|
||||||
NewPassword = <<"public_www2">>,
|
NewPassword = <<"public_www2">>,
|
||||||
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, ?ROLE_SUPERUSER, <<"desc">>),
|
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, ?ROLE_SUPERUSER, <<"desc">>),
|
||||||
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
|
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
|
||||||
FakeReq = #{method => <<"get">>},
|
FakeReq = #{method => <<"GET">>},
|
||||||
ok = emqx_dashboard_admin:verify_token(FakeReq, Token),
|
ok = emqx_dashboard_admin:verify_token(FakeReq, Token),
|
||||||
%% change password
|
%% change password
|
||||||
{ok, _} = emqx_dashboard_admin:change_password(Username, Password, NewPassword),
|
{ok, _} = emqx_dashboard_admin:change_password(Username, Password, NewPassword),
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
request/2,
|
request/2,
|
||||||
request/3,
|
request/3,
|
||||||
request/4,
|
request/4,
|
||||||
|
request/5,
|
||||||
multipart_formdata_request/3,
|
multipart_formdata_request/3,
|
||||||
multipart_formdata_request/4,
|
multipart_formdata_request/4,
|
||||||
host/0,
|
host/0,
|
||||||
|
@ -73,6 +74,9 @@ request(Method, Url, Body) ->
|
||||||
request(<<"admin">>, Method, Url, Body).
|
request(<<"admin">>, Method, Url, Body).
|
||||||
|
|
||||||
request(Username, Method, Url, Body) ->
|
request(Username, Method, Url, Body) ->
|
||||||
|
request(Username, <<"public">>, Method, Url, Body).
|
||||||
|
|
||||||
|
request(Username, Password, Method, Url, Body) ->
|
||||||
Request =
|
Request =
|
||||||
case Body of
|
case Body of
|
||||||
[] when
|
[] when
|
||||||
|
@ -80,9 +84,10 @@ request(Username, Method, Url, Body) ->
|
||||||
Method =:= head orelse Method =:= delete orelse
|
Method =:= head orelse Method =:= delete orelse
|
||||||
Method =:= trace
|
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,
|
end,
|
||||||
ct:pal("Method: ~p, Request: ~p", [Method, Request]),
|
ct:pal("Method: ~p, Request: ~p", [Method, Request]),
|
||||||
case httpc:request(Method, Request, [], [{body_format, binary}]) of
|
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])).
|
Host ++ "/" ++ to_list(filename:join([?BASE_PATH, ?API_VERSION | NParts])).
|
||||||
|
|
||||||
auth_header(Username) ->
|
auth_header(Username) ->
|
||||||
Password = <<"public">>,
|
auth_header(Username, <<"public">>).
|
||||||
|
|
||||||
|
auth_header(Username, Password) ->
|
||||||
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
|
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
|
||||||
{"Authorization", "Bearer " ++ binary_to_list(Token)}.
|
{"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},
|
{:ekka, github: "emqx/ekka", tag: "0.15.13", override: true},
|
||||||
{:gen_rpc, github: "emqx/gen_rpc", tag: "2.8.1", 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},
|
{: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},
|
{:ecpool, github: "emqx/ecpool", tag: "0.5.4", override: true},
|
||||||
{:replayq, github: "emqx/replayq", tag: "0.3.7", override: true},
|
{:replayq, github: "emqx/replayq", tag: "0.3.7", override: true},
|
||||||
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},
|
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},
|
||||||
|
|
Loading…
Reference in New Issue