Fix dashboard APIs return (#6177)

* fix(dashboard_api): delete non-exist user wrongly return 204

* fix(dashboard): dashboard user should use `tags` not `tag`

* fix(dashboard): create/update user return 200 with full users list

* fix(dashboard): logout status code 204

* fix(dashboard): update pwd status code 204

* test: test suite for dashboard APIs

* refactor(dashboard): user info mnesia record name use description

* style: make elvis happy

* fix(api): dashboard swagger check request should not override env

* fix(dashboard): add/modify dashboard returns single record

* ci: update emqx-fvt version to new tag 1.0.2-dev1
This commit is contained in:
JimMoen 2021-11-18 14:36:12 +08:00 committed by GitHub
parent 14da850e06
commit e361cd5733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 199 additions and 138 deletions

View File

@ -61,7 +61,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
repository: emqx/emqx-fvt repository: emqx/emqx-fvt
ref: v1.5.0 ref: 1.0.2-dev1
path: . path: .
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:

View File

@ -57,15 +57,14 @@ do_request_api(Method, Request, HttpOpts) ->
case httpc:request(Method, Request, HttpOpts, [{body_format, binary}]) of case httpc:request(Method, Request, HttpOpts, [{body_format, binary}]) of
{error, socket_closed_remotely} -> {error, socket_closed_remotely} ->
{error, socket_closed_remotely}; {error, socket_closed_remotely};
{ok, {{"HTTP/1.1", Code, _}, _Headers, Return} } {ok, {{"HTTP/1.1", Code, _}, _Headers, Return} } ->
when Code =:= 200 orelse Code =:= 201 -> {ok, Code, Return};
{ok, Return};
{ok, {Reason, _, _}} -> {ok, {Reason, _, _}} ->
{error, Reason} {error, Reason}
end. end.
get_http_data(ResponseBody) -> get_http_data(ResponseBody) ->
maps:get(<<"data">>, emqx_json:decode(ResponseBody, [return_maps])). emqx_json:decode(ResponseBody, [return_maps]).
auth_header(User, Pass) -> auth_header(User, Pass) ->
Encoded = base64:encode_to_string(lists:append([User,":",Pass])), Encoded = base64:encode_to_string(lists:append([User,":",Pass])),

View File

@ -18,7 +18,7 @@
-record(?ADMIN, { -record(?ADMIN, {
username :: binary(), username :: binary(),
pwdhash :: binary(), pwdhash :: binary(),
tags :: list() | binary(), description :: binary(),
role = undefined :: atom(), role = undefined :: atom(),
extra = [] :: term() %% not used so far, for future extension extra = [] :: term() %% not used so far, for future extension
}). }).

View File

@ -40,7 +40,7 @@ start_listeners() ->
Authorization = {?MODULE, authorize_appid}, Authorization = {?MODULE, authorize_appid},
GlobalSpec = #{ GlobalSpec = #{
openapi => "3.0.0", openapi => "3.0.0",
info => #{title => "EMQ X Dashboard API", version => "5.0.0"}, info => #{title => "EMQ X API", version => "5.0.0"},
servers => [#{url => ?BASE_PATH}], servers => [#{url => ?BASE_PATH}],
components => #{ components => #{
schemas => #{}, schemas => #{},

View File

@ -19,6 +19,7 @@
-module(emqx_dashboard_admin). -module(emqx_dashboard_admin).
-include("emqx_dashboard.hrl"). -include("emqx_dashboard.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
@ -63,17 +64,17 @@ mnesia(boot) ->
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(add_user(binary(), binary(), binary()) -> ok | {error, any()}). -spec(add_user(binary(), binary(), binary()) -> {ok, map()} | {error, any()}).
add_user(Username, Password, Tags) when is_binary(Username), is_binary(Password) -> add_user(Username, Password, Desc)
Admin = #?ADMIN{username = Username, pwdhash = hash(Password), tags = Tags}, when is_binary(Username), is_binary(Password) ->
return(mria:transaction(?DASHBOARD_SHARD, fun add_user_/1, [Admin])). return(mria:transaction(?DASHBOARD_SHARD, fun add_user_/3, [Username, Password, Desc])).
%% black-magic: force overwrite a user %% black-magic: force overwrite a user
force_add_user(Username, Password, Tags) -> force_add_user(Username, Password, Desc) ->
AddFun = fun() -> AddFun = fun() ->
mnesia:write(#?ADMIN{username = Username, mnesia:write(#?ADMIN{username = Username,
pwdhash = hash(Password), pwdhash = hash(Password),
tags = Tags}) description = Desc})
end, end,
case mria:transaction(?DASHBOARD_SHARD, AddFun) of case mria:transaction(?DASHBOARD_SHARD, AddFun) of
{atomic, ok} -> ok; {atomic, ok} -> ok;
@ -81,33 +82,38 @@ force_add_user(Username, Password, Tags) ->
end. end.
%% @private %% @private
add_user_(Admin = #?ADMIN{username = Username}) -> add_user_(Username, Password, Desc) ->
case mnesia:wread({?ADMIN, Username}) of case mnesia:wread({?ADMIN, Username}) of
[] -> mnesia:write(Admin); [] ->
[_] -> mnesia:abort(<<"Username Already Exist">>) Admin = #?ADMIN{username = Username, pwdhash = hash(Password), description = Desc},
mnesia:write(Admin),
#{username => Username, description => Desc};
[_] ->
mnesia:abort(<<"Username Already Exist">>)
end. end.
-spec(remove_user(binary()) -> ok | {error, any()}). -spec(remove_user(binary()) -> {ok, any()} | {error, any()}).
remove_user(Username) when is_binary(Username) -> remove_user(Username) when is_binary(Username) ->
Trans = fun() -> Trans = fun() ->
case lookup_user(Username) of case lookup_user(Username) of
[] -> [] -> mnesia:abort(<<"Username Not Found">>);
mnesia:abort(<<"Username Not Found">>); _ -> mnesia:delete({?ADMIN, Username})
_ -> ok end
end,
mnesia:delete({?ADMIN, Username})
end, end,
return(mria:transaction(?DASHBOARD_SHARD, Trans)). return(mria:transaction(?DASHBOARD_SHARD, Trans)).
-spec(update_user(binary(), binary()) -> ok | {error, term()}). -spec(update_user(binary(), binary()) -> {ok, map()} | {error, term()}).
update_user(Username, Tags) when is_binary(Username) -> update_user(Username, Desc) when is_binary(Username) ->
return(mria:transaction(?DASHBOARD_SHARD, fun update_user_/2, [Username, Tags])). return(mria:transaction(?DASHBOARD_SHARD, fun update_user_/2, [Username, Desc])).
%% @private %% @private
update_user_(Username, Tags) -> update_user_(Username, Desc) ->
case mnesia:wread({?ADMIN, Username}) of case mnesia:wread({?ADMIN, Username}) of
[] -> mnesia:abort(<<"Username Not Found">>); [] ->
[Admin] -> mnesia:write(Admin#?ADMIN{tags = Tags}) mnesia:abort(<<"Username Not Found">>);
[Admin] ->
mnesia:write(Admin#?ADMIN{description = Desc}),
#{username => Username, description => Desc}
end. end.
change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) -> change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) ->
@ -146,17 +152,15 @@ lookup_user(Username) when is_binary(Username) ->
-spec(all_users() -> [map()]). -spec(all_users() -> [map()]).
all_users() -> all_users() ->
lists:map(fun(#?ADMIN{username = Username, lists:map(fun(#?ADMIN{username = Username,
tags = Tags description = Desc
}) -> }) ->
#{username => Username, #{username => Username,
%% named tag but not tags, for unknown reason description => Desc
%% TODO: fix this comment
tag => Tags
} }
end, ets:tab2list(?ADMIN)). end, ets:tab2list(?ADMIN)).
return({atomic, _}) -> return({atomic, Result}) ->
ok; {ok, Result};
return({aborted, Reason}) -> return({aborted, Reason}) ->
{error, Reason}. {error, Reason}.
@ -219,5 +223,5 @@ add_default_user(Username, Password) when ?EMPTY_KEY(Username) orelse ?EMPTY_KEY
add_default_user(Username, Password) -> add_default_user(Username, Password) ->
case lookup_user(Username) of case lookup_user(Username) of
[] -> add_user(Username, Password, <<"administrator">>); [] -> add_user(Username, Password, <<"administrator">>);
_ -> ok _ -> {ok, default_user_exists}
end. end.

