Merge pull request #9698 from thalesmg/sync-v4414-into-v44

Sync `v4.4.14` into `v44`
This commit is contained in:
Zaiming (Stone) Shi 2023-01-08 10:28:18 +01:00 committed by GitHub
commit 80a1c43a19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 222 additions and 86 deletions

View File

@ -1,6 +1,6 @@
{application, emqx_management,
[{description, "EMQ X Management API and CLI"},
{vsn, "4.4.11"}, % strict semver, bump manually!
{vsn, "4.4.12"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_management_sup]},
{applications, [kernel,stdlib,emqx_plugin_libs,minirest]},

View File

@ -36,7 +36,6 @@
, del_app/1
, list_apps/0
, init_bootstrap_apps/0
, need_bootstrap/0
, clear_bootstrap_apps/0
]).
@ -83,21 +82,8 @@ add_default_app() ->
end.
init_bootstrap_apps() ->
case need_bootstrap() of
true ->
Bootstrap = application:get_env(emqx_management, bootstrap_apps_file, undefined),
init_bootstrap_apps(Bootstrap);
false ->
ok
end.
need_bootstrap() ->
{atomic, Res} = mnesia:transaction(
fun() ->
Spec = [{#mqtt_app{id = '$1', desc = ?BOOTSTRAP_TAG, _ = '_'}, [], ['$1']}],
mnesia:select(mqtt_app, Spec, 1, read) =:= '$end_of_table'
end),
Res.
Bootstrap = application:get_env(emqx_management, bootstrap_apps_file, undefined),
init_bootstrap_apps(Bootstrap).
clear_bootstrap_apps() ->
{atomic, ok} =
@ -113,13 +99,7 @@ init_bootstrap_apps(File) ->
case file:open(File, [read, binary]) of
{ok, Dev} ->
{ok, MP} = re:compile(<<"(\.+):(\.+$)">>, [ungreedy]),
case init_bootstrap_apps(File, Dev, MP) of
ok -> ok;
Error ->
%% if failed add bootstrap users, we should clear all bootstrap apps
clear_bootstrap_apps(),
Error
end;
init_bootstrap_apps(File, Dev, MP);
{error, Reason} = Error ->
?LOG(error,
"failed to open the mgmt bootstrap apps file(~s) for ~p",
@ -145,8 +125,8 @@ add_bootstrap_app(File, Dev, MP, Line) ->
case re:run(Bin, MP, [global, {capture, all_but_first, binary}]) of
{match, [[AppId, AppSecret]]} ->
Name = <<"bootstraped">>,
case add_app(AppId, Name, AppSecret, ?BOOTSTRAP_TAG, true, undefined) of
{ok, _} ->
case force_add_app(AppId, Name, AppSecret, ?BOOTSTRAP_TAG, true, undefined) of
ok ->
add_bootstrap_app(File, Dev, MP, Line + 1);
{error, Reason} ->
throw(#{file => File, line => Line, content => Bin, reason => Reason})

View File

@ -66,6 +66,70 @@ t_importee430(_) ->
{ok, _} = emqx_mgmt_data_backup:export(),
remove_all_users_and_acl().
t_import_test(_) ->
SimpleAdmin = <<"simpleAdmin">>,
SimplePassword = <<"simplepassword">>,
SimplePasswordHash = emqx_dashboard_admin:hash(SimplePassword),
Admins = [<<"Admin1">>, <<"Admin2">>, <<"Admin3">>, <<"Admin4">>, <<"Admin5">>],
Passwords = [<<"password1">>, <<"PAssword2">>,<<"3&*)dkdKlkd">>,<<"&*qwl4kd>">>,<<"PASSWORD5D">>],
%% add some users
add_admins(Admins, Passwords),
%% Allow force import simple password.
ok = emqx_dashboard_admin:force_add_user(SimpleAdmin, SimplePasswordHash, <<"test">>),
ct:pal("1111~p~n", [ets:info(mqtt_admin)]),
ct:pal("~p~n", [ets:tab2list(mqtt_admin)]),
check_admins_ok(Admins, Passwords),
{ok, #{filename := FileName}} = emqx_mgmt_data_backup:export(),
remove_admins(Admins),
ok = emqx_dashboard_admin:remove_user(SimpleAdmin),
ct:pal("0000~n"),
check_admins_failed(Admins, Passwords),
{error, _} = emqx_dashboard_admin:check(SimpleAdmin, SimplePassword),
ok = emqx_mgmt_data_backup:import(FileName, <<"{}">>),
ct:pal("2222~n"),
check_admins_ok(Admins, Passwords),
ok = emqx_dashboard_admin:check(SimpleAdmin, SimplePassword),
remove_admins(Admins),
ok = emqx_dashboard_admin:remove_user(SimpleAdmin),
remove_all_users_and_acl(),
ok.
add_admins(Admins, Passwords) ->
lists:foreach(
fun({Admin, Password}) ->
ok = emqx_dashboard_admin:add_user(Admin, Password, <<"test">>)
end, lists:zip(Admins, Passwords)),
ok.
check_admins_ok(Admins, Passwords) ->
lists:foreach(
fun({Admin, Password}) ->
?assertMatch(ok, emqx_dashboard_admin:check(Admin, Password), {Admin, Password})
end, lists:zip(Admins, Passwords)),
ok.
check_admins_failed(Admins, Passwords) ->
lists:foreach(
fun({Admin, Password}) ->
?assertMatch({error, _}, emqx_dashboard_admin:check(Admin, Password), {Admin, Password})
end, lists:zip(Admins, Passwords)),
ok.
remove_admins(Admins) ->
lists:foreach(
fun(Admin) ->
ok = emqx_dashboard_admin:remove_user(Admin)
end, Admins),
ok.
remove_all_users_and_acl() ->
mnesia:delete_table(emqx_user),
mnesia:delete_table(emqx_acl).

View File

@ -68,10 +68,10 @@ t_load_ok(_) ->
ok = file:write_file(File, Bin1),
application:set_env(emqx_management, bootstrap_apps_file, File),
{ok, _} = application:ensure_all_started(emqx_management),
?assert(emqx_mgmt_auth:is_authorized(<<"test-1">>, <<"secret-1">>)),
?assert(emqx_mgmt_auth:is_authorized(<<"test-2">>, <<"secret-2">>)),
?assertNot(emqx_mgmt_auth:is_authorized(<<"test-1">>, <<"new-secret-1">>)),
?assertNot(emqx_mgmt_auth:is_authorized(<<"test-2">>, <<"new-secret-2">>)),
?assertNot(emqx_mgmt_auth:is_authorized(<<"test-1">>, <<"secret-1">>)),
?assertNot(emqx_mgmt_auth:is_authorized(<<"test-2">>, <<"secret-2">>)),
?assert(emqx_mgmt_auth:is_authorized(<<"test-1">>, <<"new-secret-1">>)),
?assert(emqx_mgmt_auth:is_authorized(<<"test-2">>, <<"new-secret-2">>)),
application:stop(emqx_management).
t_bootstrap_user_file_not_found(_) ->
@ -84,13 +84,17 @@ t_load_invalid_username_failed(_) ->
File = "./bootstrap_apps.txt",
ok = file:write_file(File, Bin),
check_load_failed(File),
?assert(emqx_mgmt_auth:is_authorized(<<"test-1">>, <<"password-1">>)),
?assertNot(emqx_mgmt_auth:is_authorized(<<"test&2">>, <<"password-2">>)),
ok.
t_load_invalid_format_failed(_) ->
Bin = <<"test-1:password-1\ntest-2password-2">>,
Bin = <<"test-1:password-1\ntest-2 password-2">>,
File = "./bootstrap_apps.txt",
ok = file:write_file(File, Bin),
check_load_failed(File),
?assert(emqx_mgmt_auth:is_authorized(<<"test-1">>, <<"password-1">>)),
?assertNot(emqx_mgmt_auth:is_authorized(<<"test-2">>, <<"password-2">>)),
ok.
check_load_failed(File) ->
@ -98,5 +102,4 @@ check_load_failed(File) ->
application:stop(emqx_management),
application:set_env(emqx_management, bootstrap_apps_file, File),
?assertMatch({error, _}, application:ensure_all_started(emqx_management)),
?assertNot(lists:member(emqx_management, application:which_applications())),
?assert(emqx_mgmt_auth:need_bootstrap()).
?assertNot(lists:member(emqx_management, application:which_applications())).

View File

@ -1,7 +1,11 @@
# v4.4.14
## Enhancements
- Add a password complexity requirement when adding or modifying Dashboard users via the API. Now passwords must contain at least 2 of alphabetic, numeric and special characters, and must be 8 to 64 characters long [#9696](https://github.com/emqx/emqx/pull/9696).
## Bug fixes
## Bug Fixes
- Fix dashboard password validator is too simple. Now dashboard password must contain at least two different kind of characters from groups of letters, numbers and special characters. [#9696](https://github.com/emqx/emqx/pull/9696).
- Fix the problem that adding or importing Dashboard users via the API fails to add complex passwords due to incorrect checksum of the passwords [#9692](https://github.com/emqx/emqx/pull/9692).
- Fix load bootstrap_app_file's apps is not sync when reboot. [#9692](https://github.com/emqx/emqx/pull/9692).

View File

@ -2,5 +2,10 @@
## 增强
- 通过 API 添加、修改 Dashboard 用户时,增加对密码复杂度的要求。现在密码必须包含字母、数字以及特殊字符中的至少 2 种,并且长度范围必须是 8~64 个字符 [#9696](https://github.com/emqx/emqx/pull/9696)。
## 修复
- 修复通过 API 添加或者导入 Dashboard 用户时,对密码进行了错误的校验,导致复杂密码添加失败的问题 [#9696](https://github.com/emqx/emqx/pull/9696)。
- 修复 boostrap_apps_file 文件更新后未同步至数据库,导致变更的 apps 无法生效 [#9692](https://github.com/emqx/emqx/pull/9692)。

View File

@ -29,7 +29,7 @@
-ifndef(EMQX_ENTERPRISE).
-define(EMQX_RELEASE, {opensource, "4.4.14-alpha.1"}).
-define(EMQX_RELEASE, {opensource, "4.4.14"}).
-else.

View File

@ -1,6 +1,6 @@
{application, emqx_dashboard,
[{description, "EMQX Web Dashboard"},
{vsn, "4.4.13"}, % strict semver, bump manually!
{vsn, "4.4.14"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_dashboard_sup]},
{applications, [kernel,stdlib,mnesia,minirest]},

View File

@ -23,6 +23,7 @@
-include("emqx_dashboard.hrl").
-include_lib("emqx/include/logger.hrl").
-define(DEFAULT_PASSWORD, <<"public">>).
-define(INVALID_PASSWORD_MSG, <<"The password must contain at least two different kind of characters from groups of letters, numbers, and special characters. For example, if password is composed from letters, it must contain at least one number or a special character.">>).
-boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}).
@ -41,8 +42,10 @@
, lookup_user/1
, change_password/2
, change_password/3
, force_change_password/2
, all_users/0
, check/2
, hash/1
]).
%% gen_server Function Exports
@ -79,7 +82,7 @@ start_link() ->
-spec(add_user(binary(), binary(), binary()) -> ok | {error, any()}).
add_user(Username, Password, Tags) when is_binary(Username), is_binary(Password) ->
case {emqx_misc:is_sane_id(Username), emqx_misc:is_sane_id(Password, 2, 32)} of
case {emqx_misc:is_sane_id(Username), is_valid_pwd(Password)} of
{ok, ok} ->
Admin = #mqtt_admin{username = Username, password = hash(Password), tags = Tags},
return(mnesia:transaction(fun add_user_/1, [Admin]));
@ -88,17 +91,12 @@ add_user(Username, Password, Tags) when is_binary(Username), is_binary(Password)
end.
force_add_user(Username, Password, Tags) ->
case {emqx_misc:is_sane_id(Username), emqx_misc:is_sane_id(Password, 2, 32)} of
{ok, ok} ->
AddFun = fun() ->
mnesia:write(#mqtt_admin{username = Username, password = Password, tags = Tags})
end,
case mnesia:transaction(AddFun) of
{atomic, ok} -> ok;
{aborted, Reason} -> {error, Reason}
end;
{{error, Reason}, _} -> {error, Reason};
{_, {error, Reason}} -> {error, Reason}
AddFun = fun() ->
mnesia:write(#mqtt_admin{username = Username, password = Password, tags = Tags})
end,
case mnesia:transaction(AddFun) of
{atomic, ok} -> ok;
{aborted, Reason} -> {error, Reason}
end.
%% @private
@ -138,6 +136,12 @@ change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) ->
end.
change_password(Username, Password) when is_binary(Username), is_binary(Password) ->
case is_valid_pwd(Password) of
ok -> change_password_hash(Username, hash(Password));
{error, Error} -> {error, Error}
end.
force_change_password(Username, Password) when is_binary(Username), is_binary(Password) ->
change_password_hash(Username, hash(Password)).
change_password_hash(Username, PasswordHash) ->
@ -145,6 +149,37 @@ change_password_hash(Username, PasswordHash) ->
User#mqtt_admin{password = PasswordHash}
end).
-define(LOW_LETTER_CHARS, "abcdefghijklmnopqrstuvwxyz").
-define(UPPER_LETTER_CHARS, "ABCDEFGHIJKLMNOPQRSTUVWXYZ").
-define(LETTER, ?LOW_LETTER_CHARS ++ ?UPPER_LETTER_CHARS).
-define(NUMBER, "0123456789").
-define(SPECIAL_CHARS, "!@#$%^&*()_+-=[]{}\"|;':,./<>?`~ ").
is_valid_pwd(Password) when is_binary(Password) ->
is_valid_pwd(binary_to_list(Password));
is_valid_pwd(Password) ->
Len = erlang:length(Password),
case Len >= 8 andalso Len =< 64 of
true ->
Letter = contain(Password, ?LETTER),
Number = contain(Password, ?NUMBER),
Special = contain(Password, ?SPECIAL_CHARS),
OK = lists:filter(fun(C) -> C end, [Letter, Number, Special]),
case length(OK) >= 2 of
true ->
%% regex-any-ascii-character
case re:run(Password, "[^\\x00-\\x7F]+", [unicode, {capture, none}]) of
match -> {error, <<"only ascii characters are allowed in the password">>};
nomatch -> ok
end;
_ -> {error, ?INVALID_PASSWORD_MSG}
end;
false ->
{error, <<"The password length: 8-64">>}
end.
contain(Xs, Spec) -> lists:any(fun(X) -> lists:member(X, Spec) end, Xs).
update_pwd(Username, Fun) ->
Trans = fun() ->
User =
@ -317,3 +352,36 @@ maybe_warn_default_pwd() ->
false ->
ok
end.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
password_test() ->
?assertEqual({error, <<"The password length: 8-64">>}, is_valid_pwd(<<"123">>)),
MaxPassword = iolist_to_binary([lists:duplicate(63, "x"), "1"]),
?assertEqual(ok, is_valid_pwd(MaxPassword)),
TooLongPassword = lists:duplicate(65, "y"),
?assertEqual({error, <<"The password length: 8-64">>}, is_valid_pwd(TooLongPassword)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, is_valid_pwd(<<"12345678">>)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, is_valid_pwd(?LETTER)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, is_valid_pwd(?NUMBER)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, is_valid_pwd(?SPECIAL_CHARS)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, is_valid_pwd(<<"映映映映无天在请"/utf8>>)),
?assertEqual({error, <<"only ascii characters are allowed in the password">>}, is_valid_pwd(<<"test_for_non_ascii1中"/utf8>>)),
?assertEqual({error, <<"only ascii characters are allowed in the password">>}, is_valid_pwd(<<"云☁test_for_unicode"/utf8>>)),
?assertEqual(ok, is_valid_pwd(?LOW_LETTER_CHARS ++ ?NUMBER)),
?assertEqual(ok, is_valid_pwd(?UPPER_LETTER_CHARS ++ ?NUMBER)),
?assertEqual(ok, is_valid_pwd(?LOW_LETTER_CHARS ++ ?SPECIAL_CHARS)),
?assertEqual(ok, is_valid_pwd(?UPPER_LETTER_CHARS ++ ?SPECIAL_CHARS)),
?assertEqual(ok, is_valid_pwd(?SPECIAL_CHARS ++ ?NUMBER)),
?assertEqual(ok, is_valid_pwd(<<"abckldiekflkdf12">>)),
?assertEqual(ok, is_valid_pwd(<<"abckldiekflkdf w">>)),
?assertEqual(ok, is_valid_pwd(<<"# abckldiekflkdf w">>)),
?assertEqual(ok, is_valid_pwd(<<"# 12344858">>)),
?assertEqual(ok, is_valid_pwd(<<"# %12344858">>)),
ok.
-endif.

View File

@ -71,7 +71,7 @@ init_per_testcase(Case, Config) ->
end_per_testcase(Case, Config) ->
%% revert to default password
emqx_dashboard_admin:change_password(<<"admin">>, <<"public">>),
emqx_dashboard_admin:force_change_password(<<"admin">>, <<"public">>),
?MODULE:Case({'end', Config}).
t_overview({init, Config}) -> Config;
@ -85,35 +85,36 @@ t_admins_add_delete(_) ->
?assertEqual({error,<<"0 < Length =< 256">>},
emqx_dashboard_admin:add_user(<<"">>, <<"password">>, <<"tag1">>)),
?assertEqual({error,<<"2 < Length =< 32">>},
?assertEqual({error,<<"The password length: 8-64">>},
emqx_dashboard_admin:add_user(<<"badusername">>, <<"">>, <<"tag1">>)),
?assertEqual({error,<<"2 < Length =< 32">>},
?assertEqual({error,<<"The password length: 8-64">>},
emqx_dashboard_admin:add_user(<<"badusername">>, <<"p">>, <<"tag1">>)),
P33 = iolist_to_binary(lists:duplicate(33, <<"p">>)),
?assertEqual({error,<<"2 < Length =< 32">>},
emqx_dashboard_admin:add_user(<<"badusername">>, P33, <<"tag1">>)),
P32 = iolist_to_binary(lists:duplicate(32, <<"p">>)),
?assertEqual(ok, emqx_dashboard_admin:add_user(<<"goodusername">>, P32, <<"tag1">>)),
P65 = iolist_to_binary(lists:duplicate(65, <<"p">>)),
?assertEqual({error,<<"The password length: 8-64">>},
emqx_dashboard_admin:add_user(<<"badusername">>, P65, <<"tag1">>)),
P64 = iolist_to_binary([<<"1">> | lists:duplicate(63, <<"p">>)]),
?assertEqual(ok, emqx_dashboard_admin:add_user(<<"goodusername">>, P64, <<"tag1">>)),
ok = emqx_dashboard_admin:remove_user(<<"goodusername">>),
ok = emqx_dashboard_admin:add_user(<<"username">>, <<"password">>, <<"tag">>),
ok = emqx_dashboard_admin:add_user(<<"username1">>, <<"password1">>, <<"tag1">>),
ok = emqx_dashboard_admin:add_user(<<"username1">>, <<"password1">>, <<"tag">>),
ok = emqx_dashboard_admin:add_user(<<"username2">>, <<"password2">>, <<"tag1">>),
ok = emqx_dashboard_admin:add_user(<<"1username1">>, <<"password1">>, <<"tag1">>),
{error, _} = emqx_dashboard_admin:add_user(<<"u/sername1">>, <<"password1">>, <<"tag1">>),
{error, _} = emqx_dashboard_admin:add_user(<<"/username1">>, <<"password1">>, <<"tag1">>),
Admins = emqx_dashboard_admin:all_users(),
?assertEqual(4, length(Admins)),
ok = emqx_dashboard_admin:remove_user(<<"username1">>),
ok = emqx_dashboard_admin:remove_user(<<"username2">>),
ok = emqx_dashboard_admin:remove_user(<<"1username1">>),
Users = emqx_dashboard_admin:all_users(),
?assertEqual(2, length(Users)),
ok = emqx_dashboard_admin:change_password(<<"username">>, <<"password">>, <<"pwd">>),
{error, _} = emqx_dashboard_admin:change_password(<<"username1">>, <<"password1">>, <<"password">>),
ok = emqx_dashboard_admin:change_password(<<"username1">>, <<"password1">>, <<"password+">>),
timer:sleep(10),
?assert(request_dashboard(get, api_path("brokers"), auth_header_("username", "pwd"))),
?assert(request_dashboard(get, api_path("brokers"), auth_header_("username1", "password+"))),
ok = emqx_dashboard_admin:remove_user(<<"username">>),
ok = emqx_dashboard_admin:remove_user(<<"username1">>),
?assertNotEqual(true, request_dashboard(get, api_path("brokers"),
auth_header_("username", "pwd"))).
auth_header_("username1", "password+"))).
t_admins_persist_default_password({init, Config}) -> Config;
t_admins_persist_default_password({'end', _Config}) -> ok;
@ -212,16 +213,19 @@ t_rest_api(_Config) ->
?assert(lists:member(#{<<"username">> => <<"admin">>, <<"tags">> => <<"administrator">>},
Users)),
{ok, ErrorRes} = http_put("change_pwd/admin", [{<<"old_pwd">>, <<"public">>}, {<<"new_pwd">>, <<"simplepwd">>}]),
?assertMatch(#{<<"message">> := _}, json(ErrorRes)),
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("change_pwd/admin", #{<<"old_pwd">> => <<"public">>, <<"new_pwd">> => <<"newpwd">>})
, http_post("auth", #{<<"username">> => <<"admin">>, <<"password">> => <<"newpwd">>})
, http_post("users", #{<<"username">> => <<"username1">>, <<"password">> => <<"passwd+123">>})
, http_post("auth", #{<<"username">> => <<"username1">>, <<"password">> => <<"passwd+123">>})
, http_delete("users/username1")
, http_put("change_pwd/admin", #{<<"old_pwd">> => <<"public">>, <<"new_pwd">> => <<"newpwd+123">>})
, http_post("auth", #{<<"username">> => <<"admin">>, <<"password">> => <<"newpwd+123">>})
]],
ok.
@ -236,18 +240,18 @@ t_cli({init, Config}) -> Config;
t_cli({'end', _Config}) -> ok;
t_cli(_Config) ->
[mnesia:dirty_delete({mqtt_admin, Admin}) || Admin <- mnesia:dirty_all_keys(mqtt_admin)],
emqx_dashboard_cli:admins(["add", "username", "password"]),
emqx_dashboard_cli:admins(["add", "username", "password1"]),
[{mqtt_admin, <<"username">>, <<Salt:4/binary, Hash/binary>>, _}] =
emqx_dashboard_admin:lookup_user(<<"username">>),
?assertEqual(Hash, erlang:md5(<<Salt/binary, <<"password">>/binary>>)),
emqx_dashboard_cli:admins(["passwd", "username", "newpassword"]),
?assertEqual(Hash, erlang:md5(<<Salt/binary, <<"password1">>/binary>>)),
emqx_dashboard_cli:admins(["passwd", "username", "newpassword1"]),
[{mqtt_admin, <<"username">>, <<Salt1:4/binary, Hash1/binary>>, _}] =
emqx_dashboard_admin:lookup_user(<<"username">>),
?assertEqual(Hash1, erlang:md5(<<Salt1/binary, <<"newpassword">>/binary>>)),
?assertEqual(Hash1, erlang:md5(<<Salt1/binary, <<"newpassword1">>/binary>>)),
emqx_dashboard_cli:admins(["del", "username"]),
[] = emqx_dashboard_admin:lookup_user(<<"username">>),
emqx_dashboard_cli:admins(["add", "admin1", "pass1"]),
emqx_dashboard_cli:admins(["add", "admin2", "passw2"]),
emqx_dashboard_cli:admins(["add", "admin1", "password+1"]),
emqx_dashboard_cli:admins(["add", "admin2", "password+2"]),
AdminList = emqx_dashboard_admin:all_users(),
?assertEqual(2, length(AdminList)).

View File

@ -13,7 +13,7 @@ case "${PKG_VSN}" in
;;
4.4*)
# keep the above 4.3 untouched, otherwise conflicts!
EMQX_CE_DASHBOARD_VERSION='v4.4.7'
EMQX_CE_DASHBOARD_VERSION='v4.4.8'
EMQX_EE_DASHBOARD_VERSION='v4.4.18'
;;
*)

View File

@ -6,7 +6,7 @@
%% the emqx `release' version, which in turn is comprised of several
%% apps, one of which is this. See `emqx_release.hrl' for more
%% info.
{vsn, "4.4.14"}, % strict semver, bump manually!
{vsn, "4.4.15"}, % strict semver, bump manually!
{modules, []},
{registered, []},
{applications, [ kernel

View File

@ -1,14 +1,18 @@
%% -*- mode: erlang -*-
%% Unless you know what you are doing, DO NOT edit manually!!
{VSN,
[{"4.4.13",
[{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]},
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
[{"4.4.14",
[{load_module,emqx_misc,brutal_purge,soft_purge,[]},
{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]}]},
{"4.4.13",
[{load_module,emqx_misc,brutal_purge,soft_purge,[]},
{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]},
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
{load_module,emqx_relup,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
{"4.4.12",
[{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]},
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
[{load_module,emqx_misc,brutal_purge,soft_purge,[]},
{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]},
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
{load_module,emqx_relup,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
@ -412,14 +416,18 @@
{apply,{application,set_env,
[gen_rpc,insecure_auth_fallback_allowed,true]}}]},
{<<".*">>,[]}],
[{"4.4.13",
[{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]},
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
[{"4.4.14",
[{load_module,emqx_misc,brutal_purge,soft_purge,[]},
{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]}]},
{"4.4.13",
[{load_module,emqx_misc,brutal_purge,soft_purge,[]},
{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]},
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
{load_module,emqx_relup,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
{"4.4.12",
[{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]},
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
[{load_module,emqx_misc,brutal_purge,soft_purge,[]},
{load_module,emqx_rule_actions_trans,brutal_purge,soft_purge,[]},
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
{load_module,emqx_relup,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},