feat: add dashboard password validate

This commit is contained in:
Zhongwen Deng 2023-01-16 17:33:28 +08:00
parent 050d245fa5
commit a523fa2fa2
2 changed files with 120 additions and 16 deletions

View File

@ -92,21 +92,79 @@ add_default_user() ->
add_user(Username, Password, Desc) when add_user(Username, Password, Desc) when
is_binary(Username), is_binary(Password) is_binary(Username), is_binary(Password)
-> ->
case legal_username(Username) of case {legal_username(Username), legal_password(Password)} of
true -> {ok, ok} -> do_add_user(Username, Password, Desc);
return( {{error, Reason}, _} -> {error, Reason};
mria:transaction(?DASHBOARD_SHARD, fun add_user_/3, [Username, Password, Desc]) {_, {error, Reason}} -> {error, Reason}
); end.
false ->
do_add_user(Username, Password, Desc) ->
Res = mria:transaction(?DASHBOARD_SHARD, fun add_user_/3, [Username, Password, Desc]),
return(Res).
%% 0-9 or A-Z or a-z or $_
legal_username(<<>>) ->
{error, <<"Username can not be empty">>};
legal_username(UserName) ->
case re:run(UserName, "^[_a-zA-Z0-9]*$", [{capture, none}]) of
nomatch ->
{error, << {error, <<
"Bad Username." "Bad Username."
" Only upper and lower case letters, numbers and underscores are supported" " Only upper and lower case letters, numbers and underscores are supported"
>>} >>};
match ->
ok
end. end.
%% 0 - 9 or A -Z or a - z or $_ -define(LOW_LETTER_CHARS, "abcdefghijklmnopqrstuvwxyz").
legal_username(<<>>) -> false; -define(UPPER_LETTER_CHARS, "ABCDEFGHIJKLMNOPQRSTUVWXYZ").
legal_username(UserName) -> nomatch /= re:run(UserName, "^[_a-zA-Z0-9]*$"). -define(LETTER, ?LOW_LETTER_CHARS ++ ?UPPER_LETTER_CHARS).
-define(NUMBER, "0123456789").
-define(SPECIAL_CHARS, "!@#$%^&*()_+-=[]{}\"|;':,./<>?`~ ").
-define(INVALID_PASSWORD_MSG, <<
"Bad username."
"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."
>>).
-define(BAD_PASSWORD_LEN, <<"The range of password length is 8~64">>).
legal_password(Password) when is_binary(Password) ->
legal_password(binary_to_list(Password));
legal_password(Password) when is_list(Password) ->
legal_password(Password, erlang:length(Password)).
legal_password(Password, Len) when Len >= 8 andalso Len =< 64 ->
case is_mixed_password(Password) of
true -> ascii_character_validate(Password);
false -> {error, ?INVALID_PASSWORD_MSG}
end;
legal_password(_Password, _Len) ->
{error, ?BAD_PASSWORD_LEN}.
%% The password must contain at least two different kind of characters
%% from groups of letters, numbers, and special characters.
is_mixed_password(Password) -> is_mixed_password(Password, [?NUMBER, ?LETTER, ?SPECIAL_CHARS], 0).
is_mixed_password(_Password, _Chars, 2) ->
true;
is_mixed_password(_Password, [], _Count) ->
false;
is_mixed_password(Password, [Chars | Rest], Count) ->
NewCount =
case contain(Password, Chars) of
true -> Count + 1;
false -> Count
end,
is_mixed_password(Password, Rest, NewCount).
%% regex-non-ascii-character, such as Chinese, Japanese, Korean, etc.
ascii_character_validate(Password) ->
case re:run(Password, "[^\\x00-\\x7F]+", [unicode, {capture, none}]) of
match -> {error, <<"Only ascii characters are allowed in the password">>};
nomatch -> ok
end.
contain(Xs, Spec) -> lists:any(fun(X) -> lists:member(X, Spec) end, Xs).
%% black-magic: force overwrite a user %% black-magic: force overwrite a user
force_add_user(Username, Password, Desc) -> force_add_user(Username, Password, Desc) ->
@ -188,7 +246,10 @@ change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) ->
end. end.
change_password(Username, Password) when is_binary(Username), is_binary(Password) -> change_password(Username, Password) when is_binary(Username), is_binary(Password) ->
change_password_hash(Username, hash(Password)). case legal_password(Password) of
ok -> change_password_hash(Username, hash(Password));
Error -> Error
end.
change_password_hash(Username, PasswordHash) -> change_password_hash(Username, PasswordHash) ->
ChangePWD = ChangePWD =
@ -292,6 +353,45 @@ add_default_user(Username, Password) when ?EMPTY_KEY(Username) orelse ?EMPTY_KEY
{ok, empty}; {ok, empty};
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">>); [] -> do_add_user(Username, Password, <<"administrator">>);
_ -> {ok, default_user_exists} _ -> {ok, default_user_exists}
end. end.
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
legal_password_test() ->
?assertEqual({error, ?BAD_PASSWORD_LEN}, legal_password(<<"123">>)),
MaxPassword = iolist_to_binary([lists:duplicate(63, "x"), "1"]),
?assertEqual(ok, legal_password(MaxPassword)),
TooLongPassword = lists:duplicate(65, "y"),
?assertEqual({error, ?BAD_PASSWORD_LEN}, legal_password(TooLongPassword)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(<<"12345678">>)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(?LETTER)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(?NUMBER)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(?SPECIAL_CHARS)),
?assertEqual({error, ?INVALID_PASSWORD_MSG}, legal_password(<<"映映映映无天在请"/utf8>>)),
?assertEqual(
{error, <<"Only ascii characters are allowed in the password">>},
legal_password(<<"test_for_non_ascii1中"/utf8>>)
),
?assertEqual(
{error, <<"Only ascii characters are allowed in the password">>},
legal_password(<<"云☁test_for_unicode"/utf8>>)
),
?assertEqual(ok, legal_password(?LOW_LETTER_CHARS ++ ?NUMBER)),
?assertEqual(ok, legal_password(?UPPER_LETTER_CHARS ++ ?NUMBER)),
?assertEqual(ok, legal_password(?LOW_LETTER_CHARS ++ ?SPECIAL_CHARS)),
?assertEqual(ok, legal_password(?UPPER_LETTER_CHARS ++ ?SPECIAL_CHARS)),
?assertEqual(ok, legal_password(?SPECIAL_CHARS ++ ?NUMBER)),
?assertEqual(ok, legal_password(<<"abckldiekflkdf12">>)),
?assertEqual(ok, legal_password(<<"abckldiekflkdf w">>)),
?assertEqual(ok, legal_password(<<"# abckldiekflkdf w">>)),
?assertEqual(ok, legal_password(<<"# 12344858">>)),
?assertEqual(ok, legal_password(<<"# %12344858">>)),
ok.
-endif.

View File

@ -51,7 +51,7 @@ end_suite() ->
t_check_user(_) -> t_check_user(_) ->
Username = <<"admin1">>, Username = <<"admin1">>,
Password = <<"public">>, Password = <<"public_1">>,
BadUsername = <<"admin_bad">>, BadUsername = <<"admin_bad">>,
BadPassword = <<"public_bad">>, BadPassword = <<"public_bad">>,
EmptyUsername = <<>>, EmptyUsername = <<>>,
@ -108,7 +108,7 @@ t_lookup_user(_) ->
t_all_users(_) -> t_all_users(_) ->
Username = <<"admin_all">>, Username = <<"admin_all">>,
Password = <<"public">>, Password = <<"public_2">>,
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>), {ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>),
All = emqx_dashboard_admin:all_users(), All = emqx_dashboard_admin:all_users(),
?assert(erlang:length(All) >= 1), ?assert(erlang:length(All) >= 1),
@ -153,6 +153,7 @@ t_change_password(_) ->
Description = <<"change_description">>, Description = <<"change_description">>,
NewPassword = <<"new_password">>, NewPassword = <<"new_password">>,
NewBadPassword = <<"public">>,
BadChangeUser = <<"change_user_bad">>, BadChangeUser = <<"change_user_bad">>,
@ -163,14 +164,17 @@ t_change_password(_) ->
{error, <<"password_error">>} = {error, <<"password_error">>} =
emqx_dashboard_admin:change_password(User, OldPassword, NewPassword), emqx_dashboard_admin:change_password(User, OldPassword, NewPassword),
{error, <<"The range of password length is 8~64">>} =
emqx_dashboard_admin:change_password(User, NewPassword, NewBadPassword),
{error, <<"username_not_found">>} = {error, <<"username_not_found">>} =
emqx_dashboard_admin:change_password(BadChangeUser, OldPassword, NewPassword), emqx_dashboard_admin:change_password(BadChangeUser, OldPassword, NewPassword),
ok. ok.
t_clean_token(_) -> t_clean_token(_) ->
Username = <<"admin_token">>, Username = <<"admin_token">>,
Password = <<"public">>, Password = <<"public_www1">>,
NewPassword = <<"public1">>, NewPassword = <<"public_www2">>,
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>), {ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>),
{ok, Token} = emqx_dashboard_admin:sign_token(Username, Password), {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
ok = emqx_dashboard_admin:verify_token(Token), ok = emqx_dashboard_admin:verify_token(Token),