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:
parent
14da850e06
commit
e361cd5733
|
@ -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:
|
||||||
|
|
|
@ -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])),
|
||||||
|
|
|
@ -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
|
||||||
}).
|
}).
|
||||||
|
|
|
@ -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 => #{},
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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" }]).
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue