Bladeren bron

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
JimMoen 4 jaren geleden
bovenliggende
commit
e361cd5733

+ 1 - 1
.github/workflows/run_api_tests.yaml

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

+ 11 - 12
apps/emqx/test/emqx_common_test_http.erl

@@ -19,14 +19,14 @@
 -include_lib("common_test/include/ct.hrl").
 -include_lib("common_test/include/ct.hrl").
 
 
 -export([ request_api/3
 -export([ request_api/3
-    , request_api/4
-    , request_api/5
-    , get_http_data/1
-    , create_default_app/0
-    , delete_default_app/0
-    , default_auth_header/0
-    , auth_header/2
-]).
+        , request_api/4
+        , request_api/5
+        , get_http_data/1
+        , create_default_app/0
+        , delete_default_app/0
+        , default_auth_header/0
+        , auth_header/2
+        ]).
 
 
 request_api(Method, Url, Auth) ->
 request_api(Method, Url, Auth) ->
     request_api(Method, Url, [], Auth, []).
     request_api(Method, Url, [], Auth, []).
@@ -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} }
-            when Code =:= 200 orelse Code =:= 201 ->
-            {ok, Return};
+        {ok, {{"HTTP/1.1", Code, _}, _Headers, Return} } ->
+            {ok, Code, 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])),

+ 1 - 1
apps/emqx_dashboard/include/emqx_dashboard.hrl

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

+ 1 - 1
apps/emqx_dashboard/src/emqx_dashboard.erl

@@ -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 => #{},

+ 32 - 28
apps/emqx_dashboard/src/emqx_dashboard_admin.erl

@@ -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()}).
-add_user(Username, Password, Tags) when is_binary(Username), is_binary(Password) ->
-    Admin = #?ADMIN{username = Username, pwdhash = hash(Password), tags = Tags},
-    return(mria:transaction(?DASHBOARD_SHARD, fun add_user_/1, [Admin])).
+-spec(add_user(binary(), binary(), binary()) -> {ok, map()} | {error, any()}).
+add_user(Username, Password, Desc)
+  when is_binary(Username), is_binary(Password) ->
+    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">>);
-                    _  -> ok
-                    end,
-                    mnesia:delete({?ADMIN, Username})
+                        [] -> mnesia:abort(<<"Username Not Found">>);
+                        _  -> mnesia:delete({?ADMIN, Username})
+                    end
             end,
             end,
     return(mria:transaction(?DASHBOARD_SHARD, Trans)).
     return(mria:transaction(?DASHBOARD_SHARD, Trans)).
 
 
--spec(update_user(binary(), binary()) -> ok | {error, term()}).
-update_user(Username, Tags) when is_binary(Username) ->
-    return(mria:transaction(?DASHBOARD_SHARD, fun update_user_/2, [Username, Tags])).
+-spec(update_user(binary(), binary()) -> {ok, map()} | {error, term()}).
+update_user(Username, Desc) when is_binary(Username) ->
+    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
-                        %% TODO: fix this comment
-                        tag => Tags
+                        description => Desc
                        }
                        }
               end, ets:tab2list(?ADMIN)).
               end, ets:tab2list(?ADMIN)).
 
 
-return({atomic, _}) ->
-    ok;
+return({atomic, Result}) ->
+    {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.

+ 71 - 36
apps/emqx_dashboard/src/emqx_dashboard_api.erl

@@ -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",
-    "/users/:username", "/users/:username/change_pwd"].
+paths() ->
+    [ "/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)),
-                    #{desc => "User lists"})
+                200 => mk( array(ref(?MODULE, user))
+                         , #{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">>,
-                400 => [{code, mk(string(), #{example => 'UPDATE_FAIL'})},
-                    {message, mk(string(), #{example => "Update Failed unknown"})}]}},
+                200 => mk( ref(?MODULE, user)
+                         , #{desc => <<"Update User successfully">>}),
+                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
-                ok -> {200};
+            case emqx_dashboard_admin:add_user(Username, Password, Desc) of
+                {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),
-    case emqx_dashboard_admin:update_user(Username, Tag) of
-        ok -> {200};
-        {error, Reason} ->
-            {400, #{code => <<"UPDATE_FAIL">>, message => Reason}}
+    Desc = maps:get(<<"description">>, Params),
+    case emqx_dashboard_admin:update_user(Username, Desc) of
+        {ok, Result} ->
+            {200, Result};
+        {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">>,
-            message => <<"Cannot delete admin">>}};
+        true ->
+            {400, #{code => <<"ACTION_NOT_ALLOWED">>,
+                    message => <<"Cannot delete admin">>}};
         false ->
         false ->
-            _ = emqx_dashboard_admin:remove_user(Username),
-            {204}
+            case emqx_dashboard_admin:remove_user(Username) of
+                {error, _Reason} ->
+                    {404, ?USER_NOT_FOUND_BODY};
+                {ok, _} ->
+                    {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.

+ 1 - 1
apps/emqx_dashboard/src/emqx_dashboard_app.erl

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

+ 7 - 6
apps/emqx_dashboard/src/emqx_dashboard_cli.erl

@@ -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]) ->
-    case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Tag)) of
-        ok ->
+admins(["add", Username, Password, Desc]) ->
+    case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Desc)) of
+        {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,9 +46,10 @@ 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"},
-                    {"admins passwd <Username> <Password>",      "Reset dashboard user password"},
-                    {"admins del <Username>",                    "Delete dashboard user" }]).
+    emqx_ctl:usage(
+      [{"admins add <Username> <Password> <Description>",  "Add dashboard user"},
+       {"admins passwd <Username> <Password>",             "Reset dashboard user password"},
+       {"admins del <Username>",                           "Delete dashboard user" }]).
 
 
 unload() ->
 unload() ->
     emqx_ctl:unregister_command(admins).
     emqx_ctl:unregister_command(admins).

+ 1 - 1
apps/emqx_dashboard/src/emqx_dashboard_swagger.erl

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

+ 73 - 51
apps/emqx_dashboard/test/emqx_dashboard_SUITE.erl

@@ -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',
@@ -51,9 +54,24 @@
                    ]).
                    ]).
 
 
 all() ->
 all() ->
-%%    TODO: V5 API
-%    emqx_common_test_helpers:all(?MODULE).
-    [t_cli, t_lookup_by_username_jwt, t_clean_expired_jwt].
+    %% TODO: V5 API
+    %% emqx_common_test_helpers:all(?MODULE).
+    [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,
-        applications =>[#{id => "admin", secret => "public"}]}),
+    Config = #{listeners => Listeners,
+               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">>,
-    ok = emqx_dashboard_admin:add_user(<<"username">>, <<"password">>, Tag),
-    ok = emqx_dashboard_admin:add_user(<<"username1">>, <<"password1">>, Tag),
+    Desc = <<"simple description">>,
+    ok = emqx_dashboard_admin:add_user(<<"username">>, <<"password">>, Desc),
+    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">>,
-    emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, Tag),
-    {ok, Res0} = http_get("users"),
-
+    Desc = <<"administrator">>,
+    emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, Desc),
+    {ok, 200, Res0} = http_get(["users"]),
     ?assertEqual([#{<<"username">> => <<"admin">>,
     ?assertEqual([#{<<"username">> => <<"admin">>,
-                    <<"tags">> => <<"administrator">>}], get_http_data(Res0)),
-
-    AssertSuccess = fun({ok, Res}) ->
-                        ?assertEqual(#{<<"code">> => 0}, json(Res))
-                    end,
-    [AssertSuccess(R)
-     || R <- [ http_put("users/admin", #{<<"tags">> => <<"a_new_tag">>})
-             , http_post("users", #{<<"username">> => <<"usera">>,
-                                    <<"password">> => <<"passwd">>})
-             , http_post("auth", #{<<"username">> => <<"usera">>,
-                                   <<"password">> => <<"passwd">>})
-             , http_delete("users/usera")
-             , http_put("users/admin/change_pwd", #{<<"old_pwd">> => <<"public">>,
-                                                    <<"new_pwd">> => <<"newpwd">>})
-             , http_post("auth", #{<<"username">> => <<"admin">>,
-                                   <<"password">> => <<"newpwd">>})
-             ]],
+                    <<"description">> => <<"administrator">>}], get_http_data(Res0)),
+    {ok, 200, _} = http_put(["users", "admin"], #{<<"description">> => <<"a_new_description">>}),
+    {ok, 200, _} = http_post(["users"], #{<<"username">> => <<"usera">>,
+                                          <<"password">> => <<"passwd">>,
+                                          <<"description">> => Desc}),
+    {ok, 204, _} = http_delete(["users", "usera"]),
+    {ok, 404, _} = http_delete(["users", "usera"]),
+    {ok, 204, _} = http_put( ["users", "admin", "change_pwd"]
+                           , #{<<"old_pwd">> => <<"public">>,
+                               <<"new_pwd">> => <<"newpwd">>}),
+    mnesia:clear_table(?ADMIN),
+    emqx_dashboard_admin:add_user(<<"admin">>, <<"public">>, <<"administrator">>),
     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) ->
-    request_api(get, api_path(Path), auth_header_()).
+http_get(Parts) ->
+    request_api(get, api_path(Parts), auth_header_()).
 
 
-http_delete(Path) ->
-    request_api(delete, api_path(Path), auth_header_()).
+http_delete(Parts) ->
+    request_api(delete, api_path(Parts), auth_header_()).
 
 
-http_post(Path, Body) ->
-    request_api(post, api_path(Path), [], auth_header_(), Body).
+http_post(Parts, Body) ->
+    request_api(post, api_path(Parts), [], auth_header_(), Body).
 
 
-http_put(Path, Body) ->
-    request_api(put, api_path(Path), [], auth_header_(), Body).
+http_put(Parts, 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} }  ->
-            true;
+        {ok, {{"HTTP/1.1", Code, _}, _Headers, Return} }
+          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) ->
-    Encoded = base64:encode_to_string(lists:append([User,":",Pass])),
-    {"Authorization","Basic " ++ Encoded}.
+auth_header_(Username, Password) ->
+    {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
+    {"Authorization","Bearer " ++ binary_to_list(Token)}.
 
 
-api_path(Path) ->
-    ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION, Path]).
+api_path(Parts) ->
+    ?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.