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
|
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.
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue