feat: add dashboard password validate
This commit is contained in:
parent
050d245fa5
commit
a523fa2fa2
|
@ -92,21 +92,79 @@ add_default_user() ->
|
|||
add_user(Username, Password, Desc) when
|
||||
is_binary(Username), is_binary(Password)
|
||||
->
|
||||
case legal_username(Username) of
|
||||
true ->
|
||||
return(
|
||||
mria:transaction(?DASHBOARD_SHARD, fun add_user_/3, [Username, Password, Desc])
|
||||
);
|
||||
false ->
|
||||
case {legal_username(Username), legal_password(Password)} of
|
||||
{ok, ok} -> do_add_user(Username, Password, Desc);
|
||||
{{error, Reason}, _} -> {error, Reason};
|
||||
{_, {error, Reason}} -> {error, Reason}
|
||||
end.
|
||||
|
||||
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, <<
|
||||
"Bad Username."
|
||||
" Only upper and lower case letters, numbers and underscores are supported"
|
||||
>>}
|
||||
>>};
|
||||
match ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% 0 - 9 or A -Z or a - z or $_
|
||||
legal_username(<<>>) -> false;
|
||||
legal_username(UserName) -> nomatch /= re:run(UserName, "^[_a-zA-Z0-9]*$").
|
||||
-define(LOW_LETTER_CHARS, "abcdefghijklmnopqrstuvwxyz").
|
||||
-define(UPPER_LETTER_CHARS, "ABCDEFGHIJKLMNOPQRSTUVWXYZ").
|
||||
-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
|
||||
force_add_user(Username, Password, Desc) ->
|
||||
|
@ -188,7 +246,10 @@ change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) ->
|
|||
end.
|
||||
|
||||
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) ->
|
||||
ChangePWD =
|
||||
|
@ -292,6 +353,45 @@ add_default_user(Username, Password) when ?EMPTY_KEY(Username) orelse ?EMPTY_KEY
|
|||
{ok, empty};
|
||||
add_default_user(Username, Password) ->
|
||||
case lookup_user(Username) of
|
||||
[] -> add_user(Username, Password, <<"administrator">>);
|
||||
[] -> do_add_user(Username, Password, <<"administrator">>);
|
||||
_ -> {ok, default_user_exists}
|
||||
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.
|
||||
|
|
|
@ -51,7 +51,7 @@ end_suite() ->
|
|||
|
||||
t_check_user(_) ->
|
||||
Username = <<"admin1">>,
|
||||
Password = <<"public">>,
|
||||
Password = <<"public_1">>,
|
||||
BadUsername = <<"admin_bad">>,
|
||||
BadPassword = <<"public_bad">>,
|
||||
EmptyUsername = <<>>,
|
||||
|
@ -108,7 +108,7 @@ t_lookup_user(_) ->
|
|||
|
||||
t_all_users(_) ->
|
||||
Username = <<"admin_all">>,
|
||||
Password = <<"public">>,
|
||||
Password = <<"public_2">>,
|
||||
{ok, _} = emqx_dashboard_admin:add_user(Username, Password, <<"desc">>),
|
||||
All = emqx_dashboard_admin:all_users(),
|
||||
?assert(erlang:length(All) >= 1),
|
||||
|
@ -153,6 +153,7 @@ t_change_password(_) ->
|
|||
Description = <<"change_description">>,
|
||||
|
||||
NewPassword = <<"new_password">>,
|
||||
NewBadPassword = <<"public">>,
|
||||
|
||||
BadChangeUser = <<"change_user_bad">>,
|
||||
|
||||
|
@ -163,14 +164,17 @@ t_change_password(_) ->
|
|||
{error, <<"password_error">>} =
|
||||
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">>} =
|
||||
emqx_dashboard_admin:change_password(BadChangeUser, OldPassword, NewPassword),
|
||||
ok.
|
||||
|
||||
t_clean_token(_) ->
|
||||
Username = <<"admin_token">>,
|
||||
Password = <<"public">>,
|
||||
NewPassword = <<"public1">>,
|
||||
Password = <<"public_www1">>,
|
||||
NewPassword = <<"public_www2">>,
|
||||
{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),
|
||||
|
|
Loading…
Reference in New Issue