View File

@ -37,14 +37,21 @@
-define(EMPTY(V), (V == undefined orelse V == <<>>)). -define(EMPTY(V), (V == undefined orelse V == <<>>)).
-define(ERROR_USERNAME_OR_PWD, 'ERROR_USERNAME_OR_PWD'). -define(ERROR_USERNAME_OR_PWD, 'ERROR_USERNAME_OR_PWD').
-define(USER_NOT_FOUND_BODY, #{ code => <<"USER_NOT_FOUND">>
, message => <<"User not found">>}).
namespace() -> "dashboard". namespace() -> "dashboard".
api_spec() -> api_spec() ->
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}). emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}).
paths() -> ["/login", "/logout", "/users", paths() ->
"/users/:username", "/users/:username/change_pwd"]. [ "/login"
, "/logout"
, "/users"
, "/users/:username"
, "/users/:username/change_pwd"].
schema("/login") -> schema("/login") ->
#{ #{
@ -53,8 +60,7 @@ schema("/login") ->
tags => [<<"dashboard">>], tags => [<<"dashboard">>],
description => <<"Dashboard Auth">>, description => <<"Dashboard Auth">>,
summary => <<"Dashboard Auth">>, summary => <<"Dashboard Auth">>,
'requestBody' => 'requestBody' => [
[
{username, mk(binary(), {username, mk(binary(),
#{desc => <<"The User for which to create the token.">>, #{desc => <<"The User for which to create the token.">>,
'maxLength' => 100, example => <<"admin">>})}, 'maxLength' => 100, example => <<"admin">>})},
@ -67,10 +73,12 @@ schema("/login") ->
{license, [{edition, {license, [{edition,
mk(enum([community, enterprise]), #{desc => <<"license">>, mk(enum([community, enterprise]), #{desc => <<"license">>,
example => "community"})}]}, example => "community"})}]},
{version, mk(string(), #{desc => <<"version">>, example => <<"5.0.0">>})}], {version, mk(string(), #{desc => <<"version">>, example => <<"5.0.0">>})}
],
401 => [ 401 => [
{code, mk(string(), #{example => 'ERROR_USERNAME_OR_PWD'})}, {code, mk(string(), #{example => 'ERROR_USERNAME_OR_PWD'})},
{message, mk(string(), #{example => "Unauthorized"})}] {message, mk(string(), #{example => "Unauthorized"})}
]
}, },
security => [] security => []
}}; }};
@ -86,7 +94,7 @@ schema("/logout") ->
'maxLength' => 100, example => <<"admin">>})} 'maxLength' => 100, example => <<"admin">>})}
], ],
responses => #{ responses => #{
200 => <<"Dashboard logout successfully">> 204 => <<"Dashboard logout successfully">>
} }
} }
}; };
@ -95,10 +103,10 @@ schema("/users") ->
'operationId' => users, 'operationId' => users,
get => #{ get => #{
tags => [<<"dashboard">>], tags => [<<"dashboard">>],
description => <<"Get dashboard users">>, description => <<"Get dashboard users list">>,
responses => #{ responses => #{
200 => mk(array(ref(?MODULE, user)), 200 => mk( array(ref(?MODULE, user))
#{desc => "User lists"}) , #{desc => "User lists"})
} }
}, },
post => #{ post => #{
@ -106,9 +114,12 @@ schema("/users") ->
description => <<"Create dashboard users">>, description => <<"Create dashboard users">>,
'requestBody' => fields(user_password), 'requestBody' => fields(user_password),
responses => #{ responses => #{
200 => <<"Create user successfully">>, 200 => mk( ref(?MODULE, user)
, #{desc => <<"Create User successfully">>}),
400 => [{code, mk(string(), #{example => 'CREATE_FAIL'})}, 400 => [{code, mk(string(), #{example => 'CREATE_FAIL'})},
{message, mk(string(), #{example => "Create user failed"})}]} {message, mk(string(), #{example => "Create user failed"})}
]
}
} }
}; };
@ -120,11 +131,20 @@ schema("/users/:username") ->
description => <<"Update dashboard users">>, description => <<"Update dashboard users">>,
parameters => [{username, mk(binary(), parameters => [{username, mk(binary(),
#{in => path, example => <<"admin">>})}], #{in => path, example => <<"admin">>})}],
'requestBody' => [{tag, mk(binary(), #{desc => <<"Tag">>})}], 'requestBody' => [
{ description
, mk(binary(), #{desc => <<"User description">>, example => <<"administrator">>})}
],
responses => #{ responses => #{
200 => <<"Update User successfully">>, 200 => mk( ref(?MODULE, user)
400 => [{code, mk(string(), #{example => 'UPDATE_FAIL'})}, , #{desc => <<"Update User successfully">>}),
{message, mk(string(), #{example => "Update Failed unknown"})}]}}, 400 => [
{code, mk(string(), #{example => 'UPDATE_FAIL'})},
{message, mk(string(), #{example => "Update Failed unknown"})}
],
404 => emqx_dashboard_swagger:error_codes(['USER_NOT_FOUND'], <<"User Not Found">>)
}
},
delete => #{ delete => #{
tags => [<<"dashboard">>], tags => [<<"dashboard">>],
description => <<"Delete dashboard users">>, description => <<"Delete dashboard users">>,
@ -134,7 +154,11 @@ schema("/users/:username") ->
204 => <<"Delete User successfully">>, 204 => <<"Delete User successfully">>,
400 => [ 400 => [
{code, mk(string(), #{example => 'CANNOT_DELETE_ADMIN'})}, {code, mk(string(), #{example => 'CANNOT_DELETE_ADMIN'})},
{message, mk(string(), #{example => "CANNOT DELETE ADMIN"})}]}} {message, mk(string(), #{example => "CANNOT DELETE ADMIN"})}
],
404 => emqx_dashboard_swagger:error_codes(['USER_NOT_FOUND'], <<"User Not Found">>)
}
}
}; };
schema("/users/:username/change_pwd") -> schema("/users/:username/change_pwd") ->
#{ #{
@ -149,23 +173,26 @@ schema("/users/:username/change_pwd") ->
{new_pwd, mk(binary(), #{required => true})} {new_pwd, mk(binary(), #{required => true})}
], ],
responses => #{ responses => #{
200 => <<"Update user password successfully">>, 204 => <<"Update user password successfully">>,
400 => [ 400 => [
{code, mk(string(), #{example => 'UPDATE_FAIL'})}, {code, mk(string(), #{example => 'UPDATE_FAIL'})},
{message, mk(string(), #{example => "Failed Reason"})}]}} {message, mk(string(), #{example => "Failed Reason"})}
]
}
}
}. }.
fields(user) -> fields(user) ->
[ [
{tag, {description,
mk(binary(), mk(binary(),
#{desc => <<"tag">>, example => "administrator"})}, #{desc => <<"User description">>, example => "administrator"})},
{username, {username,
mk(binary(), mk(binary(),
#{desc => <<"username">>, example => "emqx"})} #{desc => <<"username">>, example => "emqx"})}
]; ];
fields(user_password) -> fields(user_password) ->
fields(user) ++ [{password, mk(binary(), #{desc => "Password"})}]. fields(user) ++ [{password, mk(binary(), #{desc => "Password", example => <<"public">>})}].
login(post, #{body := Params}) -> login(post, #{body := Params}) ->
Username = maps:get(<<"username">>, Params), Username = maps:get(<<"username">>, Params),
@ -182,7 +209,7 @@ logout(_, #{body := #{<<"username">> := Username},
headers := #{<<"authorization">> := <<"Bearer ", Token/binary>>}}) -> headers := #{<<"authorization">> := <<"Bearer ", Token/binary>>}}) ->
case emqx_dashboard_admin:destroy_token_by_username(Username, Token) of case emqx_dashboard_admin:destroy_token_by_username(Username, Token) of
ok -> ok ->
200; 204;
_R -> _R ->
{401, 'BAD_TOKEN_OR_USERNAME', <<"Ensure your token & username">>} {401, 'BAD_TOKEN_OR_USERNAME', <<"Ensure your token & username">>}
end. end.
@ -191,7 +218,7 @@ users(get, _Request) ->
{200, emqx_dashboard_admin:all_users()}; {200, emqx_dashboard_admin:all_users()};
users(post, #{body := Params}) -> users(post, #{body := Params}) ->
Tag = maps:get(<<"tag">>, Params), Desc = maps:get(<<"description">>, Params),
Username = maps:get(<<"username">>, Params), Username = maps:get(<<"username">>, Params),
Password = maps:get(<<"password">>, Params), Password = maps:get(<<"password">>, Params),
case ?EMPTY(Username) orelse ?EMPTY(Password) of case ?EMPTY(Username) orelse ?EMPTY(Password) of
@ -199,35 +226,43 @@ users(post, #{body := Params}) ->
{400, #{code => <<"CREATE_USER_FAIL">>, {400, #{code => <<"CREATE_USER_FAIL">>,
message => <<"Username or password undefined">>}}; message => <<"Username or password undefined">>}};
false -> false ->
case emqx_dashboard_admin:add_user(Username, Password, Tag) of case emqx_dashboard_admin:add_user(Username, Password, Desc) of
ok -> {200}; {ok, Result} ->
{200, Result};
{error, Reason} -> {error, Reason} ->
{400, #{code => <<"CREATE_USER_FAIL">>, message => Reason}} {400, #{code => <<"CREATE_USER_FAIL">>, message => Reason}}
end end
end. end.
user(put, #{bindings := #{username := Username}, body := Params}) -> user(put, #{bindings := #{username := Username}, body := Params}) ->
Tag = maps:get(<<"tag">>, Params), Desc = maps:get(<<"description">>, Params),
case emqx_dashboard_admin:update_user(Username, Tag) of case emqx_dashboard_admin:update_user(Username, Desc) of
ok -> {200}; {ok, Result} ->
{error, Reason} -> {200, Result};
{400, #{code => <<"UPDATE_FAIL">>, message => Reason}} {error, _Reason} ->
{404, ?USER_NOT_FOUND_BODY}
end; end;
user(delete, #{bindings := #{username := Username}}) -> user(delete, #{bindings := #{username := Username}}) ->
case Username == <<"admin">> of case Username == <<"admin">> of
true -> {400, #{code => <<"CANNOT_DELETE_ADMIN">>, true ->
{400, #{code => <<"ACTION_NOT_ALLOWED">>,
message => <<"Cannot delete admin">>}}; message => <<"Cannot delete admin">>}};
false -> false ->
_ = emqx_dashboard_admin:remove_user(Username), case emqx_dashboard_admin:remove_user(Username) of
{error, _Reason} ->
{404, ?USER_NOT_FOUND_BODY};
{ok, _} ->
{204} {204}
end
end. end.
change_pwd(put, #{bindings := #{username := Username}, body := Params}) -> change_pwd(put, #{bindings := #{username := Username}, body := Params}) ->
OldPwd = maps:get(<<"old_pwd">>, Params), OldPwd = maps:get(<<"old_pwd">>, Params),
NewPwd = maps:get(<<"new_pwd">>, Params), NewPwd = maps:get(<<"new_pwd">>, Params),
case emqx_dashboard_admin:change_password(Username, OldPwd, NewPwd) of case emqx_dashboard_admin:change_password(Username, OldPwd, NewPwd) of
ok -> {200}; {ok, _} ->
{204};
{error, Reason} -> {error, Reason} ->
{400, #{code => <<"CHANGE_PWD_FAIL">>, message => Reason}} {400, #{code => <<"CHANGE_PWD_FAIL">>, message => Reason}}
end. end.

View File

@ -29,7 +29,7 @@ start(_StartType, _StartArgs) ->
ok = mria_rlog:wait_for_shards([?DASHBOARD_SHARD], infinity), ok = mria_rlog:wait_for_shards([?DASHBOARD_SHARD], infinity),
_ = emqx_dashboard:start_listeners(), _ = emqx_dashboard:start_listeners(),
emqx_dashboard_cli:load(), emqx_dashboard_cli:load(),
ok = emqx_dashboard_admin:add_default_user(), {ok, _Result} = emqx_dashboard_admin:add_default_user(),
{ok, Sup}. {ok, Sup}.
stop(_State) -> stop(_State) ->

View File

@ -27,9 +27,9 @@ load() ->
admins(["add", Username, Password]) -> admins(["add", Username, Password]) ->
admins(["add", Username, Password, ""]); admins(["add", Username, Password, ""]);
admins(["add", Username, Password, Tag]) -> admins(["add", Username, Password, Desc]) ->
case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Tag)) of case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Desc)) of
ok -> {ok, _} ->
emqx_ctl:print("ok~n"); emqx_ctl:print("ok~n");
{error, already_existed} -> {error, already_existed} ->
emqx_ctl:print("Error: already existed~n"); emqx_ctl:print("Error: already existed~n");
@ -46,7 +46,8 @@ admins(["del", Username]) ->
emqx_ctl:print("~p~n", [Status]); emqx_ctl:print("~p~n", [Status]);
admins(_) -> admins(_) ->
emqx_ctl:usage([{"admins add <Username> <Password> <Tags>", "Add dashboard user"}, emqx_ctl:usage(
[{"admins add <Username> <Password> <Description>", "Add dashboard user"},
{"admins passwd <Username> <Password>", "Reset dashboard user password"}, {"admins passwd <Username> <Password>", "Reset dashboard user password"},
{"admins del <Username>", "Delete dashboard user" }]). {"admins del <Username>", "Delete dashboard user" }]).

View File

@ -213,7 +213,7 @@ check_request_body(#{body := Body}, Schema, Module, CheckFun, true) ->
check_request_body(#{body := Body}, Spec, _Module, CheckFun, false) -> check_request_body(#{body := Body}, Spec, _Module, CheckFun, false) ->
lists:foldl(fun({Name, Type}, Acc) -> lists:foldl(fun({Name, Type}, Acc) ->
Schema = ?INIT_SCHEMA#{roots => [{Name, Type}]}, Schema = ?INIT_SCHEMA#{roots => [{Name, Type}]},
maps:merge(Acc, CheckFun(Schema, Body, #{})) maps:merge(Acc, CheckFun(Schema, Body, #{override_env => false}))
end, #{}, Spec). end, #{}, Spec).
%% tags, description, summary, security, deprecated %% tags, description, summary, security, deprecated

View File

@ -31,11 +31,14 @@
-define(CONTENT_TYPE, "application/x-www-form-urlencoded"). -define(CONTENT_TYPE, "application/x-www-form-urlencoded").
-define(HOST, "http://127.0.0.1:18083/"). -define(HOST, "http://127.0.0.1:18083").
-define(API_VERSION, "v4"). %% -define(API_VERSION, "v5").
-define(BASE_PATH, "api"). -define(BASE_PATH, "/api/v5").
-define(APP_DASHBOARD, emqx_dashboard).
-define(APP_MANAGEMENT, emqx_management).
-define(OVERVIEWS, ['alarms/activated', -define(OVERVIEWS, ['alarms/activated',
'alarms/deactivated', 'alarms/deactivated',
@ -52,8 +55,23 @@
all() -> all() ->
%% TODO: V5 API %% TODO: V5 API
% emqx_common_test_helpers:all(?MODULE). %% emqx_common_test_helpers:all(?MODULE).
[t_cli, t_lookup_by_username_jwt, t_clean_expired_jwt]. [t_cli, t_lookup_by_username_jwt, t_clean_expired_jwt, t_rest_api].
init_suite() ->
init_suite([]).
init_suite(Apps) ->
mria:start(),
application:load(emqx_management),
emqx_common_test_helpers:start_apps(Apps ++ [emqx_dashboard], fun set_special_configs/1).
end_suite() ->
end_suite([]).
end_suite(Apps) ->
application:unload(emqx_management),
emqx_common_test_helpers:stop_apps(Apps ++ [emqx_dashboard]).
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_common_test_helpers:start_apps([emqx_management, emqx_dashboard], emqx_common_test_helpers:start_apps([emqx_management, emqx_dashboard],
@ -66,23 +84,32 @@ end_per_suite(_Config) ->
set_special_configs(emqx_management) -> set_special_configs(emqx_management) ->
Listeners = [#{protocol => http, port => 8081}], Listeners = [#{protocol => http, port => 8081}],
emqx_config:put([emqx_management], #{listeners => Listeners, Config = #{listeners => Listeners,
applications =>[#{id => "admin", secret => "public"}]}), applications => [#{id => "admin", secret => "public"}]},
emqx_config:put([emqx_management], Config),
ok;
set_special_configs(emqx_dashboard) ->
Listeners = [#{protocol => http, port => 18083}],
Config = #{listeners => Listeners,
default_username => <<"admin">>,
default_password => <<"public">>
},
emqx_config:put([emqx_dashboard], Config),
ok; ok;
set_special_configs(_) -> set_special_configs(_) ->
ok. ok.
t_overview(_) -> t_overview(_) ->
mnesia:clear_table(?ADMIN), mnesia:clear_table(?ADMIN),
emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, <<"tag">>), emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, <<"simple_description">>),
[?assert(request_dashboard(get, api_path(erlang:atom_to_list(Overview)), [?assert(request_dashboard(get, api_path(erlang:atom_to_list(Overview)),
auth_header_())) || Overview <- ?OVERVIEWS]. auth_header_())) || Overview <- ?OVERVIEWS].
t_admins_add_delete(_) -> t_admins_add_delete(_) ->
mnesia:clear_table(?ADMIN), mnesia:clear_table(?ADMIN),
Tag = <<"tag">>, Desc = <<"simple description">>,
ok = emqx_dashboard_admin:add_user(<<"username">>, <<"password">>, Tag), ok = emqx_dashboard_admin:add_user(<<"username">>, <<"password">>, Desc),
ok = emqx_dashboard_admin:add_user(<<"username1">>, <<"password1">>, Tag), ok = emqx_dashboard_admin:add_user(<<"username1">>, <<"password1">>, Desc),
Admins = emqx_dashboard_admin:all_users(), Admins = emqx_dashboard_admin:all_users(),
?assertEqual(2, length(Admins)), ?assertEqual(2, length(Admins)),
ok = emqx_dashboard_admin:remove_user(<<"username1">>), ok = emqx_dashboard_admin:remove_user(<<"username1">>),
@ -92,7 +119,7 @@ t_admins_add_delete(_) ->
<<"password">>, <<"password">>,
<<"pwd">>), <<"pwd">>),
timer:sleep(10), timer:sleep(10),
Header = auth_header_("username", "pwd"), Header = auth_header_(<<"username">>, <<"pwd">>),
?assert(request_dashboard(get, api_path("brokers"), Header)), ?assert(request_dashboard(get, api_path("brokers"), Header)),
ok = emqx_dashboard_admin:remove_user(<<"username">>), ok = emqx_dashboard_admin:remove_user(<<"username">>),
@ -100,28 +127,22 @@ t_admins_add_delete(_) ->
t_rest_api(_Config) -> t_rest_api(_Config) ->
mnesia:clear_table(?ADMIN), mnesia:clear_table(?ADMIN),
Tag = <<"administrator">>, Desc = <<"administrator">>,
emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, Tag), emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, Desc),
{ok, Res0} = http_get("users"), {ok, 200, Res0} = http_get(["users"]),
?assertEqual([#{<<"username">> => <<"admin">>, ?assertEqual([#{<<"username">> => <<"admin">>,
<<"tags">> => <<"administrator">>}], get_http_data(Res0)), <<"description">> => <<"administrator">>}], get_http_data(Res0)),
{ok, 200, _} = http_put(["users", "admin"], #{<<"description">> => <<"a_new_description">>}),
AssertSuccess = fun({ok, Res}) -> {ok, 200, _} = http_post(["users"], #{<<"username">> => <<"usera">>,
?assertEqual(#{<<"code">> => 0}, json(Res)) <<"password">> => <<"passwd">>,
end, <<"description">> => Desc}),
[AssertSuccess(R) {ok, 204, _} = http_delete(["users", "usera"]),
|| R <- [ http_put("users/admin", #{<<"tags">> => <<"a_new_tag">>}) {ok, 404, _} = http_delete(["users", "usera"]),
, http_post("users", #{<<"username">> => <<"usera">>, {ok, 204, _} = http_put( ["users", "admin", "change_pwd"]
<<"password">> => <<"passwd">>}) , #{<<"old_pwd">> => <<"public">>,
, http_post("auth", #{<<"username">> => <<"usera">>, <<"new_pwd">> => <<"newpwd">>}),
<<"password">> => <<"passwd">>}) mnesia:clear_table(?ADMIN),
, http_delete("users/usera") emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, <<"administrator">>),
, http_put("users/admin/change_pwd", #{<<"old_pwd">> => <<"public">>,
<<"new_pwd">> => <<"newpwd">>})
, http_post("auth", #{<<"username">> => <<"admin">>,
<<"password">> => <<"newpwd">>})
]],
ok. ok.
t_cli(_Config) -> t_cli(_Config) ->
@ -175,17 +196,17 @@ bin(X) -> iolist_to_binary(X).
random_num() -> random_num() ->
erlang:system_time(nanosecond). erlang:system_time(nanosecond).
http_get(Path) -> http_get(Parts) ->
request_api(get, api_path(Path), auth_header_()). request_api(get, api_path(Parts), auth_header_()).
http_delete(Path) -> http_delete(Parts) ->
request_api(delete, api_path(Path), auth_header_()). request_api(delete, api_path(Parts), auth_header_()).
http_post(Path, Body) -> http_post(Parts, Body) ->
request_api(post, api_path(Path), [], auth_header_(), Body). request_api(post, api_path(Parts), [], auth_header_(), Body).
http_put(Path, Body) -> http_put(Parts, Body) ->
request_api(put, api_path(Path), [], auth_header_(), Body). request_api(put, api_path(Parts), [], auth_header_(), Body).
request_dashboard(Method, Url, Auth) -> request_dashboard(Method, Url, Auth) ->
Request = {Url, [Auth]}, Request = {Url, [Auth]},
@ -198,21 +219,22 @@ do_request_dashboard(Method, Request)->
case httpc:request(Method, Request, [], []) of case httpc:request(Method, Request, [], []) of
{error, socket_closed_remotely} -> {error, socket_closed_remotely} ->
{error, socket_closed_remotely}; {error, socket_closed_remotely};
{ok, {{"HTTP/1.1", 200, _}, _, _Return} } -> {ok, {{"HTTP/1.1", Code, _}, _Headers, Return} }
true; when Code >= 200 andalso Code =< 299 ->
{ok, Return};
{ok, {Reason, _, _}} -> {ok, {Reason, _, _}} ->
{error, Reason} {error, Reason}
end. end.
auth_header_() -> auth_header_() ->
auth_header_("admin", "public"). auth_header_(<<"admin">>, <<"public">>).
auth_header_(User, Pass) -> auth_header_(Username, Password) ->
Encoded = base64:encode_to_string(lists:append([User,":",Pass])), {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
{"Authorization","Basic " ++ Encoded}. {"Authorization","Bearer " ++ binary_to_list(Token)}.
api_path(Path) -> api_path(Parts) ->
?HOST ++ filename:join([?BASE_PATH, ?API_VERSION, Path]). ?HOST ++ filename:join([?BASE_PATH | Parts]).
json(Data) -> json(Data) ->
{ok, Jsx} = emqx_json:safe_decode(Data, [return_maps]), Jsx. {ok, Jsx} = emqx_json:safe_decode(Data, [return_maps]), Jsx.