Merge pull request #7749 from DDDHuang/fix_some_api
fix: create banned & dashboard default user name
This commit is contained in:
commit
2ea66ebcee
|
@ -0,0 +1,150 @@
|
||||||
|
emqx_dashboard_api {
|
||||||
|
|
||||||
|
token {
|
||||||
|
desc {
|
||||||
|
en: """Dashboard Auth Token"""
|
||||||
|
zh: """Dashboard 认证 Token"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
username {
|
||||||
|
desc {
|
||||||
|
en: """Dashboard Username"""
|
||||||
|
zh: """Dashboard 用户名"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user_description {
|
||||||
|
desc {
|
||||||
|
en: """Dashboard User Description"""
|
||||||
|
zh: """Dashboard 用户描述"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
password {
|
||||||
|
desc {
|
||||||
|
en: """Dashboard Password"""
|
||||||
|
zh: """Dashboard 密码"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
license {
|
||||||
|
desc {
|
||||||
|
en: """EMQX License. Community or enterprise"""
|
||||||
|
zh: """EMQX 许可。开源版本 或者企业版"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version {
|
||||||
|
desc {
|
||||||
|
en: """EMQX Version"""
|
||||||
|
zh: """EMQX 版本"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
login_api {
|
||||||
|
desc {
|
||||||
|
en: """Dashboard Auth. Get Token"""
|
||||||
|
zh: """Dashboard 认证。获取 Token"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
login_success {
|
||||||
|
desc {
|
||||||
|
en: """Dashboard Auth. Success"""
|
||||||
|
zh: """Dashboard 认证。成功"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
login_failed401 {
|
||||||
|
desc {
|
||||||
|
en: """Login failed. Bad username or password"""
|
||||||
|
zh: """登录失败。用户名或密码错误"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logout_api {
|
||||||
|
desc {
|
||||||
|
en: """Dashboard user logout"""
|
||||||
|
zh: """Dashboard 用户登出"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list_users_api {
|
||||||
|
desc {
|
||||||
|
en: """Dashboard list users"""
|
||||||
|
zh: """Dashboard 用户列表"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create_user_api {
|
||||||
|
desc {
|
||||||
|
en: """Create dashboard user"""
|
||||||
|
zh: """创建 Dashboard 用户"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create_user_api_success {
|
||||||
|
desc {
|
||||||
|
en: """Create dashboard user success"""
|
||||||
|
zh: """创建 Dashboard 用户成功"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_user_api {
|
||||||
|
desc {
|
||||||
|
en: """Update dashboard user description"""
|
||||||
|
zh: """更新 Dashboard 用户描述"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_user_api200 {
|
||||||
|
desc {
|
||||||
|
en: """Update dashboard user success"""
|
||||||
|
zh: """更新 Dashboard 用户成功"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_user_api {
|
||||||
|
desc {
|
||||||
|
en: """Delete dashboard user"""
|
||||||
|
zh: """删除 Dashboard 用户"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
users_api404 {
|
||||||
|
desc {
|
||||||
|
en: """Dashboard user not found"""
|
||||||
|
zh: """Dashboard 用户不存在"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
change_pwd_api {
|
||||||
|
desc {
|
||||||
|
en: """Change dashboard user password"""
|
||||||
|
zh: """更改 Dashboard 用户密码"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
old_pwd {
|
||||||
|
desc {
|
||||||
|
en: """Old password"""
|
||||||
|
zh: """旧密码"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_pwd {
|
||||||
|
desc {
|
||||||
|
en: """New password"""
|
||||||
|
zh: """新密码"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
login_failed_response400 {
|
||||||
|
desc {
|
||||||
|
en: """Login failed. Bad username or password"""
|
||||||
|
zh: """登录失败。用户名或密码错误"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -45,7 +45,9 @@
|
||||||
, verify_hash/2
|
, verify_hash/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([add_default_user/0]).
|
-export([ add_default_user/0
|
||||||
|
, default_username/0
|
||||||
|
]).
|
||||||
|
|
||||||
-type emqx_admin() :: #?ADMIN{}.
|
-type emqx_admin() :: #?ADMIN{}.
|
||||||
|
|
||||||
|
@ -104,18 +106,24 @@ add_user_(Username, Password, Desc) ->
|
||||||
mnesia:write(Admin),
|
mnesia:write(Admin),
|
||||||
#{username => Username, description => Desc};
|
#{username => Username, description => Desc};
|
||||||
[_] ->
|
[_] ->
|
||||||
mnesia:abort(<<"Username Already Exist">>)
|
mnesia:abort(<<"username_already_exist">>)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec(remove_user(binary()) -> {ok, any()} | {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})
|
_ -> mnesia:delete({?ADMIN, Username})
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
return(mria:transaction(?DASHBOARD_SHARD, Trans)).
|
case return(mria:transaction(?DASHBOARD_SHARD, Trans)) of
|
||||||
|
{ok, Result} ->
|
||||||
|
_ = emqx_dashboard_token:destroy_by_username(Username),
|
||||||
|
{ok, Result};
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
-spec(update_user(binary(), binary()) -> {ok, map()} | {error, term()}).
|
-spec(update_user(binary(), binary()) -> {ok, map()} | {error, term()}).
|
||||||
update_user(Username, Desc) when is_binary(Username) ->
|
update_user(Username, Desc) when is_binary(Username) ->
|
||||||
|
@ -142,7 +150,7 @@ sha256(SaltBin, Password) ->
|
||||||
update_user_(Username, Desc) ->
|
update_user_(Username, Desc) ->
|
||||||
case mnesia:wread({?ADMIN, Username}) of
|
case mnesia:wread({?ADMIN, Username}) of
|
||||||
[] ->
|
[] ->
|
||||||
mnesia:abort(<<"Username Not Found">>);
|
mnesia:abort(<<"username_not_found">>);
|
||||||
[Admin] ->
|
[Admin] ->
|
||||||
mnesia:write(Admin#?ADMIN{description = Desc}),
|
mnesia:write(Admin#?ADMIN{description = Desc}),
|
||||||
#{username => Username, description => Desc}
|
#{username => Username, description => Desc}
|
||||||
|
@ -158,20 +166,28 @@ change_password(Username, Password) when is_binary(Username), is_binary(Password
|
||||||
change_password_hash(Username, hash(Password)).
|
change_password_hash(Username, hash(Password)).
|
||||||
|
|
||||||
change_password_hash(Username, PasswordHash) ->
|
change_password_hash(Username, PasswordHash) ->
|
||||||
update_pwd(Username, fun(User) ->
|
ChangePWD =
|
||||||
User#?ADMIN{pwdhash = PasswordHash}
|
fun(User) ->
|
||||||
end).
|
User#?ADMIN{pwdhash = PasswordHash}
|
||||||
|
end,
|
||||||
|
case update_pwd(Username, ChangePWD) of
|
||||||
|
{ok, Result} ->
|
||||||
|
_ = emqx_dashboard_token:destroy_by_username(Username),
|
||||||
|
{ok, Result};
|
||||||
|
{error, Reason} -> {error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
update_pwd(Username, Fun) ->
|
update_pwd(Username, Fun) ->
|
||||||
Trans = fun() ->
|
Trans =
|
||||||
User =
|
fun() ->
|
||||||
case lookup_user(Username) of
|
User =
|
||||||
|
case lookup_user(Username) of
|
||||||
[Admin] -> Admin;
|
[Admin] -> Admin;
|
||||||
[] ->
|
[] ->
|
||||||
mnesia:abort(<<"Username Not Found">>)
|
mnesia:abort(<<"username_not_found">>)
|
||||||
end,
|
end,
|
||||||
mnesia:write(Fun(User))
|
mnesia:write(Fun(User))
|
||||||
end,
|
end,
|
||||||
return(mria:transaction(?DASHBOARD_SHARD, Trans)).
|
return(mria:transaction(?DASHBOARD_SHARD, Trans)).
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,7 +206,7 @@ all_users() ->
|
||||||
description => Desc
|
description => Desc
|
||||||
}
|
}
|
||||||
end, ets:tab2list(?ADMIN)).
|
end, ets:tab2list(?ADMIN)).
|
||||||
|
-spec(return({atomic | aborted, term()}) -> {ok, term()} | {error, Reason :: binary()}).
|
||||||
return({atomic, Result}) ->
|
return({atomic, Result}) ->
|
||||||
{ok, Result};
|
{ok, Result};
|
||||||
return({aborted, Reason}) ->
|
return({aborted, Reason}) ->
|
||||||
|
@ -240,6 +256,9 @@ destroy_token_by_username(Username, Token) ->
|
||||||
add_default_user() ->
|
add_default_user() ->
|
||||||
add_default_user(binenv(default_username), binenv(default_password)).
|
add_default_user(binenv(default_username), binenv(default_password)).
|
||||||
|
|
||||||
|
default_username() ->
|
||||||
|
binenv(default_username).
|
||||||
|
|
||||||
binenv(Key) ->
|
binenv(Key) ->
|
||||||
iolist_to_binary(emqx_conf:get([dashboard, Key], "")).
|
iolist_to_binary(emqx_conf:get([dashboard, Key], "")).
|
||||||
|
|
||||||
|
|
|
@ -19,17 +19,40 @@
|
||||||
-behaviour(minirest_api).
|
-behaviour(minirest_api).
|
||||||
|
|
||||||
-include("emqx_dashboard.hrl").
|
-include("emqx_dashboard.hrl").
|
||||||
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
-include_lib("typerefl/include/types.hrl").
|
-include_lib("typerefl/include/types.hrl").
|
||||||
-import(hoconsc, [mk/2, ref/2, array/1, enum/1]).
|
|
||||||
|
|
||||||
-export([api_spec/0, fields/1, paths/0, schema/1, namespace/0]).
|
-import(hoconsc, [
|
||||||
-export([login/2, logout/2, users/2, user/2, change_pwd/2]).
|
mk/2,
|
||||||
|
array/1,
|
||||||
|
enum/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
api_spec/0,
|
||||||
|
fields/1,
|
||||||
|
paths/0,
|
||||||
|
schema/1,
|
||||||
|
namespace/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
login/2,
|
||||||
|
logout/2,
|
||||||
|
users/2,
|
||||||
|
user/2,
|
||||||
|
change_pwd/2
|
||||||
|
]).
|
||||||
|
|
||||||
-define(EMPTY(V), (V == undefined orelse V == <<>>)).
|
-define(EMPTY(V), (V == undefined orelse V == <<>>)).
|
||||||
-define(ERROR_USERNAME_OR_PWD, 'ERROR_USERNAME_OR_PWD').
|
|
||||||
-define(USER_NOT_FOUND_BODY, #{ code => <<"USER_NOT_FOUND">>
|
|
||||||
, message => <<"User not found">>}).
|
|
||||||
|
|
||||||
|
-define(WRONG_USERNAME_OR_PWD, 'WRONG_USERNAME_OR_PWD').
|
||||||
|
-define(WRONG_TOKEN_OR_USERNAME, 'WRONG_TOKEN_OR_USERNAME').
|
||||||
|
-define(USER_NOT_FOUND, 'USER_NOT_FOUND').
|
||||||
|
-define(ERROR_PWD_NOT_MATCH, 'ERROR_PWD_NOT_MATCH').
|
||||||
|
-define(NOT_ALLOWED, 'NOT_ALLOWED').
|
||||||
|
-define(BAD_REQUEST, 'BAD_REQUEST').
|
||||||
|
|
||||||
namespace() -> "dashboard".
|
namespace() -> "dashboard".
|
||||||
|
|
||||||
|
@ -48,43 +71,26 @@ schema("/login") ->
|
||||||
'operationId' => login,
|
'operationId' => login,
|
||||||
post => #{
|
post => #{
|
||||||
tags => [<<"dashboard">>],
|
tags => [<<"dashboard">>],
|
||||||
desc => <<"Dashboard Auth">>,
|
desc => ?DESC(login_api),
|
||||||
summary => <<"Dashboard Auth">>,
|
summary => <<"Dashboard Auth">>,
|
||||||
'requestBody' => [
|
'requestBody' => fields([username, password]),
|
||||||
{username, mk(binary(),
|
|
||||||
#{desc => <<"The User for which to create the token.">>,
|
|
||||||
'maxLength' => 100, example => <<"admin">>})},
|
|
||||||
{password, mk(binary(),
|
|
||||||
#{desc => "password", example => "public"})}
|
|
||||||
],
|
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => [
|
200 => fields([token, version, license]),
|
||||||
{token, mk(string(), #{desc => <<"JWT Token">>})},
|
401 => response_schema(401)
|
||||||
{license, [{edition,
|
|
||||||
mk(enum([community, enterprise]), #{desc => <<"license">>,
|
|
||||||
example => "community"})}]},
|
|
||||||
{version, mk(string(), #{desc => <<"version">>, example => <<"5.0.0">>})}
|
|
||||||
],
|
|
||||||
401 => [
|
|
||||||
{code, mk(string(), #{example => 'ERROR_USERNAME_OR_PWD'})},
|
|
||||||
{message, mk(string(), #{example => "Unauthorized"})}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
security => []
|
security => []
|
||||||
}};
|
}
|
||||||
|
};
|
||||||
schema("/logout") ->
|
schema("/logout") ->
|
||||||
#{
|
#{
|
||||||
'operationId' => logout,
|
'operationId' => logout,
|
||||||
post => #{
|
post => #{
|
||||||
tags => [<<"dashboard">>],
|
tags => [<<"dashboard">>],
|
||||||
desc => <<"Dashboard User logout">>,
|
desc => ?DESC(logout_api),
|
||||||
'requestBody' => [
|
'requestBody' => fields([username]),
|
||||||
{username, mk(binary(),
|
|
||||||
#{desc => <<"The User for which to create the token.">>,
|
|
||||||
'maxLength' => 100, example => <<"admin">>})}
|
|
||||||
],
|
|
||||||
responses => #{
|
responses => #{
|
||||||
204 => <<"Dashboard logout successfully">>
|
204 => <<"Dashboard logout successfully">>,
|
||||||
|
401 => response_schema(401)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -93,22 +99,18 @@ schema("/users") ->
|
||||||
'operationId' => users,
|
'operationId' => users,
|
||||||
get => #{
|
get => #{
|
||||||
tags => [<<"dashboard">>],
|
tags => [<<"dashboard">>],
|
||||||
desc => <<"Get dashboard users list">>,
|
desc => ?DESC(list_users_api),
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => mk( array(ref(?MODULE, user))
|
200 => mk(array(hoconsc:ref(user)),
|
||||||
, #{desc => "User lists"})
|
#{desc => ?DESC(list_users_api)})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
post => #{
|
post => #{
|
||||||
tags => [<<"dashboard">>],
|
tags => [<<"dashboard">>],
|
||||||
desc => <<"Create dashboard users">>,
|
desc => ?DESC(create_user_api),
|
||||||
'requestBody' => fields(user_password),
|
'requestBody' => fields([username, password, description]),
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => mk( ref(?MODULE, user)
|
200 => fields([username, description])
|
||||||
, #{desc => <<"Create User successfully">>}),
|
|
||||||
400 => [{code, mk(string(), #{example => 'CREATE_FAIL'})},
|
|
||||||
{message, mk(string(), #{example => "Create user failed"})}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -118,36 +120,23 @@ schema("/users/:username") ->
|
||||||
'operationId' => user,
|
'operationId' => user,
|
||||||
put => #{
|
put => #{
|
||||||
tags => [<<"dashboard">>],
|
tags => [<<"dashboard">>],
|
||||||
desc => <<"Update dashboard users">>,
|
desc => ?DESC(update_user_api),
|
||||||
parameters => [{username, mk(binary(),
|
parameters => fields([username_in_path]),
|
||||||
#{in => path, example => <<"admin">>})}],
|
'requestBody' => fields([description]),
|
||||||
'requestBody' => [
|
|
||||||
{ description
|
|
||||||
, mk(binary(),
|
|
||||||
#{desc => <<"User description">>, example => <<"administrator">>})}
|
|
||||||
],
|
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => mk( ref(?MODULE, user)
|
200 => fields([username, description]),
|
||||||
, #{desc => <<"Update User successfully">>}),
|
404 => response_schema(404)
|
||||||
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">>],
|
||||||
desc => <<"Delete dashboard users">>,
|
desc => ?DESC(delete_user_api),
|
||||||
parameters => [{username, mk(binary(),
|
parameters => fields([username_in_path]),
|
||||||
#{in => path, example => <<"admin">>})}],
|
|
||||||
responses => #{
|
responses => #{
|
||||||
204 => <<"Delete User successfully">>,
|
204 => <<"Delete User successfully">>,
|
||||||
400 => [
|
400 => emqx_dashboard_swagger:error_codes(
|
||||||
{code, mk(string(), #{example => 'CANNOT_DELETE_ADMIN'})},
|
[?BAD_REQUEST, ?NOT_ALLOWED], ?DESC(login_failed_response400)),
|
||||||
{message, mk(string(), #{example => "CANNOT DELETE ADMIN"})}
|
404 => response_schema(404)
|
||||||
],
|
|
||||||
404 => emqx_dashboard_swagger:error_codes(['USER_NOT_FOUND'], <<"User Not Found">>)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -156,76 +145,107 @@ schema("/users/:username/change_pwd") ->
|
||||||
'operationId' => change_pwd,
|
'operationId' => change_pwd,
|
||||||
put => #{
|
put => #{
|
||||||
tags => [<<"dashboard">>],
|
tags => [<<"dashboard">>],
|
||||||
desc => <<"Update dashboard users password">>,
|
desc => ?DESC(change_pwd_api),
|
||||||
parameters => [{username, mk(binary(),
|
parameters => fields([username_in_path]),
|
||||||
#{in => path, required => true, example => <<"admin">>})}],
|
'requestBody' => fields([old_pwd, new_pwd]),
|
||||||
'requestBody' => [
|
|
||||||
{old_pwd, mk(binary(), #{required => true})},
|
|
||||||
{new_pwd, mk(binary(), #{required => true})}
|
|
||||||
],
|
|
||||||
responses => #{
|
responses => #{
|
||||||
204 => <<"Update user password successfully">>,
|
204 => <<"Update user password successfully">>,
|
||||||
400 => [
|
401 => emqx_dashboard_swagger:error_codes(
|
||||||
{code, mk(string(), #{example => 'UPDATE_FAIL'})},
|
[?WRONG_USERNAME_OR_PWD, ?ERROR_PWD_NOT_MATCH], ?DESC(login_failed401)),
|
||||||
{message, mk(string(), #{example => "Failed Reason"})}
|
404 => response_schema(404),
|
||||||
]
|
400 => emqx_dashboard_swagger:error_codes(
|
||||||
|
[?BAD_REQUEST], ?DESC(login_failed_response400))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
response_schema(401) ->
|
||||||
|
emqx_dashboard_swagger:error_codes([?WRONG_USERNAME_OR_PWD], ?DESC(login_failed401));
|
||||||
|
response_schema(404) ->
|
||||||
|
emqx_dashboard_swagger:error_codes([?USER_NOT_FOUND], ?DESC(users_api404)).
|
||||||
|
|
||||||
fields(user) ->
|
fields(user) ->
|
||||||
[
|
fields([username, description]);
|
||||||
{description,
|
fields(List) ->
|
||||||
mk(binary(),
|
[field(Key) || Key <- List].
|
||||||
#{desc => <<"User description">>, example => "administrator"})},
|
|
||||||
{username,
|
field(username) ->
|
||||||
mk(binary(),
|
{username,
|
||||||
#{desc => <<"username">>, example => "emqx"})}
|
mk(binary(), #{desc => ?DESC(username), 'maxLength' => 100, example => <<"admin">>})};
|
||||||
];
|
field(username_in_path) ->
|
||||||
fields(user_password) ->
|
{username,
|
||||||
fields(user) ++
|
mk(binary(), #{desc => ?DESC(username), 'maxLength' => 100, example => <<"admin">>,
|
||||||
[{password, mk(binary(), #{desc => "Password", example => <<"public">>})}].
|
in => path, required => true})};
|
||||||
|
field(password) ->
|
||||||
|
{password,
|
||||||
|
mk(binary(), #{desc => ?DESC(password), 'maxLength' => 100, example => <<"public">>})};
|
||||||
|
field(description) ->
|
||||||
|
{description,
|
||||||
|
mk(binary(), #{desc => ?DESC(user_description), example => <<"administrator">>})};
|
||||||
|
field(token) ->
|
||||||
|
{token, mk(binary(), #{desc => ?DESC(token)})};
|
||||||
|
field(license) ->
|
||||||
|
{license, [
|
||||||
|
{edition, mk(enum([community, enterprise]),
|
||||||
|
#{desc => ?DESC(license), example => community})}]};
|
||||||
|
field(version) ->
|
||||||
|
{version, mk(string(), #{desc => ?DESC(version), example => <<"5.0.0">>})};
|
||||||
|
|
||||||
|
field(old_pwd) ->
|
||||||
|
{old_pwd, mk(binary(), #{desc => ?DESC(old_pwd)})};
|
||||||
|
|
||||||
|
field(new_pwd) ->
|
||||||
|
{new_pwd, mk(binary(), #{desc => ?DESC(new_pwd)})}.
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------------------------------------
|
||||||
|
%% API
|
||||||
|
|
||||||
login(post, #{body := Params}) ->
|
login(post, #{body := Params}) ->
|
||||||
Username = maps:get(<<"username">>, Params),
|
Username = maps:get(<<"username">>, Params),
|
||||||
Password = maps:get(<<"password">>, Params),
|
Password = maps:get(<<"password">>, Params),
|
||||||
case emqx_dashboard_admin:sign_token(Username, Password) of
|
case emqx_dashboard_admin:sign_token(Username, Password) of
|
||||||
{ok, Token} ->
|
{ok, Token} ->
|
||||||
|
?SLOG(info, #{msg => "Dashboard login successfully", username => Username}),
|
||||||
Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
|
Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
|
||||||
{200, #{token => Token,
|
{200, #{token => Token,
|
||||||
version => Version,
|
version => Version,
|
||||||
license => #{edition => emqx_release:edition()}
|
license => #{edition => emqx_release:edition()}
|
||||||
}};
|
}};
|
||||||
{error, _} ->
|
{error, R} ->
|
||||||
{401, #{code => ?ERROR_USERNAME_OR_PWD, message => <<"Auth filed">>}}
|
?SLOG(info, #{msg => "Dashboard login failed", username => Username, reason => R}),
|
||||||
|
{401, ?WRONG_USERNAME_OR_PWD, <<"Auth filed">>}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
logout(_, #{body := #{<<"username">> := Username},
|
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 ->
|
||||||
|
?SLOG(info, #{msg => "Dashboard logout successfully", username => Username}),
|
||||||
204;
|
204;
|
||||||
_R ->
|
_R ->
|
||||||
{401, 'BAD_TOKEN_OR_USERNAME', <<"Ensure your token & username">>}
|
?SLOG(info, #{msg => "Dashboard logout failed.", username => Username}),
|
||||||
|
{401, ?WRONG_TOKEN_OR_USERNAME, <<"Ensure your token & username">>}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
users(get, _Request) ->
|
users(get, _Request) ->
|
||||||
{200, emqx_dashboard_admin:all_users()};
|
{200, emqx_dashboard_admin:all_users()};
|
||||||
|
|
||||||
users(post, #{body := Params}) ->
|
users(post, #{body := Params}) ->
|
||||||
Desc = maps:get(<<"description">>, 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
|
||||||
true ->
|
true ->
|
||||||
{400, #{code => <<"CREATE_USER_FAIL">>,
|
{400, ?BAD_REQUEST, <<"Username or password undefined">>};
|
||||||
message => <<"Username or password undefined">>}};
|
|
||||||
false ->
|
false ->
|
||||||
case emqx_dashboard_admin:add_user(Username, Password, Desc) of
|
case emqx_dashboard_admin:add_user(Username, Password, Desc) of
|
||||||
{ok, Result} ->
|
{ok, Result} ->
|
||||||
|
?SLOG(info, #{msg => "Create dashboard success", username => Username}),
|
||||||
{200, Result};
|
{200, Result};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{400, #{code => <<"CREATE_USER_FAIL">>, message => Reason}}
|
?SLOG(info, #{msg => "Create dashboard failed",
|
||||||
|
username => Username, reason => Reason}),
|
||||||
|
{400, ?BAD_REQUEST, Reason}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -234,30 +254,47 @@ user(put, #{bindings := #{username := Username}, body := Params}) ->
|
||||||
case emqx_dashboard_admin:update_user(Username, Desc) of
|
case emqx_dashboard_admin:update_user(Username, Desc) of
|
||||||
{ok, Result} ->
|
{ok, Result} ->
|
||||||
{200, Result};
|
{200, Result};
|
||||||
{error, _Reason} ->
|
{error, Reason} ->
|
||||||
{404, ?USER_NOT_FOUND_BODY}
|
{404, ?USER_NOT_FOUND, Reason}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
user(delete, #{bindings := #{username := Username}}) ->
|
user(delete, #{bindings := #{username := Username}}) ->
|
||||||
case Username == <<"admin">> of
|
case Username == emqx_dashboard_admin:default_username() of
|
||||||
true ->
|
true ->
|
||||||
{400, #{code => <<"ACTION_NOT_ALLOWED">>,
|
?SLOG(info, #{msg => "Dashboard delete admin user failed", username => Username}),
|
||||||
message => <<"Cannot delete admin">>}};
|
Message = list_to_binary(io_lib:format("Cannot delete user ~p", [Username])),
|
||||||
|
{400, ?NOT_ALLOWED, Message};
|
||||||
false ->
|
false ->
|
||||||
case emqx_dashboard_admin:remove_user(Username) of
|
case emqx_dashboard_admin:remove_user(Username) of
|
||||||
{error, _Reason} ->
|
{error, Reason} ->
|
||||||
{404, ?USER_NOT_FOUND_BODY};
|
{404, ?USER_NOT_FOUND, Reason};
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
|
?SLOG(info, #{msg => "Dashboard delete admin user", username => Username}),
|
||||||
{204}
|
{204}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
change_pwd(put, #{bindings := #{username := Username}, body := Params}) ->
|
change_pwd(put, #{bindings := #{username := Username}, body := Params}) ->
|
||||||
|
LogMeta = #{msg => "Dashboard change password", username => Username},
|
||||||
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 ?EMPTY(OldPwd) orelse ?EMPTY(NewPwd) of
|
||||||
{ok, _} ->
|
true ->
|
||||||
{204};
|
?SLOG(error, LogMeta#{result => failed, reason => "password undefined or empty"}),
|
||||||
{error, Reason} ->
|
{400, ?BAD_REQUEST, <<"Old password or new password undefined">>};
|
||||||
{400, #{code => <<"CHANGE_PWD_FAIL">>, message => Reason}}
|
false ->
|
||||||
|
case emqx_dashboard_admin:change_password(Username, OldPwd, NewPwd) of
|
||||||
|
{ok, _} ->
|
||||||
|
?SLOG(info, LogMeta#{result => success}),
|
||||||
|
{204};
|
||||||
|
{error, <<"username_not_found">>} ->
|
||||||
|
?SLOG(error, LogMeta#{result => failed, reason => "username not found"}),
|
||||||
|
{404, ?USER_NOT_FOUND, <<"User not found">>};
|
||||||
|
{error, <<"password_error">>} ->
|
||||||
|
?SLOG(error, LogMeta#{result => failed, reason => "error old pwd"}),
|
||||||
|
{401, ?ERROR_PWD_NOT_MATCH, <<"Old password not match">>};
|
||||||
|
{error, Reason} ->
|
||||||
|
?SLOG(error, LogMeta#{result => failed, reason => Reason}),
|
||||||
|
{400, ?BAD_REQUEST, Reason}
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -31,8 +31,6 @@ admins(["add", Username, Password, Desc]) ->
|
||||||
case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Desc)) 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} ->
|
|
||||||
emqx_ctl:print("Error: already existed~n");
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
emqx_ctl:print("Error: ~p~n", [Reason])
|
emqx_ctl:print("Error: ~p~n", [Reason])
|
||||||
end;
|
end;
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-module(emqx_dashboard_admin_SUITE).
|
||||||
|
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-compile(export_all).
|
||||||
|
|
||||||
|
-include("emqx_dashboard.hrl").
|
||||||
|
-include_lib("emqx/include/http_api.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() ->
|
||||||
|
emqx_common_test_helpers:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_suite(Config) ->
|
||||||
|
mria:start(),
|
||||||
|
application:load(emqx_dashboard),
|
||||||
|
emqx_common_test_helpers:start_apps([emqx_conf, emqx_dashboard], fun set_special_configs/1),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
set_special_configs(emqx_dashboard) ->
|
||||||
|
emqx_dashboard_api_test_helpers:set_default_config(),
|
||||||
|
ok;
|
||||||
|
set_special_configs(_) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
end_per_suite(Config) ->
|
||||||
|
end_suite(),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_, _Config) ->
|
||||||
|
All = emqx_dashboard_admin:all_users(),
|
||||||
|
[emqx_dashboard_admin:remove_user(Name) || #{username := Name} <- All].
|
||||||
|
|
||||||
|
end_suite() ->
|
||||||
|
application:unload(emqx_management),
|
||||||
|
emqx_common_test_helpers:stop_apps([emqx_dashboard]).
|
||||||
|
|
||||||
|
t_check_user(_) ->
|
||||||
|
Username = <<"admin1">>,
|
||||||
|
Password = <<"public">>,
|
||||||
|
BadUsername = <<"admin_bad">>,
|
||||||
|
BadPassword = <<"public_bad">>,
|
||||||
|
EmptyUsername = <<>>,
|
||||||
|
EmptyPassword = <<>>,
|
||||||
|
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>),
|
||||||
|
ok = emqx_dashboard_admin:check(Username, Password),
|
||||||
|
{error, <<"password_error">>} = emqx_dashboard_admin:check(Username, BadPassword),
|
||||||
|
{error, <<"username_not_found">>} = emqx_dashboard_admin:check(BadUsername, Password),
|
||||||
|
{error, <<"username_not_found">>} = emqx_dashboard_admin:check(BadUsername, BadPassword),
|
||||||
|
{error, <<"username_not_found">>} = emqx_dashboard_admin:check(EmptyUsername, Password),
|
||||||
|
{error, <<"password_error">>} = emqx_dashboard_admin:check(Username, EmptyPassword),
|
||||||
|
{error, <<"username_not_provided">>} = emqx_dashboard_admin:check(undefined, Password),
|
||||||
|
{error, <<"password_not_provided">>} = emqx_dashboard_admin:check(Username, undefined),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_add_user(_) ->
|
||||||
|
AddUser = <<"add_user">>,
|
||||||
|
AddPassword = <<"add_password">>,
|
||||||
|
AddDescription = <<"add_description">>,
|
||||||
|
|
||||||
|
BadAddUser = <<"***add_user_bad">>,
|
||||||
|
|
||||||
|
%% add success. not return password
|
||||||
|
{ok, NewUser} = emqx_dashboard_admin:add_user(AddUser, AddPassword, AddDescription),
|
||||||
|
AddUser = maps:get(username, NewUser),
|
||||||
|
AddDescription = maps:get(description, NewUser),
|
||||||
|
false = maps:is_key(password, NewUser),
|
||||||
|
|
||||||
|
%% add again
|
||||||
|
{error, <<"username_already_exist">>} =
|
||||||
|
emqx_dashboard_admin:add_user(AddUser, AddPassword, AddDescription),
|
||||||
|
|
||||||
|
%% add bad username
|
||||||
|
BadNameError =
|
||||||
|
<<"Bad Username. Only upper and lower case letters, numbers and underscores are supported">>,
|
||||||
|
{error, BadNameError} = emqx_dashboard_admin:add_user(BadAddUser, AddPassword, AddDescription),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_lookup_user(_) ->
|
||||||
|
LookupUser = <<"lookup_user">>,
|
||||||
|
LookupPassword = <<"lookup_password">>,
|
||||||
|
LookupDescription = <<"lookup_description">>,
|
||||||
|
|
||||||
|
BadLookupUser = <<"***lookup_user_bad">>,
|
||||||
|
|
||||||
|
{ok, _} =
|
||||||
|
emqx_dashboard_admin:add_user(LookupUser, LookupPassword, LookupDescription),
|
||||||
|
%% lookup success. not return password
|
||||||
|
[#emqx_admin{username = LookupUser, description = LookupDescription}] =
|
||||||
|
emqx_dashboard_admin:lookup_user(LookupUser),
|
||||||
|
|
||||||
|
[] = emqx_dashboard_admin:lookup_user(BadLookupUser),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_all_users(_) ->
|
||||||
|
Username = <<"admin_all">>,
|
||||||
|
Password = <<"public">>,
|
||||||
|
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>),
|
||||||
|
All = emqx_dashboard_admin:all_users(),
|
||||||
|
?assert(erlang:length(All) >= 1),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_delete_user(_) ->
|
||||||
|
DeleteUser = <<"delete_user">>,
|
||||||
|
DeletePassword = <<"delete_password">>,
|
||||||
|
DeleteDescription = <<"delete_description">>,
|
||||||
|
|
||||||
|
DeleteBadUser = <<"delete_user_bad">>,
|
||||||
|
|
||||||
|
{ok, _NewUser} =
|
||||||
|
emqx_dashboard_admin:add_user(DeleteUser, DeletePassword, DeleteDescription),
|
||||||
|
{ok, ok} = emqx_dashboard_admin:remove_user(DeleteUser),
|
||||||
|
%% remove again
|
||||||
|
{error, <<"username_not_found">>} = emqx_dashboard_admin:remove_user(DeleteUser),
|
||||||
|
{error, <<"username_not_found">>} = emqx_dashboard_admin:remove_user(DeleteBadUser),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_update_user(_) ->
|
||||||
|
UpdateUser = <<"update_user">>,
|
||||||
|
UpdatePassword = <<"update_password">>,
|
||||||
|
UpdateDescription = <<"update_description">>,
|
||||||
|
|
||||||
|
NewDesc = <<"new_description">>,
|
||||||
|
|
||||||
|
BadUpdateUser = <<"update_user_bad">>,
|
||||||
|
|
||||||
|
{ok, _} = emqx_dashboard_admin:add_user(UpdateUser, UpdatePassword, UpdateDescription),
|
||||||
|
{ok, NewUserInfo} =
|
||||||
|
emqx_dashboard_admin:update_user(UpdateUser, NewDesc),
|
||||||
|
UpdateUser = maps:get(username, NewUserInfo),
|
||||||
|
NewDesc = maps:get(description, NewUserInfo),
|
||||||
|
|
||||||
|
{error,<<"username_not_found">>} = emqx_dashboard_admin:update_user(BadUpdateUser, NewDesc),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_change_password(_) ->
|
||||||
|
User = <<"change_user">>,
|
||||||
|
OldPassword = <<"change_password">>,
|
||||||
|
Description = <<"change_description">>,
|
||||||
|
|
||||||
|
NewPassword = <<"new_password">>,
|
||||||
|
|
||||||
|
BadChangeUser = <<"change_user_bad">>,
|
||||||
|
|
||||||
|
{ok, _} = emqx_dashboard_admin:add_user(User, OldPassword, Description),
|
||||||
|
|
||||||
|
{ok, ok} = emqx_dashboard_admin:change_password(User, OldPassword, NewPassword),
|
||||||
|
%% change pwd again
|
||||||
|
{error,<<"password_error">>} =
|
||||||
|
emqx_dashboard_admin:change_password(User, OldPassword, NewPassword),
|
||||||
|
|
||||||
|
{error, <<"username_not_found">>} =
|
||||||
|
emqx_dashboard_admin:change_password(BadChangeUser, OldPassword, NewPassword),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_clean_token(_) ->
|
||||||
|
Username = <<"admin_token">>,
|
||||||
|
Password = <<"public">>,
|
||||||
|
NewPassword = <<"public1">>,
|
||||||
|
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>),
|
||||||
|
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
|
||||||
|
ok = emqx_dashboard_admin:verify_token(Token),
|
||||||
|
%% change password
|
||||||
|
{ok, _} = emqx_dashboard_admin:change_password(Username, Password, NewPassword),
|
||||||
|
timer:sleep(5),
|
||||||
|
{error, not_found} = emqx_dashboard_admin:verify_token(Token),
|
||||||
|
%% remove user
|
||||||
|
{ok, Token2} = emqx_dashboard_admin:sign_token(Username, NewPassword),
|
||||||
|
ok = emqx_dashboard_admin:verify_token(Token2),
|
||||||
|
{ok, _} = emqx_dashboard_admin:remove_user(Username),
|
||||||
|
timer:sleep(5),
|
||||||
|
{error, not_found} = emqx_dashboard_admin:verify_token(Token2),
|
||||||
|
ok.
|
||||||
|
|
|
@ -159,7 +159,9 @@ banned(post, #{body := Body}) ->
|
||||||
Ban ->
|
Ban ->
|
||||||
case emqx_banned:create(Ban) of
|
case emqx_banned:create(Ban) of
|
||||||
{ok, Banned} -> {200, format(Banned)};
|
{ok, Banned} -> {200, format(Banned)};
|
||||||
{error, {already_exist, Old}} -> {400, 'ALREADY_EXISTS', format(Old)}
|
{error, {already_exist, Old}} ->
|
||||||
|
OldBannedFormat = emqx_json:encode(format(Old)),
|
||||||
|
{400, 'ALREADY_EXISTS', OldBannedFormat}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
-mode(compile).
|
-mode(compile).
|
||||||
|
|
||||||
main(_) ->
|
main(_) ->
|
||||||
{ok, BaseConf} = file:read_file("apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf"),
|
BaseConf = <<"">>,
|
||||||
|
|
||||||
Cfgs = get_all_cfgs("apps/"),
|
Cfgs = get_all_cfgs("apps/"),
|
||||||
Conf = [merge(BaseConf, Cfgs),
|
Conf = [merge(BaseConf, Cfgs),
|
||||||
io_lib:nl()
|
io_lib:nl()
|
||||||
|
@ -23,7 +22,7 @@ merge(BaseConf, Cfgs) ->
|
||||||
end, BaseConf, Cfgs).
|
end, BaseConf, Cfgs).
|
||||||
|
|
||||||
get_all_cfgs(Root) ->
|
get_all_cfgs(Root) ->
|
||||||
Apps = filelib:wildcard("*", Root) -- ["emqx_machine", "emqx_dashboard"],
|
Apps = filelib:wildcard("*", Root) -- ["emqx_machine"],
|
||||||
Dirs = [filename:join([Root, App]) || App <- Apps],
|
Dirs = [filename:join([Root, App]) || App <- Apps],
|
||||||
lists:foldl(fun get_cfgs/2, [], Dirs).
|
lists:foldl(fun get_cfgs/2, [], Dirs).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue